diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 77afab7d..20feb812 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -3,17 +3,16 @@ name: snapx on: push: branches: - - develop - - master + - '**' pull_request: branches: - develop env: - GITVERSION_VERSION: 5.6.6 + GITVERSION_VERSION: 5.7.0 MSVS_TOOLSET_VERSION: 16 - DOTNET_FRAMEWORK_VERSION: net5.0 - DOTNET_SDK_VERSION: 5.0.200 + SNAPX_DOTNET_FRAMEWORK_VERSION: net6.0 + DOTNET_NET60_VERSION: 6.0.100 DOTNET_CLI_TELEMETRY_OPTOUT: 1 DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: 1 @@ -29,9 +28,13 @@ jobs: SNAPX_VERSION: ${{ steps.set-version.outputs.SNAPX_VERSION }} steps: - name: Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 with: fetch-depth: 0 + + - name: Add dotnet tools to environment path + shell: pwsh + run: echo "${HOME}/.dotnet/tools" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - id: set-version name: Setup GitVersion and set build version @@ -54,7 +57,7 @@ jobs: SNAPX_VERSION: ${{ needs.setup.outputs.SNAPX_VERSION }} steps: - name: Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 with: lfs: true submodules: true @@ -66,23 +69,28 @@ jobs: username: ${{ github.actor }} password: ${{secrets.GITHUB_TOKEN }} + - name: Setup .NET 6.0 + uses: actions/setup-dotnet@v1.8.2 + with: + dotnet-version: ${{ env.DOTNET_NET60_VERSION }} + - name: Build native shell: pwsh - run: ./build.ps1 Bootstrap-Unix -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} + run: ./build.ps1 Bootstrap-Unix -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} - name: Test native if: matrix.rid != 'linux-arm64' shell: pwsh - run: ./build.ps1 Run-Native-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} + run: ./build.ps1 Run-Native-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} - name: Test .NET if: matrix.rid != 'linux-arm64' shell: pwsh - run: ./build.ps1 Run-Dotnet-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} + run: ./build.ps1 Run-Dotnet-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} - name: Collect artifacts env: - SNAPX_UNIX_SETUP_ZIP_REL_DIR: build/dotnet/${{ matrix.rid }}/Snap.Installer/${{ env.DOTNET_FRAMEWORK_VERSION }}/${{ matrix.configuration }}/publish + SNAPX_UNIX_SETUP_ZIP_REL_DIR: build/dotnet/${{ matrix.rid }}/Snap.Installer/${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }}/${{ matrix.configuration }}/publish SNAPX_UNIX_CORERUN_REL_DIR: build/native/Unix/${{ matrix.rid }}/${{ matrix.configuration }}/Snap.CoreRun SNAPX_UNIX_CORERUN_TESTS_REL_DIR: build/native/Unix/${{ matrix.rid }}/${{ matrix.configuration }}/Snap.CoreRun.Tests SNAPX_UNIX_PAL_REL_DIR: build/native/Unix/${{ matrix.rid }}/${{ matrix.configuration }}/Snap.CoreRun.Pal @@ -122,30 +130,30 @@ jobs: SNAPX_VERSION: ${{ needs.setup.outputs.SNAPX_VERSION }} steps: - name: Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 with: lfs: true submodules: true - - - name: Setup dotnet ${{ env.DOTNET_SDK_VERSION }} - uses: actions/setup-dotnet@v1.7.2 + + - name: Setup .NET 6.0 + uses: actions/setup-dotnet@v1.8.2 with: - dotnet-version: '${{ env.DOTNET_SDK_VERSION }}' + dotnet-version: ${{ env.DOTNET_NET60_VERSION }} - name: Build native - run: ./build.ps1 Bootstrap-Windows -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} + run: ./build.ps1 Bootstrap-Windows -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} - name: Test native shell: pwsh - run: ./build.ps1 Run-Native-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} + run: ./build.ps1 Run-Native-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} - name: Test .NET shell: pwsh - run: ./build.ps1 Run-Dotnet-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} + run: ./build.ps1 Run-Dotnet-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} - name: Collect artifacts env: - SNAPX_WINDOWS_SETUP_ZIP_REL_DIR: build/dotnet/${{ matrix.rid }}/Snap.Installer/${{ env.DOTNET_FRAMEWORK_VERSION }}/${{ matrix.configuration }}/publish + SNAPX_WINDOWS_SETUP_ZIP_REL_DIR: build/dotnet/${{ matrix.rid }}/Snap.Installer/${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }}/${{ matrix.configuration }}/publish SNAPX_WINDOWS_CORERUN_REL_DIR: build/native/Windows/${{ matrix.rid }}/${{ matrix.configuration }}/Snap.CoreRun/${{ matrix.configuration }} SNAPX_WINDOWS_PAL_REL_DIR: build/native/Windows/${{ matrix.rid }}/${{ matrix.configuration }}/Snap.CoreRun.Pal/${{ matrix.configuration }} run: | @@ -181,7 +189,7 @@ jobs: # SNAPX_VERSION: ${{ needs.setup.outputs.SNAPX_VERSION }} # steps: # - name: Checkout - # uses: actions/checkout@v2.3.4 + # uses: actions/checkout@v2.4.0 # with: # fetch-depth: 0 # @@ -191,10 +199,12 @@ jobs: # name: ubuntu-latest-${{ matrix.rid }}-${{ matrix.configuration }} # path: ${{ github.workspace }} # - # - name: Setup dotnet ${{ env.DOTNET_SDK_VERSION }} - # uses: actions/setup-dotnet@v1.7.2 + + # - name: Setup .NET 6.0 + # uses: actions/setup-dotnet@v1.8.2 # with: - # dotnet-version: '${{ env.DOTNET_SDK_VERSION }}' + # dotnet-version: ${{ env.DOTNET_NET60_VERSION }} + # # - name: Test native # run: | @@ -203,7 +213,7 @@ jobs: # # - name: Test .NET # shell: pwsh - # run: ./build.ps1 Run-Dotnet-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} + # run: ./build.ps1 Run-Dotnet-UnitTests -Version ${{ env.SNAPX_VERSION }} -Configuration ${{ matrix.configuration }} -CIBuild -NetCoreAppVersion ${{ env.SNAPX_DOTNET_FRAMEWORK_VERSION }} -Rid ${{ matrix.rid }} publish: if: success() && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master') @@ -214,7 +224,7 @@ jobs: SNAPX_VERSION: ${{ needs.setup.outputs.SNAPX_VERSION }} steps: - name: Checkout - uses: actions/checkout@v2.3.4 + uses: actions/checkout@v2.4.0 with: lfs: true @@ -242,11 +252,15 @@ jobs: name: windows-latest-win-x64-Release path: ${{ github.workspace }} - - name: Setup dotnet ${{ env.DOTNET_SDK_VERSION }} - uses: actions/setup-dotnet@v1.7.2 + - name: Setup .NET 6.0 + uses: actions/setup-dotnet@v1.8.2 with: - dotnet-version: '${{ env.DOTNET_SDK_VERSION }}' - + dotnet-version: ${{ env.DOTNET_NET60_VERSION }} + + - name: Add dotnet tools to environment path + shell: pwsh + run: echo "${HOME}/.dotnet/tools" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - name: Build nupkgs shell: pwsh run: ./build.ps1 -Target Snapx -CIBuild -Version ${{ env.SNAPX_VERSION }} -Configuration Release @@ -266,7 +280,7 @@ jobs: - name: Create github release tag if: github.ref == 'refs/heads/master' - uses: actions/create-release@v1 + uses: actions/create-release@v1.1.4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/GitVersion.yml b/GitVersion.yml index e90d26b6..51babd1d 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,4 +1,4 @@ -next-version: 3.0.11 +next-version: 4.0.1 mode: ContinuousDeployment continuous-delivery-fallback-tag: '' branches: diff --git a/README.md b/README.md index bfc47eef..27881ac8 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,12 @@ Checkout our sample application, [snapx demoapp](https://github.com/fintermobili - Docker >= 19.03.8 **Windows**: -- Docker Desktop >= v2.3.0.3 +- Docker Desktop >= v4.0.1 - GitVersion `dotnet tool update gitversion.tool -g` - Powershell v7 `dotnet tool update powershell -g` -- .NET SDK v5.0 +- .NET SDK v6.0 -- Visual Studio 2019 16.8 Community with C++ workload installed +- Visual Studio 2022 Preview 4.1 Community with C++ workload installed #### Bootstrap snapx @@ -47,7 +47,7 @@ Run `init.ps1` and all dependencies will be built in `Debug` and `Release` mode. ## .NET frameworks supported -- .NET >= 5.0 +- .NET 6.0 LTS ## Platforms supported @@ -61,12 +61,10 @@ Run `init.ps1` and all dependencies will be built in `Debug` and `Release` mode. - Windows Server 2019 R2 - Ubuntu Desktop x64 - - 16.04 - 18.04 - 20.04 - Ubuntu Server x64 - - 16.04 - 18.04 - 20.04 diff --git a/bootstrap.ps1 b/bootstrap.ps1 index 41db7239..4f38eb1b 100644 --- a/bootstrap.ps1 +++ b/bootstrap.ps1 @@ -232,8 +232,7 @@ function Invoke-Build-Snap-Installer { Get-Item $SnapInstallerDotnetBuildPublishDir | Remove-Item -Recurse -Force } - # R2R is only available on Windows. - $PublishReadyToRun = ($Rid.StartsWith("win-")) -and ($Configuration -eq "Debug" ? "False" : "True") + $PublishReadyToRun = "False" $RuntimeIdentifier = $Rid diff --git a/build.ps1 b/build.ps1 index 19fd5d42..5755aba9 100644 --- a/build.ps1 +++ b/build.ps1 @@ -8,7 +8,7 @@ param( [Parameter(Position = 2, ValueFromPipelineByPropertyName = $true)] [string] $DockerImageName = "snapx", [Parameter(Position = 3, ValueFromPipelineByPropertyName = $true)] - [string] $DockerVersion = "6.5", + [string] $DockerVersion = "10.0", [Parameter(Position = 4, ValueFromPipelineByPropertyName = $true)] [switch] $DockerLocal, [Parameter(Position = 5, ValueFromPipelineByPropertyName = $true)] @@ -16,7 +16,7 @@ param( [Parameter(Position = 6, ValueFromPipelineByPropertyName = $true)] [switch] $CIBuild, [Parameter(Position = 7, ValueFromPipelineByPropertyName = $true)] - [string] $NetCoreAppVersion = "net5.0", + [string] $NetCoreAppVersion = "net6.0", [Parameter(Position = 8, ValueFromPipelineByPropertyName = $true)] [string] $Version = "0.0.0", [Parameter(Position = 9, ValueFromPipelineByPropertyName = $true)] diff --git a/cmake/aarch64-linux-gnu.toolchain.cmake b/cmake/aarch64-linux-gnu.toolchain.cmake index ea927ed0..b9b9f6c4 100644 --- a/cmake/aarch64-linux-gnu.toolchain.cmake +++ b/cmake/aarch64-linux-gnu.toolchain.cmake @@ -1,8 +1,8 @@ set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) -set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc-8") -set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++-8") +set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc") +set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++") set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) diff --git a/docker/Dockerfile b/docker/Dockerfile index 27dcad9d..5e29779e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -17,8 +17,8 @@ RUN \ echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports focal-backports main restricted universe multiverse" >> /etc/apt/sources.list && \ echo "deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports focal-security main restricted universe multiverse" >> /etc/apt/sources.list && \ apt-get update && \ - apt-get install -y --no-install-recommends \ - uuid-dev:arm64 + apt-get install -y --no-install-recommends \ + uuid-dev:arm64 RUN \ dpkg --force-architecture --remove-architecture arm64 && \ @@ -28,14 +28,14 @@ RUN \ rm -rf /var/lib/apt/lists/* && \ apt-get update && \ apt-get install -y --no-install-recommends \ - gcc-aarch64-linux-gnu:amd64 g++-8-aarch64-linux-gnu:amd64 && \ + gcc-aarch64-linux-gnu:amd64 g++-aarch64-linux-gnu:amd64 && \ rm -rf /var/lib/apt/lists/* # x64 RUN \ apt-get update && \ apt-get install -y --no-install-recommends \ - cmake:amd64 make:amd64 gcc-8:amd64 g++-8:amd64 uuid-dev:amd64 lsb-core:amd64 curl:amd64 wget:amd64 && \ + cmake:amd64 make:amd64 gcc:amd64 g++:amd64 uuid-dev:amd64 lsb-core:amd64 curl:amd64 wget:amd64 && \ rm -rf /var/lib/apt/lists/* RUN \ @@ -43,14 +43,18 @@ RUN \ apt-get install -y apt-transport-https:amd64 ca-certificates:amd64 && \ wget --no-check-certificate https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb && \ dpkg -i packages-microsoft-prod.deb && \ - apt-get update && \ - apt-get install -y dotnet-sdk-5.0:amd64 && \ - dotnet tool update powershell -g + apt-get update + +RUN \ + wget https://download.visualstudio.microsoft.com/download/pr/5fcb98bb-21e1-47a5-bb8e-bb25f41a3e52/04811d5d05b7e694f040d2a13c1aae4c/dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz && \ + mkdir -p /root/dotnet && tar zxf dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -C /root/dotnet && \ + rm dotnet-sdk-6.0.100-rc.1.21463.6-linux-x64.tar.gz -RUN \ - update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 800 --slave /usr/bin/g++ g++ /usr/bin/g++-8 +RUN \ + /root/dotnet/dotnet tool update powershell -g FROM env-build as env-run -ENV PATH="/root/.dotnet/tools:${PATH}" +ENV DOTNET_ROOT="/root/dotnet" +ENV PATH="/root/dotnet:/root/.dotnet/tools:${PATH}" ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 CMD ["sh", "-c", "(cd $SNAPX_DOCKER_WORKING_DIR && pwsh ./build.ps1 $BUILD_PS_PARAMETERS)"] diff --git a/nuget.config b/nuget.config index f082520e..53cbd4fc 100644 --- a/nuget.config +++ b/nuget.config @@ -2,7 +2,6 @@ - diff --git a/snapx-dev.cmd b/snapx-dev.cmd index a77d87f5..90e216be 100644 --- a/snapx-dev.cmd +++ b/snapx-dev.cmd @@ -1,2 +1,2 @@ dotnet build -c Debug src/Snapx/Snapx.csproj -dotnet src/Snapx/bin/Debug/net5.0/Snapx.dll %* +dotnet src/Snapx/bin/Debug/net6.0/Snapx.dll %* diff --git a/snapx-dev.sh b/snapx-dev.sh index 01107120..c27c5aa8 100644 --- a/snapx-dev.sh +++ b/snapx-dev.sh @@ -1,3 +1,3 @@ #!/bin/bash dotnet build -c Debug src/Snapx/Snapx.csproj -dotnet src/Snapx/bin/Debug/net5.0/snapx.dll $* +dotnet src/Snapx/bin/Debug/net6.0/snapx.dll $* diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7561648f..10660ce5 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,13 +1,14 @@ - 9.0 + 10.0 + true - Finter Mobility As + Finter As Snapx - Copyright © Finter Mobility As + Copyright © Finter As en-US false https://github.com/fintermobilityas/snapx diff --git a/src/Snap.Installer.Tests/Snap.Installer.Tests.csproj b/src/Snap.Installer.Tests/Snap.Installer.Tests.csproj index 192b7765..5dfa5b08 100644 --- a/src/Snap.Installer.Tests/Snap.Installer.Tests.csproj +++ b/src/Snap.Installer.Tests/Snap.Installer.Tests.csproj @@ -5,7 +5,7 @@ false true - net5.0 + net6.0 @@ -22,10 +22,10 @@ - + - - + + all diff --git a/src/Snap.Installer/App.xaml.cs b/src/Snap.Installer/App.xaml.cs index e9e4ed2c..be009e5b 100644 --- a/src/Snap.Installer/App.xaml.cs +++ b/src/Snap.Installer/App.xaml.cs @@ -3,26 +3,25 @@ using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -namespace Snap.Installer +namespace Snap.Installer; + +internal sealed class App : Application { - internal sealed class App : Application + public override void Initialize() { - public override void Initialize() - { - AvaloniaXamlLoader.Load(this); - } - - public override void OnFrameworkInitializationCompleted() - { - base.OnFrameworkInitializationCompleted(); + AvaloniaXamlLoader.Load(this); + } - if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime classicDesktopStyleApplicationLifetime) - { - classicDesktopStyleApplicationLifetime.MainWindow = new MainWindow(); - return; - } + public override void OnFrameworkInitializationCompleted() + { + base.OnFrameworkInitializationCompleted(); - throw new NotSupportedException($"Unsupported {nameof(ApplicationLifetime)}: {ApplicationLifetime?.GetType().FullName}"); + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime classicDesktopStyleApplicationLifetime) + { + classicDesktopStyleApplicationLifetime.MainWindow = new MainWindow(); + return; } + + throw new NotSupportedException($"Unsupported {nameof(ApplicationLifetime)}: {ApplicationLifetime?.GetType().FullName}"); } -} +} \ No newline at end of file diff --git a/src/Snap.Installer/Assets/AssetsTypeRoot.cs b/src/Snap.Installer/Assets/AssetsTypeRoot.cs index 6554b36c..12e1ea4c 100644 --- a/src/Snap.Installer/Assets/AssetsTypeRoot.cs +++ b/src/Snap.Installer/Assets/AssetsTypeRoot.cs @@ -1,6 +1,5 @@ -namespace Snap.Installer.Assets +namespace Snap.Installer.Assets; + +class AssetsTypeRoot { - class AssetsTypeRoot - { - } -} +} \ No newline at end of file diff --git a/src/Snap.Installer/Controls/GifAnimationControl.cs b/src/Snap.Installer/Controls/GifAnimationControl.cs index 078bbdf6..3967967f 100644 --- a/src/Snap.Installer/Controls/GifAnimationControl.cs +++ b/src/Snap.Installer/Controls/GifAnimationControl.cs @@ -8,72 +8,71 @@ using Avalonia.Threading; using JetBrains.Annotations; -namespace Snap.Installer.Controls +namespace Snap.Installer.Controls; + +public class GifAnimationControl : Canvas { - public class GifAnimationControl : Canvas - { - readonly List _bitmaps; - DispatcherTimer _dispatcherTimer; - TimeSpan _delayTimespan; - int _bitmapindex; - Action _onFirstDrawAction; - bool _isFirstDraw; + readonly List _bitmaps; + DispatcherTimer _dispatcherTimer; + TimeSpan _delayTimespan; + int _bitmapindex; + Action _onFirstDrawAction; + bool _isFirstDraw; - public GifAnimationControl() - { - _bitmaps = new List(); - } + public GifAnimationControl() + { + _bitmaps = new List(); + } - public void AddImages([NotNull] IEnumerable bitmaps) - { - if (bitmaps == null) throw new ArgumentNullException(nameof(bitmaps)); - _bitmaps.AddRange(bitmaps); + public void AddImages([NotNull] IEnumerable bitmaps) + { + if (bitmaps == null) throw new ArgumentNullException(nameof(bitmaps)); + _bitmaps.AddRange(bitmaps); - if (_bitmaps.Count == 0) - { - throw new ArgumentException("Value cannot be an empty collection.", nameof(bitmaps)); - } - } - - public void Run(TimeSpan delayTimeSpan, [NotNull] Action onFirstDrawAction) + if (_bitmaps.Count == 0) { - _delayTimespan = delayTimeSpan; - _isFirstDraw = true; - _onFirstDrawAction = onFirstDrawAction ?? throw new ArgumentNullException(nameof(onFirstDrawAction)); - - _dispatcherTimer = new DispatcherTimer(_delayTimespan, DispatcherPriority.Render, - (sender, args) => InvalidateVisual()); - _dispatcherTimer.Start(); + throw new ArgumentException("Value cannot be an empty collection.", nameof(bitmaps)); } + } - public override void Render(DrawingContext context) - { - if (!_bitmaps.Any()) - { - goto done; - } + public void Run(TimeSpan delayTimeSpan, [NotNull] Action onFirstDrawAction) + { + _delayTimespan = delayTimeSpan; + _isFirstDraw = true; + _onFirstDrawAction = onFirstDrawAction ?? throw new ArgumentNullException(nameof(onFirstDrawAction)); - var bitmap = _bitmaps[_bitmapindex++]; + _dispatcherTimer = new DispatcherTimer(_delayTimespan, DispatcherPriority.Render, + (sender, args) => InvalidateVisual()); + _dispatcherTimer.Start(); + } - context.DrawImage(bitmap, new Rect(0, 0, bitmap.Size.Width, bitmap.Size.Height)); + public override void Render(DrawingContext context) + { + if (!_bitmaps.Any()) + { + goto done; + } - if (_bitmapindex == 1 - && _isFirstDraw) - { - _onFirstDrawAction(); - _isFirstDraw = false; - } + var bitmap = _bitmaps[_bitmapindex++]; - if (_bitmapindex < _bitmaps.Count) - { - goto done; - } + context.DrawImage(bitmap, new Rect(0, 0, bitmap.Size.Width, bitmap.Size.Height)); - _bitmapindex = 0; + if (_bitmapindex == 1 + && _isFirstDraw) + { + _onFirstDrawAction(); + _isFirstDraw = false; + } - done: - base.Render(context); + if (_bitmapindex < _bitmaps.Count) + { + goto done; } + _bitmapindex = 0; + + done: + base.Render(context); } -} + +} \ No newline at end of file diff --git a/src/Snap.Installer/Core/SnapEnvironment.cs b/src/Snap.Installer/Core/SnapEnvironment.cs index ca6d76cb..92b2d7ae 100644 --- a/src/Snap.Installer/Core/SnapEnvironment.cs +++ b/src/Snap.Installer/Core/SnapEnvironment.cs @@ -6,55 +6,54 @@ using Snap.Logging; using LogLevel = Snap.Logging.LogLevel; -namespace Snap.Installer.Core +namespace Snap.Installer.Core; + +internal interface ISnapInstallerIoEnvironment { - internal interface ISnapInstallerIoEnvironment - { - ISnapOsSpecialFolders SpecialFolders { get; } - string WorkingDirectory { get; set; } - string ThisExeWorkingDirectory { get; set; } - } + ISnapOsSpecialFolders SpecialFolders { get; } + string WorkingDirectory { get; set; } + string ThisExeWorkingDirectory { get; set; } +} - internal interface ISnapInstallerEnvironment - { - IServiceContainer Container { get; } - ISnapInstallerIoEnvironment Io { get; } - CancellationToken CancellationToken { get; } - ILog BuildLogger(); - void Shutdown(); - } +internal interface ISnapInstallerEnvironment +{ + IServiceContainer Container { get; } + ISnapInstallerIoEnvironment Io { get; } + CancellationToken CancellationToken { get; } + ILog BuildLogger(); + void Shutdown(); +} - internal class SnapInstallerEnvironment : ISnapInstallerEnvironment - { - readonly CancellationTokenSource _cancellationTokenSource; - readonly string _loggerName; - public CancellationToken CancellationToken => _cancellationTokenSource.Token; - public LogLevel LogLevel { get; set; } - public IServiceContainer Container { get; set; } - public ISnapInstallerIoEnvironment Io { get; set; } +internal class SnapInstallerEnvironment : ISnapInstallerEnvironment +{ + readonly CancellationTokenSource _cancellationTokenSource; + readonly string _loggerName; + public CancellationToken CancellationToken => _cancellationTokenSource.Token; + public LogLevel LogLevel { get; set; } + public IServiceContainer Container { get; set; } + public ISnapInstallerIoEnvironment Io { get; set; } - public SnapInstallerEnvironment(LogLevel logLevel, [NotNull] CancellationTokenSource cancellationTokenSource, [NotNull] string loggerName) - { - LogLevel = logLevel; - _cancellationTokenSource = cancellationTokenSource ?? throw new ArgumentNullException(nameof(cancellationTokenSource)); - _loggerName = loggerName ?? throw new ArgumentNullException(nameof(loggerName)); - } + public SnapInstallerEnvironment(LogLevel logLevel, [NotNull] CancellationTokenSource cancellationTokenSource, [NotNull] string loggerName) + { + LogLevel = logLevel; + _cancellationTokenSource = cancellationTokenSource ?? throw new ArgumentNullException(nameof(cancellationTokenSource)); + _loggerName = loggerName ?? throw new ArgumentNullException(nameof(loggerName)); + } - public ILog BuildLogger() - { - return LogProvider.GetLogger($"{_loggerName}.{typeof(T).Name}"); - } - - public void Shutdown() - { - _cancellationTokenSource.Cancel(); - } + public ILog BuildLogger() + { + return LogProvider.GetLogger($"{_loggerName}.{typeof(T).Name}"); } - internal class SnapInstallerIoEnvironment : ISnapInstallerIoEnvironment + public void Shutdown() { - public ISnapOsSpecialFolders SpecialFolders { get; set; } - public string WorkingDirectory { get; set; } - public string ThisExeWorkingDirectory { get; set; } + _cancellationTokenSource.Cancel(); } } + +internal class SnapInstallerIoEnvironment : ISnapInstallerIoEnvironment +{ + public ISnapOsSpecialFolders SpecialFolders { get; set; } + public string WorkingDirectory { get; set; } + public string ThisExeWorkingDirectory { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Installer/Core/SnapInstallerEmbeddedResources.cs b/src/Snap.Installer/Core/SnapInstallerEmbeddedResources.cs index c43cba04..386c519f 100644 --- a/src/Snap.Installer/Core/SnapInstallerEmbeddedResources.cs +++ b/src/Snap.Installer/Core/SnapInstallerEmbeddedResources.cs @@ -4,29 +4,28 @@ using Snap.Extensions; using Snap.Installer.Assets; -namespace Snap.Installer.Core +namespace Snap.Installer.Core; + +internal interface ISnapInstallerEmbeddedResources : IEmbedResources { - internal interface ISnapInstallerEmbeddedResources : IEmbedResources - { - List GifAnimation { get; } - } + List GifAnimation { get; } +} - internal sealed class SnapInstallerEmbeddedResources : EmbeddedResources, ISnapInstallerEmbeddedResources - { - public List GifAnimation { get; } +internal sealed class SnapInstallerEmbeddedResources : EmbeddedResources, ISnapInstallerEmbeddedResources +{ + public List GifAnimation { get; } - public SnapInstallerEmbeddedResources() - { - AddFromTypeRoot(typeof(AssetsTypeRoot), x => x.StartsWith("Snap.Installer.Assets")); + public SnapInstallerEmbeddedResources() + { + AddFromTypeRoot(typeof(AssetsTypeRoot), x => x.StartsWith("Snap.Installer.Assets")); - GifAnimation = new List(); + GifAnimation = new List(); - const string animatedGifNs = "AnimatedGif."; - foreach (var image in Resources.Where(x => x.Filename.StartsWith(animatedGifNs)).OrderBy(x => x.Filename[animatedGifNs.Length..].ToIntSafe())) - { - GifAnimation.Add(image.Stream.ToArray()); - } + const string animatedGifNs = "AnimatedGif."; + foreach (var image in Resources.Where(x => x.Filename.StartsWith(animatedGifNs)).OrderBy(x => x.Filename[animatedGifNs.Length..].ToIntSafe())) + { + GifAnimation.Add(image.Stream.ToArray()); } - } -} + +} \ No newline at end of file diff --git a/src/Snap.Installer/MainWindow.xaml.cs b/src/Snap.Installer/MainWindow.xaml.cs index 1a769dc5..6e82f351 100644 --- a/src/Snap.Installer/MainWindow.xaml.cs +++ b/src/Snap.Installer/MainWindow.xaml.cs @@ -12,57 +12,56 @@ using Snap.Installer.Windows; using Snap.Logging; -namespace Snap.Installer +namespace Snap.Installer; + +internal sealed class MainWindow : CustomChromeWindow { - internal sealed class MainWindow : CustomChromeWindow - { - public static ISnapInstallerEnvironment Environment { get; set; } - public static AvaloniaMainWindowViewModel ViewModel { get; set; } + public static ISnapInstallerEnvironment Environment { get; set; } + public static AvaloniaMainWindowViewModel ViewModel { get; set; } - public MainWindow() - { - Debug.Assert(Environment != null, nameof(Environment) + " != null"); + public MainWindow() + { + Debug.Assert(Environment != null, nameof(Environment) + " != null"); - var logger = Environment.BuildLogger(); - - InitializeComponent(); + var logger = Environment.BuildLogger(); - ViewModel.CancelCommand = ReactiveCommand.CreateFromTask(async () => - { - await ViewModel.SetStatusTextAsync("Cancelling installation because user pressed CTRL + C"); - await Task.Delay(TimeSpan.FromSeconds(3)); - Close(); - }); + InitializeComponent(); - DataContext = ViewModel; + ViewModel.CancelCommand = ReactiveCommand.CreateFromTask(async () => + { + await ViewModel.SetStatusTextAsync("Cancelling installation because user pressed CTRL + C"); + await Task.Delay(TimeSpan.FromSeconds(3)); + Close(); + }); - Environment.CancellationToken.Register(() => - { - logger.Info("Cancellation detected, closing main window."); - Dispatcher.UIThread.InvokeAsync(Close); - }); - } + DataContext = ViewModel; - protected override void OnOpened(EventArgs eventArgs) + Environment.CancellationToken.Register(() => { - ViewModel.GifAnimation = this.FindControl("GifAnimation"); - ViewModel.OnInitialized(); + logger.Info("Cancellation detected, closing main window."); + Dispatcher.UIThread.InvokeAsync(Close); + }); + } - base.OnOpened(eventArgs); - } + protected override void OnOpened(EventArgs eventArgs) + { + ViewModel.GifAnimation = this.FindControl("GifAnimation"); + ViewModel.OnInitialized(); - protected override void OnClosing(CancelEventArgs e) - { - if (!Environment.CancellationToken.IsCancellationRequested) - { - Environment.Shutdown(); - } - base.OnClosing(e); - } + base.OnOpened(eventArgs); + } - void InitializeComponent() + protected override void OnClosing(CancelEventArgs e) + { + if (!Environment.CancellationToken.IsCancellationRequested) { - AvaloniaXamlLoader.Load(this); + Environment.Shutdown(); } + base.OnClosing(e); + } + + void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); } -} +} \ No newline at end of file diff --git a/src/Snap.Installer/Program.Install.cs b/src/Snap.Installer/Program.Install.cs index 46b62f6c..662ca893 100644 --- a/src/Snap.Installer/Program.Install.cs +++ b/src/Snap.Installer/Program.Install.cs @@ -18,434 +18,433 @@ using Snap.Logging; using Snap.NuGet; -namespace Snap.Installer +namespace Snap.Installer; + +internal partial class Program { - internal partial class Program + static async Task<(int exitCode, SnapInstallerType installerType)> InstallAsync([NotNull] ISnapInstallerEnvironment environment, + [NotNull] ISnapInstallerEmbeddedResources snapInstallerEmbeddedResources, [NotNull] ISnapInstaller snapInstaller, + [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ISnapPack snapPack, [NotNull] ISnapOs snapOs, + [NotNull] CoreRunLib coreRunLib, [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, + [NotNull] INugetService nugetService, [NotNull] ISnapPackageManager snapPackageManager, + [NotNull] ISnapExtractor snapExtractor, [NotNull] ILog diskLogger, + bool headless) { - static async Task<(int exitCode, SnapInstallerType installerType)> InstallAsync([NotNull] ISnapInstallerEnvironment environment, - [NotNull] ISnapInstallerEmbeddedResources snapInstallerEmbeddedResources, [NotNull] ISnapInstaller snapInstaller, - [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ISnapPack snapPack, [NotNull] ISnapOs snapOs, - [NotNull] CoreRunLib coreRunLib, [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, - [NotNull] INugetService nugetService, [NotNull] ISnapPackageManager snapPackageManager, - [NotNull] ISnapExtractor snapExtractor, [NotNull] ILog diskLogger, - bool headless) + if (environment == null) throw new ArgumentNullException(nameof(environment)); + if (snapInstallerEmbeddedResources == null) throw new ArgumentNullException(nameof(snapInstallerEmbeddedResources)); + if (snapInstaller == null) throw new ArgumentNullException(nameof(snapInstaller)); + if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); + if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); + if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); + if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); + if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); + if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); + if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); + if (diskLogger == null) throw new ArgumentNullException(nameof(diskLogger)); + + // NB! All filesystem operations has to be readonly until check that verifies + // current user is not elevated to root has run. + + var cancellationToken = environment.CancellationToken; + var installerProgressSource = new SnapProgressSource(); + var onFirstAnimationRenderedEvent = new SemaphoreSlim(1, 1); + var exitCode = 1; + var installerType = SnapInstallerType.None; + + // ReSharper disable once ImplicitlyCapturedClosure + + async Task InstallInBackgroundAsync(IMainWindowViewModel mainWindowViewModel) { - if (environment == null) throw new ArgumentNullException(nameof(environment)); - if (snapInstallerEmbeddedResources == null) throw new ArgumentNullException(nameof(snapInstallerEmbeddedResources)); - if (snapInstaller == null) throw new ArgumentNullException(nameof(snapInstaller)); - if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); - if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); - if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); - if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); - if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); - if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); - if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); - if (diskLogger == null) throw new ArgumentNullException(nameof(diskLogger)); - - // NB! All filesystem operations has to be readonly until check that verifies - // current user is not elevated to root has run. - - var cancellationToken = environment.CancellationToken; - var installerProgressSource = new SnapProgressSource(); - var onFirstAnimationRenderedEvent = new SemaphoreSlim(1, 1); - var exitCode = 1; - var installerType = SnapInstallerType.None; - - // ReSharper disable once ImplicitlyCapturedClosure - - async Task InstallInBackgroundAsync(IMainWindowViewModel mainWindowViewModel) + if (mainWindowViewModel == null) throw new ArgumentNullException(nameof(mainWindowViewModel)); + + if (mainWindowViewModel.Headless) { - if (mainWindowViewModel == null) throw new ArgumentNullException(nameof(mainWindowViewModel)); + diskLogger.Info("Headless install."); + onFirstAnimationRenderedEvent.Dispose(); + } + else + { + diskLogger.Info("Waiting for main window to become visible."); + await onFirstAnimationRenderedEvent.WaitAsync(cancellationToken); + onFirstAnimationRenderedEvent.Dispose(); + diskLogger.Info("Main window should now be visible."); + } - if (mainWindowViewModel.Headless) - { - diskLogger.Info("Headless install."); - onFirstAnimationRenderedEvent.Dispose(); - } - else + var mainWindowLogger = new LogForwarder(LogLevel.Info, diskLogger, (level, func, exception, parameters) => + { + var message = func?.Invoke(); + if (message == null) { - diskLogger.Info("Waiting for main window to become visible."); - await onFirstAnimationRenderedEvent.WaitAsync(cancellationToken); - onFirstAnimationRenderedEvent.Dispose(); - diskLogger.Info("Main window should now be visible."); + return; } - var mainWindowLogger = new LogForwarder(LogLevel.Info, diskLogger, (level, func, exception, parameters) => - { - var message = func?.Invoke(); - if (message == null) - { - return; - } - - SetStatusText(mainWindowViewModel, message); - }); + SetStatusText(mainWindowViewModel, message); + }); - #if !SNAP_INSTALLER_ALLOW_ELEVATED_CONTEXT - if (coreRunLib.IsElevated()) - { - var rootUserText = snapOs.OsPlatform == OSPlatform.Windows ? "Administrator" : "root"; - mainWindowLogger.Error($"Error! Installer cannot run in an elevated user context: {rootUserText}"); - goto done; - } - #endif +#if !SNAP_INSTALLER_ALLOW_ELEVATED_CONTEXT + if (coreRunLib.IsElevated()) + { + var rootUserText = snapOs.OsPlatform == OSPlatform.Windows ? "Administrator" : "root"; + mainWindowLogger.Error($"Error! Installer cannot run in an elevated user context: {rootUserText}"); + goto done; + } +#endif - diskLogger.Debug($"{nameof(environment.Io.WorkingDirectory)}: {environment.Io.WorkingDirectory}"); - diskLogger.Debug($"{nameof(environment.Io.ThisExeWorkingDirectory)}: {environment.Io.ThisExeWorkingDirectory}"); + diskLogger.Debug($"{nameof(environment.Io.WorkingDirectory)}: {environment.Io.WorkingDirectory}"); + diskLogger.Debug($"{nameof(environment.Io.ThisExeWorkingDirectory)}: {environment.Io.ThisExeWorkingDirectory}"); - var snapAppDllAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, SnapConstants.SnapAppDllFilename); - diskLogger.Debug($"{nameof(snapAppDllAbsolutePath)}: {snapAppDllAbsolutePath}."); + var snapAppDllAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, SnapConstants.SnapAppDllFilename); + diskLogger.Debug($"{nameof(snapAppDllAbsolutePath)}: {snapAppDllAbsolutePath}."); - if (!snapFilesystem.FileExists(snapAppDllAbsolutePath)) - { - mainWindowLogger.Info($"Unable to find: {snapFilesystem.PathGetFileName(snapAppDllAbsolutePath)}"); - goto done; - } + if (!snapFilesystem.FileExists(snapAppDllAbsolutePath)) + { + mainWindowLogger.Info($"Unable to find: {snapFilesystem.PathGetFileName(snapAppDllAbsolutePath)}"); + goto done; + } - SnapApp snapApp; - SnapChannel snapChannel; - try - { - snapApp = environment.Io.ThisExeWorkingDirectory.GetSnapAppFromDirectory(snapFilesystem, snapAppReader); - snapChannel = snapApp.GetCurrentChannelOrThrow(); - } - catch (Exception ex) - { - mainWindowLogger.ErrorException($"Error reading {SnapConstants.SnapAppDllFilename}", ex); - goto done; - } + SnapApp snapApp; + SnapChannel snapChannel; + try + { + snapApp = environment.Io.ThisExeWorkingDirectory.GetSnapAppFromDirectory(snapFilesystem, snapAppReader); + snapChannel = snapApp.GetCurrentChannelOrThrow(); + } + catch (Exception ex) + { + mainWindowLogger.ErrorException($"Error reading {SnapConstants.SnapAppDllFilename}", ex); + goto done; + } - var nupkgAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, SnapConstants.SetupNupkgFilename); - var nupkgReleasesAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, snapApp.BuildNugetReleasesFilename()); + var nupkgAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, SnapConstants.SetupNupkgFilename); + var nupkgReleasesAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, snapApp.BuildNugetReleasesFilename()); - await using (var webInstallerDir = new DisposableDirectory(snapOs.SpecialFolders.NugetCacheDirectory, snapFilesystem)) - { - ISnapAppChannelReleases snapAppChannelReleases; - SnapRelease snapReleaseToInstall; + await using (var webInstallerDir = new DisposableDirectory(snapOs.SpecialFolders.NugetCacheDirectory, snapFilesystem)) + { + ISnapAppChannelReleases snapAppChannelReleases; + SnapRelease snapReleaseToInstall; - // Offline installer - if (snapFilesystem.FileExists(nupkgAbsolutePath)) + // Offline installer + if (snapFilesystem.FileExists(nupkgAbsolutePath)) + { + mainWindowLogger.Info("Offline installer is loading releases nupkg"); + + try + { + var releasesFileStream = snapFilesystem.FileRead(nupkgReleasesAbsolutePath); + using var packageArchiveReader = new PackageArchiveReader(releasesFileStream); + var snapAppsReleases = await snapExtractor.GetSnapAppsReleasesAsync(packageArchiveReader, snapAppReader, cancellationToken); + snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapChannel); + var isGenesis = !snapAppChannelReleases.HasDeltaReleases(); + snapReleaseToInstall = snapAppChannelReleases.GetMostRecentRelease().AsFullRelease(isGenesis); + } + catch (Exception e) { - mainWindowLogger.Info("Offline installer is loading releases nupkg"); + mainWindowLogger.ErrorException($"Error reading {nupkgAbsolutePath}", e); + goto done; + } + + installerType = SnapInstallerType.Offline; + } + // Web installer + else if (snapFilesystem.FileExists(snapAppDllAbsolutePath)) + { + mainWindowLogger.Info("Web installer is downloading releases nupkg"); - try + try + { + var (snapAppsReleases, packageSource, releasesMemoryStream) = + await snapPackageManager.GetSnapsReleasesAsync(snapApp, mainWindowLogger, cancellationToken); + if (releasesMemoryStream != null) { - var releasesFileStream = snapFilesystem.FileRead(nupkgReleasesAbsolutePath); - using var packageArchiveReader = new PackageArchiveReader(releasesFileStream); - var snapAppsReleases = await snapExtractor.GetSnapAppsReleasesAsync(packageArchiveReader, snapAppReader, cancellationToken); - snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapChannel); - var isGenesis = !snapAppChannelReleases.HasDeltaReleases(); - snapReleaseToInstall = snapAppChannelReleases.GetMostRecentRelease().AsFullRelease(isGenesis); + await releasesMemoryStream.DisposeAsync(); } - catch (Exception e) + if (snapAppsReleases == null) { - mainWindowLogger.ErrorException($"Error reading {nupkgAbsolutePath}", e); + mainWindowLogger.Error("Failed to download releases nupkg. Try rerunning the installer."); goto done; } - installerType = SnapInstallerType.Offline; - } - // Web installer - else if (snapFilesystem.FileExists(snapAppDllAbsolutePath)) - { - mainWindowLogger.Info("Web installer is downloading releases nupkg"); + var snapAppsReleasesBytes = snapAppWriter.ToSnapAppsReleases(snapAppsReleases); + await snapFilesystem.FileWriteAsync(snapAppsReleasesBytes, nupkgReleasesAbsolutePath, cancellationToken); - try + snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapApp.GetCurrentChannelOrThrow()); + if (!snapAppChannelReleases.Any()) { - var (snapAppsReleases, packageSource, releasesMemoryStream) = - await snapPackageManager.GetSnapsReleasesAsync(snapApp, mainWindowLogger, cancellationToken); - if (releasesMemoryStream != null) - { - await releasesMemoryStream.DisposeAsync(); - } - if (snapAppsReleases == null) - { - mainWindowLogger.Error("Failed to download releases nupkg. Try rerunning the installer."); - goto done; - } + mainWindowLogger.Error($"Unable to find any releases in channel: {snapAppChannelReleases.Channel.Name}."); + goto done; + } - var snapAppsReleasesBytes = snapAppWriter.ToSnapAppsReleases(snapAppsReleases); - await snapFilesystem.FileWriteAsync(snapAppsReleasesBytes, nupkgReleasesAbsolutePath, cancellationToken); + var isGenesis = snapAppChannelReleases.Count() == 1; + snapReleaseToInstall = snapAppChannelReleases.GetMostRecentRelease().AsFullRelease(isGenesis); + snapApp.Version = snapReleaseToInstall.Version; - snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapApp.GetCurrentChannelOrThrow()); - if (!snapAppChannelReleases.Any()) - { - mainWindowLogger.Error($"Unable to find any releases in channel: {snapAppChannelReleases.Channel.Name}."); - goto done; - } + mainWindowLogger.Info($"Current version: {snapApp.Version}. Channel: {snapAppChannelReleases.Channel.Name}."); - var isGenesis = snapAppChannelReleases.Count() == 1; - snapReleaseToInstall = snapAppChannelReleases.GetMostRecentRelease().AsFullRelease(isGenesis); - snapApp.Version = snapReleaseToInstall.Version; + if (!headless) + { + // ReSharper disable once MethodSupportsCancellation + await Task.Delay(TimeSpan.FromSeconds(3)); + } - mainWindowLogger.Info($"Current version: {snapApp.Version}. Channel: {snapAppChannelReleases.Channel.Name}."); + mainWindowLogger.Info("Downloading required assets"); - if (!headless) - { - // ReSharper disable once MethodSupportsCancellation - await Task.Delay(TimeSpan.FromSeconds(3)); - } - - mainWindowLogger.Info("Downloading required assets"); + void UpdateProgress(string type, int totalPercentage, + long releasesChecksummed = 0, long releasesToChecksum = 0, + long releasesDownloaded = 0, long releasesToDownload = 0, + long filesRestored = 0, long filesToRestore = 0, + long totalBytesDownloaded = 0, long totalBytesToDownload = 0) + { - void UpdateProgress(string type, int totalPercentage, - long releasesChecksummed = 0, long releasesToChecksum = 0, - long releasesDownloaded = 0, long releasesToDownload = 0, - long filesRestored = 0, long filesToRestore = 0, - long totalBytesDownloaded = 0, long totalBytesToDownload = 0) + void SetProgressText(long current, long total, string defaultText, string pluralText) { + var outputText = total > 1 ? pluralText : defaultText; - void SetProgressText(long current, long total, string defaultText, string pluralText) + switch (type) { - var outputText = total > 1 ? pluralText : defaultText; - - switch (type) - { - case "Download": - if (total > 1) - { - SetStatusText(mainWindowViewModel, - $"{outputText} ({totalPercentage}%): {current} of {total}. " + - $"Downloaded so far: {totalBytesDownloaded.BytesAsHumanReadable()}. " + - $"Total: {totalBytesToDownload.BytesAsHumanReadable()}"); - - goto incrementProgress; - } - + case "Download": + if (total > 1) + { SetStatusText(mainWindowViewModel, - $"{outputText} ({totalPercentage}%): " + + $"{outputText} ({totalPercentage}%): {current} of {total}. " + $"Downloaded so far: {totalBytesDownloaded.BytesAsHumanReadable()}. " + $"Total: {totalBytesToDownload.BytesAsHumanReadable()}"); goto incrementProgress; - default: - if (total > 1) - { - SetStatusText(mainWindowViewModel, $"{outputText} ({totalPercentage}%): {current} of {total}."); - goto incrementProgress; - } - - SetStatusText(mainWindowViewModel, $"{outputText}: {totalPercentage}%"); - goto incrementProgress; - } + } - incrementProgress: - installerProgressSource.Raise(totalPercentage); - } + SetStatusText(mainWindowViewModel, + $"{outputText} ({totalPercentage}%): " + + $"Downloaded so far: {totalBytesDownloaded.BytesAsHumanReadable()}. " + + $"Total: {totalBytesToDownload.BytesAsHumanReadable()}"); - switch (type) - { - case "Checksum": - SetProgressText(releasesChecksummed, releasesToChecksum, "Validating payload", "Validating payloads"); - break; - case "Download": - SetProgressText(releasesDownloaded, releasesToDownload, "Downloading payload", "Downloading payloads"); - break; - case "Restore": - SetProgressText(filesRestored, filesToRestore, "Restoring file", "Restoring files"); - break; + goto incrementProgress; default: - diskLogger.Warn($"Unknown progress type: {type}"); - break; + if (total > 1) + { + SetStatusText(mainWindowViewModel, $"{outputText} ({totalPercentage}%): {current} of {total}."); + goto incrementProgress; + } + + SetStatusText(mainWindowViewModel, $"{outputText}: {totalPercentage}%"); + goto incrementProgress; } + + incrementProgress: + installerProgressSource.Raise(totalPercentage); } - var snapPackageManagerProgressSource = new SnapPackageManagerProgressSource + switch (type) { - ChecksumProgress = x => UpdateProgress("Checksum", - x.progressPercentage, - x.releasesChecksummed, - x.releasesToChecksum), - DownloadProgress = x => UpdateProgress("Download", - x.progressPercentage, - releasesDownloaded: x.releasesDownloaded, - releasesToDownload: x.releasesToDownload, - totalBytesDownloaded: x.totalBytesDownloaded, - totalBytesToDownload: x.totalBytesToDownload), - RestoreProgress = x => UpdateProgress("Restore", - x.progressPercentage, - filesRestored: x.filesRestored, - filesToRestore: x.filesToRestore) - }; - - var restoreSummary = await snapPackageManager.RestoreAsync(webInstallerDir.WorkingDirectory, snapAppChannelReleases, - packageSource, SnapPackageManagerRestoreType.Default, snapPackageManagerProgressSource, diskLogger, cancellationToken); - if (!restoreSummary.Success) - { - mainWindowLogger.Info("Unknown error while restoring assets."); - goto done; + case "Checksum": + SetProgressText(releasesChecksummed, releasesToChecksum, "Validating payload", "Validating payloads"); + break; + case "Download": + SetProgressText(releasesDownloaded, releasesToDownload, "Downloading payload", "Downloading payloads"); + break; + case "Restore": + SetProgressText(filesRestored, filesToRestore, "Restoring file", "Restoring files"); + break; + default: + diskLogger.Warn($"Unknown progress type: {type}"); + break; } + } + + var snapPackageManagerProgressSource = new SnapPackageManagerProgressSource + { + ChecksumProgress = x => UpdateProgress("Checksum", + x.progressPercentage, + x.releasesChecksummed, + x.releasesToChecksum), + DownloadProgress = x => UpdateProgress("Download", + x.progressPercentage, + releasesDownloaded: x.releasesDownloaded, + releasesToDownload: x.releasesToDownload, + totalBytesDownloaded: x.totalBytesDownloaded, + totalBytesToDownload: x.totalBytesToDownload), + RestoreProgress = x => UpdateProgress("Restore", + x.progressPercentage, + filesRestored: x.filesRestored, + filesToRestore: x.filesToRestore) + }; + + var restoreSummary = await snapPackageManager.RestoreAsync(webInstallerDir.WorkingDirectory, snapAppChannelReleases, + packageSource, SnapPackageManagerRestoreType.Default, snapPackageManagerProgressSource, diskLogger, cancellationToken); + if (!restoreSummary.Success) + { + mainWindowLogger.Info("Unknown error while restoring assets."); + goto done; + } - mainWindowLogger.Info("Preparing to install payload"); + mainWindowLogger.Info("Preparing to install payload"); - var setupNupkgAbsolutePath = snapOs.Filesystem.PathCombine(webInstallerDir.WorkingDirectory, snapReleaseToInstall.Filename); - if (!snapFilesystem.FileExists(setupNupkgAbsolutePath)) - { - mainWindowLogger.Error($"Payload does not exist on disk: {setupNupkgAbsolutePath}."); - goto done; - } + var setupNupkgAbsolutePath = snapOs.Filesystem.PathCombine(webInstallerDir.WorkingDirectory, snapReleaseToInstall.Filename); + if (!snapFilesystem.FileExists(setupNupkgAbsolutePath)) + { + mainWindowLogger.Error($"Payload does not exist on disk: {setupNupkgAbsolutePath}."); + goto done; + } - nupkgAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, "Setup.nupkg"); + nupkgAbsolutePath = snapFilesystem.PathCombine(environment.Io.ThisExeWorkingDirectory, "Setup.nupkg"); - mainWindowLogger.Info("Copying payload to installer directory"); + mainWindowLogger.Info("Copying payload to installer directory"); - snapOs.Filesystem.FileDeleteIfExists(nupkgAbsolutePath); - await snapOs.Filesystem.FileCopyAsync(setupNupkgAbsolutePath, nupkgAbsolutePath, cancellationToken); + snapOs.Filesystem.FileDeleteIfExists(nupkgAbsolutePath); + await snapOs.Filesystem.FileCopyAsync(setupNupkgAbsolutePath, nupkgAbsolutePath, cancellationToken); - mainWindowLogger.Info("Successfully copied payload"); + mainWindowLogger.Info("Successfully copied payload"); - installerProgressSource.Reset(); + installerProgressSource.Reset(); - installerType = SnapInstallerType.Web; - } - catch (Exception e) - { - mainWindowLogger.ErrorException("Unknown error while restoring assets", e); - goto done; - } + installerType = SnapInstallerType.Web; } - else + catch (Exception e) { - mainWindowLogger.Error("Unknown error. Could not find offline or web installer payload."); + mainWindowLogger.ErrorException("Unknown error while restoring assets", e); goto done; } + } + else + { + mainWindowLogger.Error("Unknown error. Could not find offline or web installer payload."); + goto done; + } - diskLogger.Trace($"Installer type: {installerType}"); - diskLogger.Trace($"{nameof(nupkgAbsolutePath)}: {nupkgAbsolutePath}"); - diskLogger.Trace($"{nameof(nupkgReleasesAbsolutePath)}: {nupkgReleasesAbsolutePath}"); + diskLogger.Trace($"Installer type: {installerType}"); + diskLogger.Trace($"{nameof(nupkgAbsolutePath)}: {nupkgAbsolutePath}"); + diskLogger.Trace($"{nameof(nupkgReleasesAbsolutePath)}: {nupkgReleasesAbsolutePath}"); - if (!snapFilesystem.FileExists(nupkgAbsolutePath)) - { - mainWindowLogger.Error($"Unable to find installer payload: {snapFilesystem.PathGetFileName(nupkgAbsolutePath)}"); - goto done; - } + if (!snapFilesystem.FileExists(nupkgAbsolutePath)) + { + mainWindowLogger.Error($"Unable to find installer payload: {snapFilesystem.PathGetFileName(nupkgAbsolutePath)}"); + goto done; + } - mainWindowLogger.Info("Attempting to read payload release details"); + mainWindowLogger.Info("Attempting to read payload release details"); - if (!snapFilesystem.FileExists(nupkgReleasesAbsolutePath)) - { - mainWindowLogger.Error($"Unable to find releases nupkg: {snapFilesystem.PathGetFileName(nupkgReleasesAbsolutePath)}"); - goto done; - } + if (!snapFilesystem.FileExists(nupkgReleasesAbsolutePath)) + { + mainWindowLogger.Error($"Unable to find releases nupkg: {snapFilesystem.PathGetFileName(nupkgReleasesAbsolutePath)}"); + goto done; + } - var baseDirectory = snapFilesystem.PathCombine(snapOs.SpecialFolders.LocalApplicationData, snapApp.InstallDirectoryName ?? snapApp.Id); + var baseDirectory = snapFilesystem.PathCombine(snapOs.SpecialFolders.LocalApplicationData, snapApp.InstallDirectoryName ?? snapApp.Id); - mainWindowLogger.Info($"Installing {snapApp.Id}. Channel name: {snapChannel.Name}"); + mainWindowLogger.Info($"Installing {snapApp.Id}. Channel name: {snapChannel.Name}"); - try - { - var copyNupkgToPackagesDirectory = installerType == SnapInstallerType.Offline; - var snapAppInstalled = await snapInstaller.InstallAsync(nupkgAbsolutePath, baseDirectory, - snapReleaseToInstall, snapChannel, installerProgressSource, mainWindowLogger, copyNupkgToPackagesDirectory, cancellationToken); + try + { + var copyNupkgToPackagesDirectory = installerType == SnapInstallerType.Offline; + var snapAppInstalled = await snapInstaller.InstallAsync(nupkgAbsolutePath, baseDirectory, + snapReleaseToInstall, snapChannel, installerProgressSource, mainWindowLogger, copyNupkgToPackagesDirectory, cancellationToken); - if (snapAppInstalled == null) - { - goto done; - } + if (snapAppInstalled == null) + { + goto done; + } - if (installerType == SnapInstallerType.Web - && snapAppChannelReleases.HasDeltaReleases()) + if (installerType == SnapInstallerType.Web + && snapAppChannelReleases.HasDeltaReleases()) + { + var snapReleasesToCopy = new List { - var snapReleasesToCopy = new List - { - snapAppChannelReleases.GetGenesisRelease() - }; + snapAppChannelReleases.GetGenesisRelease() + }; - snapReleasesToCopy.AddRange(snapAppChannelReleases.GetDeltaReleases()); + snapReleasesToCopy.AddRange(snapAppChannelReleases.GetDeltaReleases()); - if (snapReleasesToCopy.Any()) - { - var totalSnapReleasesToCopyCount = snapReleasesToCopy.Count; + if (snapReleasesToCopy.Any()) + { + var totalSnapReleasesToCopyCount = snapReleasesToCopy.Count; - mainWindowLogger.Info($"Copying 1 of {totalSnapReleasesToCopyCount} payloads to application directory."); + mainWindowLogger.Info($"Copying 1 of {totalSnapReleasesToCopyCount} payloads to application directory."); - var packagesDirectory = snapFilesystem.PathCombine(baseDirectory, "packages"); - var snapReleasesCopied = 1; - foreach (var snapRelease in snapReleasesToCopy) - { - var nupkgPackageWebInstallerDirectoryAbsolutePath = snapFilesystem.PathCombine( - webInstallerDir.WorkingDirectory, snapRelease.Filename); + var packagesDirectory = snapFilesystem.PathCombine(baseDirectory, "packages"); + var snapReleasesCopied = 1; + foreach (var snapRelease in snapReleasesToCopy) + { + var nupkgPackageWebInstallerDirectoryAbsolutePath = snapFilesystem.PathCombine( + webInstallerDir.WorkingDirectory, snapRelease.Filename); - var nupkgPackagePackagesDirectoryAbsolutePath = snapFilesystem.PathCombine( - packagesDirectory, snapRelease.Filename); + var nupkgPackagePackagesDirectoryAbsolutePath = snapFilesystem.PathCombine( + packagesDirectory, snapRelease.Filename); - await snapFilesystem.FileCopyAsync( + await snapFilesystem.FileCopyAsync( nupkgPackageWebInstallerDirectoryAbsolutePath, nupkgPackagePackagesDirectoryAbsolutePath, cancellationToken); - mainWindowLogger.Info($"Copied {snapReleasesCopied} of {totalSnapReleasesToCopyCount} payloads to application directory."); - ++snapReleasesCopied; - } - - mainWindowLogger.Info("Successfully copied all payloads."); + mainWindowLogger.Info($"Copied {snapReleasesCopied} of {totalSnapReleasesToCopyCount} payloads to application directory."); + ++snapReleasesCopied; } - - snapFilesystem.FileDeleteIfExists(nupkgAbsolutePath); + + mainWindowLogger.Info("Successfully copied all payloads."); } - mainWindowLogger.Info($"Successfully installed {snapApp.Id}."); - - exitCode = 0; - } - catch (Exception e) - { - mainWindowLogger.ErrorException("Unknown error during install", e); + snapFilesystem.FileDeleteIfExists(nupkgAbsolutePath); } - } - done: - if (exitCode != 0) - { - await mainWindowViewModel.SetErrorAsync(); + mainWindowLogger.Info($"Successfully installed {snapApp.Id}."); + + exitCode = 0; } - if (!headless) + catch (Exception e) { - // Give user enough time to read final log message. - Thread.Sleep(exitCode == 0 ? 3000 : 10000); + mainWindowLogger.ErrorException("Unknown error during install", e); } - environment.Shutdown(); } - if (headless) + done: + if (exitCode != 0) { - try - { - await InstallInBackgroundAsync(new ConsoleMainViewModel()); - } - catch (OperationCanceledException) - { - // ignore - } - return (exitCode, installerType); + await mainWindowViewModel.SetErrorAsync(); + } + if (!headless) + { + // Give user enough time to read final log message. + Thread.Sleep(exitCode == 0 ? 3000 : 10000); } + environment.Shutdown(); + } - var avaloniaApp = BuildAvaloniaApp() - .AfterSetup(builder => - { - MainWindow.Environment = environment; - MainWindow.ViewModel = new AvaloniaMainWindowViewModel(snapInstallerEmbeddedResources, - installerProgressSource, () => - { - onFirstAnimationRenderedEvent.Dispose(); - }); + if (headless) + { + try + { + await InstallInBackgroundAsync(new ConsoleMainViewModel()); + } + catch (OperationCanceledException) + { + // ignore + } + return (exitCode, installerType); + } - Task.Factory.StartNew(() => InstallInBackgroundAsync(MainWindow.ViewModel), TaskCreationOptions.LongRunning); - }); + var avaloniaApp = BuildAvaloniaApp() + .AfterSetup(builder => + { + MainWindow.Environment = environment; + MainWindow.ViewModel = new AvaloniaMainWindowViewModel(snapInstallerEmbeddedResources, + installerProgressSource, () => + { + onFirstAnimationRenderedEvent.Dispose(); + }); - avaloniaApp.StartWithClassicDesktopLifetime(null); + Task.Factory.StartNew(() => InstallInBackgroundAsync(MainWindow.ViewModel), TaskCreationOptions.LongRunning); + }); - return (exitCode, installerType); - } + avaloniaApp.StartWithClassicDesktopLifetime(null); - static void SetStatusText([NotNull] IMainWindowViewModel mainWindowViewModel, [NotNull] string statusText) - { - if (mainWindowViewModel == null) throw new ArgumentNullException(nameof(mainWindowViewModel)); - if (statusText == null) throw new ArgumentNullException(nameof(statusText)); - // Do not invoke logging inside this method because the logger is forwarded. - // Circular invocation -> Stack overflow! - TplHelper.RunSync(() => mainWindowViewModel.SetStatusTextAsync(statusText)); - } + return (exitCode, installerType); + } + + static void SetStatusText([NotNull] IMainWindowViewModel mainWindowViewModel, [NotNull] string statusText) + { + if (mainWindowViewModel == null) throw new ArgumentNullException(nameof(mainWindowViewModel)); + if (statusText == null) throw new ArgumentNullException(nameof(statusText)); + // Do not invoke logging inside this method because the logger is forwarded. + // Circular invocation -> Stack overflow! + TplHelper.RunSync(() => mainWindowViewModel.SetStatusTextAsync(statusText)); } -} +} \ No newline at end of file diff --git a/src/Snap.Installer/Program.cs b/src/Snap.Installer/Program.cs index bfe39d77..62b57762 100644 --- a/src/Snap.Installer/Program.cs +++ b/src/Snap.Installer/Program.cs @@ -266,7 +266,8 @@ static void ConfigureNlog([NotNull] ISnapOs snapOs) var fileTarget = new FileTarget("logfile") { FileName = snapOs.Filesystem.PathCombine(snapOs.Filesystem.DirectoryWorkingDirectory(), $"{processName}.log"), - Layout = layout + Layout = layout, + KeepFileOpen = true }; Console.WriteLine($"Logfile: {fileTarget.FileName}"); diff --git a/src/Snap.Installer/Snap.Installer.Deps.targets b/src/Snap.Installer/Snap.Installer.Deps.targets index 30f81a4f..6f10f3f5 100644 --- a/src/Snap.Installer/Snap.Installer.Deps.targets +++ b/src/Snap.Installer/Snap.Installer.Deps.targets @@ -3,6 +3,6 @@ - + diff --git a/src/Snap.Installer/Snap.Installer.csproj b/src/Snap.Installer/Snap.Installer.csproj index 6939c508..8808bbe1 100644 --- a/src/Snap.Installer/Snap.Installer.csproj +++ b/src/Snap.Installer/Snap.Installer.csproj @@ -1,7 +1,7 @@  - 0.10.0 + 0.10.10 @@ -11,11 +11,18 @@ Exe Snap.Installer Snap.Installer - net5.0 + net6.0 false false + + + + + true + + @@ -28,9 +35,4 @@ - - - - - diff --git a/src/Snap.Installer/ViewModels/AvaloniaMainWindowViewModel.cs b/src/Snap.Installer/ViewModels/AvaloniaMainWindowViewModel.cs index 59b311b9..4c8deb98 100644 --- a/src/Snap.Installer/ViewModels/AvaloniaMainWindowViewModel.cs +++ b/src/Snap.Installer/ViewModels/AvaloniaMainWindowViewModel.cs @@ -12,86 +12,85 @@ using Snap.Installer.Controls; using Snap.Installer.Core; -namespace Snap.Installer.ViewModels +namespace Snap.Installer.ViewModels; + +internal sealed class AvaloniaMainWindowViewModel : ViewModelBase, IMainWindowViewModel { - internal sealed class AvaloniaMainWindowViewModel : ViewModelBase, IMainWindowViewModel - { - [NotNull] readonly ISnapInstallerEmbeddedResources _snapInstallerEmbeddedResources; - [NotNull] readonly Action _onFirstFrameAnimatedCallback; - GifAnimationControl _gifGifAnimationControl; - string _statusText; - double _progress; - Brush _statusTextBrush; + [NotNull] readonly ISnapInstallerEmbeddedResources _snapInstallerEmbeddedResources; + [NotNull] readonly Action _onFirstFrameAnimatedCallback; + GifAnimationControl _gifGifAnimationControl; + string _statusText; + double _progress; + Brush _statusTextBrush; - public bool Headless => false; - - [UsedImplicitly] - public double Progress - { - get => _progress; - set => this.RaiseAndSetIfChanged(ref _progress, value); - } + public bool Headless => false; - [UsedImplicitly] - public string StatusText - { - get => _statusText; - set => this.RaiseAndSetIfChanged(ref _statusText, value); - } + [UsedImplicitly] + public double Progress + { + get => _progress; + set => this.RaiseAndSetIfChanged(ref _progress, value); + } - [UsedImplicitly] - public Brush StatusTextBrush - { - get => _statusTextBrush; - set => this.RaiseAndSetIfChanged(ref _statusTextBrush, value); - } + [UsedImplicitly] + public string StatusText + { + get => _statusText; + set => this.RaiseAndSetIfChanged(ref _statusText, value); + } - [UsedImplicitly] - public GifAnimationControl GifAnimation - { - get => _gifGifAnimationControl; - set => this.RaiseAndSetIfChanged(ref _gifGifAnimationControl, value); - } + [UsedImplicitly] + public Brush StatusTextBrush + { + get => _statusTextBrush; + set => this.RaiseAndSetIfChanged(ref _statusTextBrush, value); + } - public ReactiveCommand CancelCommand { get; set; } + [UsedImplicitly] + public GifAnimationControl GifAnimation + { + get => _gifGifAnimationControl; + set => this.RaiseAndSetIfChanged(ref _gifGifAnimationControl, value); + } - public AvaloniaMainWindowViewModel([NotNull] ISnapInstallerEmbeddedResources snapInstallerEmbeddedResources, - [NotNull] ISnapProgressSource progressSource, [NotNull] Action onFirstFrameAnimatedCallback) - { - if (progressSource == null) throw new ArgumentNullException(nameof(progressSource)); + public ReactiveCommand CancelCommand { get; set; } - _snapInstallerEmbeddedResources = snapInstallerEmbeddedResources ?? throw new ArgumentNullException(nameof(snapInstallerEmbeddedResources)); - _onFirstFrameAnimatedCallback = onFirstFrameAnimatedCallback ?? throw new ArgumentNullException(nameof(onFirstFrameAnimatedCallback)); + public AvaloniaMainWindowViewModel([NotNull] ISnapInstallerEmbeddedResources snapInstallerEmbeddedResources, + [NotNull] ISnapProgressSource progressSource, [NotNull] Action onFirstFrameAnimatedCallback) + { + if (progressSource == null) throw new ArgumentNullException(nameof(progressSource)); - StatusText = string.Empty; - Progress = 0; - StatusTextBrush = (Brush) Brush.Parse("#fff"); + _snapInstallerEmbeddedResources = snapInstallerEmbeddedResources ?? throw new ArgumentNullException(nameof(snapInstallerEmbeddedResources)); + _onFirstFrameAnimatedCallback = onFirstFrameAnimatedCallback ?? throw new ArgumentNullException(nameof(onFirstFrameAnimatedCallback)); - progressSource.Progress = installationProgressPercentage => - { - Dispatcher.UIThread.InvokeAsync(() => Progress = installationProgressPercentage); - }; - } + StatusText = string.Empty; + Progress = 0; + StatusTextBrush = (Brush) Brush.Parse("#fff"); - public Task SetStatusTextAsync(string text) + progressSource.Progress = installationProgressPercentage => { - return Dispatcher.UIThread.InvokeAsync(() => StatusText = text); - } + Dispatcher.UIThread.InvokeAsync(() => Progress = installationProgressPercentage); + }; + } - public Task SetErrorAsync() - { - return Dispatcher.UIThread.InvokeAsync(() => - { - StatusTextBrush = (Brush) Brush.Parse("#B80F0A"); - }); - } + public Task SetStatusTextAsync(string text) + { + return Dispatcher.UIThread.InvokeAsync(() => StatusText = text); + } - public void OnInitialized() + public Task SetErrorAsync() + { + return Dispatcher.UIThread.InvokeAsync(() => { - GifAnimation.AddImages(_snapInstallerEmbeddedResources - .GifAnimation.Select(x => new Bitmap(new MemoryStream(x)))); + StatusTextBrush = (Brush) Brush.Parse("#B80F0A"); + }); + } + + public void OnInitialized() + { + GifAnimation.AddImages(_snapInstallerEmbeddedResources + .GifAnimation.Select(x => new Bitmap(new MemoryStream(x)))); - GifAnimation.Run(TimeSpan.FromMilliseconds(66), _onFirstFrameAnimatedCallback); - } + GifAnimation.Run(TimeSpan.FromMilliseconds(66), _onFirstFrameAnimatedCallback); } -} +} \ No newline at end of file diff --git a/src/Snap.Installer/ViewModels/ConsoleMainViewModel.cs b/src/Snap.Installer/ViewModels/ConsoleMainViewModel.cs index f928696d..ec5d4f35 100644 --- a/src/Snap.Installer/ViewModels/ConsoleMainViewModel.cs +++ b/src/Snap.Installer/ViewModels/ConsoleMainViewModel.cs @@ -1,19 +1,18 @@ using System.Threading.Tasks; -namespace Snap.Installer.ViewModels +namespace Snap.Installer.ViewModels; + +internal sealed class ConsoleMainViewModel : IMainWindowViewModel { - internal sealed class ConsoleMainViewModel : IMainWindowViewModel - { - public bool Headless => true; + public bool Headless => true; - public Task SetStatusTextAsync(string text) - { - return Task.CompletedTask; - } + public Task SetStatusTextAsync(string text) + { + return Task.CompletedTask; + } - public Task SetErrorAsync() - { - return Task.CompletedTask; - } + public Task SetErrorAsync() + { + return Task.CompletedTask; } -} +} \ No newline at end of file diff --git a/src/Snap.Installer/ViewModels/IMainWindowViewModel.cs b/src/Snap.Installer/ViewModels/IMainWindowViewModel.cs index b7460dd7..ea8fd3d5 100644 --- a/src/Snap.Installer/ViewModels/IMainWindowViewModel.cs +++ b/src/Snap.Installer/ViewModels/IMainWindowViewModel.cs @@ -1,11 +1,10 @@ using System.Threading.Tasks; -namespace Snap.Installer.ViewModels +namespace Snap.Installer.ViewModels; + +internal interface IMainWindowViewModel { - internal interface IMainWindowViewModel - { - bool Headless { get; } - Task SetStatusTextAsync(string text); - Task SetErrorAsync(); - } -} + bool Headless { get; } + Task SetStatusTextAsync(string text); + Task SetErrorAsync(); +} \ No newline at end of file diff --git a/src/Snap.Installer/ViewModels/ViewModelBase.cs b/src/Snap.Installer/ViewModels/ViewModelBase.cs index 105fe013..662226e0 100644 --- a/src/Snap.Installer/ViewModels/ViewModelBase.cs +++ b/src/Snap.Installer/ViewModels/ViewModelBase.cs @@ -1,9 +1,8 @@ using ReactiveUI; -namespace Snap.Installer.ViewModels -{ - internal abstract class ViewModelBase : ReactiveObject - { +namespace Snap.Installer.ViewModels; + +internal abstract class ViewModelBase : ReactiveObject +{ - } -} +} \ No newline at end of file diff --git a/src/Snap.Installer/Windows/CustomChromeWindow.cs b/src/Snap.Installer/Windows/CustomChromeWindow.cs index e041881a..9551d291 100644 --- a/src/Snap.Installer/Windows/CustomChromeWindow.cs +++ b/src/Snap.Installer/Windows/CustomChromeWindow.cs @@ -4,46 +4,45 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; -namespace Snap.Installer.Windows +namespace Snap.Installer.Windows; + +internal class CustomChromeWindow : Window { - internal class CustomChromeWindow : Window + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { - protected override void OnApplyTemplate(TemplateAppliedEventArgs e) - { - var thisHandle = PlatformImpl.Handle.Handle; + var thisHandle = PlatformImpl.Handle.Handle; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - NativeMethodsWindows.FocusThisWindow(thisHandle); - } - - base.OnApplyTemplate(e); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + NativeMethodsWindows.FocusThisWindow(thisHandle); } - protected override void OnPointerPressed(PointerPressedEventArgs e) - { - BeginMoveDrag(e); + base.OnApplyTemplate(e); + } - base.OnPointerPressed(e); - } + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + BeginMoveDrag(e); + + base.OnPointerPressed(e); + } - protected override void HandleWindowStateChanged(WindowState state) - { - WindowState = WindowState.Normal; - } + protected override void HandleWindowStateChanged(WindowState state) + { + WindowState = WindowState.Normal; + } + + protected static class NativeMethodsWindows + { + [DllImport("user32", SetLastError = true, EntryPoint = "SetActiveWindow")] + static extern IntPtr SetActiveWindow(IntPtr hWnd); + [DllImport("user32", SetLastError = true, EntryPoint = "SetForegroundWindow")] + static extern bool SetForegroundWindow(IntPtr hWnd); - protected static class NativeMethodsWindows + public static void FocusThisWindow(IntPtr hWnd) { - [DllImport("user32", SetLastError = true, EntryPoint = "SetActiveWindow")] - static extern IntPtr SetActiveWindow(IntPtr hWnd); - [DllImport("user32", SetLastError = true, EntryPoint = "SetForegroundWindow")] - static extern bool SetForegroundWindow(IntPtr hWnd); - - public static void FocusThisWindow(IntPtr hWnd) - { - SetActiveWindow(hWnd); - SetForegroundWindow(hWnd); - } + SetActiveWindow(hWnd); + SetForegroundWindow(hWnd); } } -} +} \ No newline at end of file diff --git a/src/Snap.Shared.Tests/Snap.Shared.Tests.csproj b/src/Snap.Shared.Tests/Snap.Shared.Tests.csproj index 2131228b..58f2f5eb 100644 --- a/src/Snap.Shared.Tests/Snap.Shared.Tests.csproj +++ b/src/Snap.Shared.Tests/Snap.Shared.Tests.csproj @@ -5,15 +5,15 @@ false false - net5.0 + net6.0 - + - + diff --git a/src/Snap.Tests/Core/Extensions/StringExtensionTests.cs b/src/Snap.Tests/Core/Extensions/StringExtensionTests.cs new file mode 100644 index 00000000..fb66a7fb --- /dev/null +++ b/src/Snap.Tests/Core/Extensions/StringExtensionTests.cs @@ -0,0 +1,27 @@ +using Snap.Extensions; +using Xunit; + +namespace Snap.Tests.Core.Extensions +{ + public class StringExtensionTests + { + [Theory] + [InlineData("time.cloudflare.com", null, null)] + [InlineData("time.cloudflare.com:", null, null)] + [InlineData("time.cloudflare.com:0", null, null)] + [InlineData("time.cloudflare.com:123", "time.cloudflare.com", 123)] + public static void TestBuildNetworkTimeProvider(string connectionString, string expectedServer, int? expectedPort) + { + var ntpProvider = connectionString.BuildNtpProvider(); + if (expectedServer == null + || !expectedPort.HasValue) + { + Assert.Null(ntpProvider); + return; + } + + Assert.Equal("time.cloudflare.com", ntpProvider.Server); + Assert.Equal(123, ntpProvider.Port); + } + } +} diff --git a/src/Snap.Tests/Snap.Tests.csproj b/src/Snap.Tests/Snap.Tests.csproj index 7bf6d53c..a93292f1 100644 --- a/src/Snap.Tests/Snap.Tests.csproj +++ b/src/Snap.Tests/Snap.Tests.csproj @@ -5,7 +5,7 @@ false true - net5.0 + net6.0 @@ -19,10 +19,10 @@ - + - - + + all diff --git a/src/Snap/AnyOS/ISnapOsTaskbar.cs b/src/Snap/AnyOS/ISnapOsTaskbar.cs index 2601f61d..6e2fccce 100644 --- a/src/Snap/AnyOS/ISnapOsTaskbar.cs +++ b/src/Snap/AnyOS/ISnapOsTaskbar.cs @@ -1,6 +1,5 @@ -namespace Snap.AnyOS +namespace Snap.AnyOS; + +internal interface ISnapOsTaskbar { - internal interface ISnapOsTaskbar - { - } -} +} \ No newline at end of file diff --git a/src/Snap/AnyOS/SnapOs.cs b/src/Snap/AnyOS/SnapOs.cs index 23d9045f..cdcd6ac5 100644 --- a/src/Snap/AnyOS/SnapOs.cs +++ b/src/Snap/AnyOS/SnapOs.cs @@ -11,167 +11,166 @@ using Snap.Core; using Snap.Logging; -namespace Snap.AnyOS +namespace Snap.AnyOS; + +public interface ISnapOsExitSignal { - public interface ISnapOsExitSignal - { - event EventHandler Exit; - } + event EventHandler Exit; +} - public enum SnapOsDistroType - { - Unknown, - Windows, - Ubuntu, - RaspberryPi - } +public enum SnapOsDistroType +{ + Unknown, + Windows, + Ubuntu, + RaspberryPi +} - internal interface ISnapOs - { - ISnapOsTaskbar Taskbar { get; } - OSPlatform OsPlatform { get; } - ISnapFilesystem Filesystem { get; } - ISnapOsProcessManager ProcessManager { get; } - SnapOsDistroType DistroType { get; } - ISnapOsSpecialFolders SpecialFolders { get; } - Task CreateShortcutsForExecutableAsync([NotNull] SnapOsShortcutDescription shortcutDescription, ILog logger = null, - CancellationToken cancellationToken = default); - bool EnsureConsole(); - List GetProcesses(); - List GetProcessesRunningInDirectory(string workingDirectory); - void KillAllProcessesInsideDirectory([NotNull] string workingDirectory); - void Kill(int pid); - void Kill(SnapOsProcess process); - void Exit(int exitCode = 0); - void InstallExitSignalHandler([NotNull] Action onExit); - } +internal interface ISnapOs +{ + ISnapOsTaskbar Taskbar { get; } + OSPlatform OsPlatform { get; } + ISnapFilesystem Filesystem { get; } + ISnapOsProcessManager ProcessManager { get; } + SnapOsDistroType DistroType { get; } + ISnapOsSpecialFolders SpecialFolders { get; } + Task CreateShortcutsForExecutableAsync([NotNull] SnapOsShortcutDescription shortcutDescription, ILog logger = null, + CancellationToken cancellationToken = default); + bool EnsureConsole(); + List GetProcesses(); + List GetProcessesRunningInDirectory(string workingDirectory); + void KillAllProcessesInsideDirectory([NotNull] string workingDirectory); + void Kill(int pid); + void Kill(SnapOsProcess process); + void Exit(int exitCode = 0); + void InstallExitSignalHandler([NotNull] Action onExit); +} - internal interface ISnapOsImpl - { - ISnapOsTaskbar Taskbar {get;} - OSPlatform OsPlatform { get; } - ISnapFilesystem Filesystem { get; } - ISnapOsProcessManager OsProcessManager { get; } - SnapOsDistroType DistroType { get; } - ISnapOsSpecialFolders SpecialFolders { get; } - Task CreateShortcutsForExecutableAsync([NotNull] SnapOsShortcutDescription shortcutDescription, ILog logger = null, - CancellationToken cancellationToken = default); - bool EnsureConsole(); - List GetProcesses(); - ISnapOsExitSignal InstallExitSignalHandler(); - } +internal interface ISnapOsImpl +{ + ISnapOsTaskbar Taskbar {get;} + OSPlatform OsPlatform { get; } + ISnapFilesystem Filesystem { get; } + ISnapOsProcessManager OsProcessManager { get; } + SnapOsDistroType DistroType { get; } + ISnapOsSpecialFolders SpecialFolders { get; } + Task CreateShortcutsForExecutableAsync([NotNull] SnapOsShortcutDescription shortcutDescription, ILog logger = null, + CancellationToken cancellationToken = default); + bool EnsureConsole(); + List GetProcesses(); + ISnapOsExitSignal InstallExitSignalHandler(); +} - internal sealed class SnapOs : ISnapOs +internal sealed class SnapOs : ISnapOs +{ + internal static ISnapOs AnyOs { - internal static ISnapOs AnyOs + get { - get - { - var snapFilesystem = new SnapFilesystem(); - var snapProcess = new SnapOsProcessManager(); - var snapSpecialFolders = SnapOsSpecialFolders.AnyOs; - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return new SnapOs(new SnapOsWindows(snapFilesystem, snapProcess, snapSpecialFolders)); - } + var snapFilesystem = new SnapFilesystem(); + var snapProcess = new SnapOsProcessManager(); + var snapSpecialFolders = SnapOsSpecialFolders.AnyOs; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return new SnapOs(new SnapOsUnix(snapFilesystem, snapProcess, snapSpecialFolders)); - } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return new SnapOs(new SnapOsWindows(snapFilesystem, snapProcess, snapSpecialFolders)); + } - throw new PlatformNotSupportedException(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return new SnapOs(new SnapOsUnix(snapFilesystem, snapProcess, snapSpecialFolders)); } + + throw new PlatformNotSupportedException(); } + } - public ISnapOsTaskbar Taskbar => OsImpl.Taskbar; - public OSPlatform OsPlatform => OsImpl.OsPlatform; - public ISnapFilesystem Filesystem => OsImpl.Filesystem; - public ISnapOsProcessManager ProcessManager => OsImpl.OsProcessManager; - public SnapOsDistroType DistroType => OsImpl.DistroType; - public ISnapOsSpecialFolders SpecialFolders => OsImpl.SpecialFolders; + public ISnapOsTaskbar Taskbar => OsImpl.Taskbar; + public OSPlatform OsPlatform => OsImpl.OsPlatform; + public ISnapFilesystem Filesystem => OsImpl.Filesystem; + public ISnapOsProcessManager ProcessManager => OsImpl.OsProcessManager; + public SnapOsDistroType DistroType => OsImpl.DistroType; + public ISnapOsSpecialFolders SpecialFolders => OsImpl.SpecialFolders; - ISnapOsImpl OsImpl { get; } + ISnapOsImpl OsImpl { get; } - public SnapOs(ISnapOsImpl snapOsImpl) - { - OsImpl = snapOsImpl ?? throw new ArgumentNullException(nameof(snapOsImpl)); - } + public SnapOs(ISnapOsImpl snapOsImpl) + { + OsImpl = snapOsImpl ?? throw new ArgumentNullException(nameof(snapOsImpl)); + } - public SnapOs(ISnapFilesystem snapFilesystem, ISnapOsProcessManager snapOsProcessManager, string workingDirectory, bool isUnitTest) + public SnapOs(ISnapFilesystem snapFilesystem, ISnapOsProcessManager snapOsProcessManager, string workingDirectory, bool isUnitTest) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - OsImpl = new SnapOsWindows(snapFilesystem, snapOsProcessManager, isUnitTest ? - (ISnapOsSpecialFolders) new SnapOsSpecialFoldersUnitTest(snapFilesystem, workingDirectory) : new SnapOsSpecialFoldersWindows(), isUnitTest); - } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - OsImpl = new SnapOsUnix(snapFilesystem, snapOsProcessManager, isUnitTest ? - (ISnapOsSpecialFolders) new SnapOsSpecialFoldersUnitTest(snapFilesystem, workingDirectory) : new SnapOsSpecialFoldersUnix()); - } - else - { - throw new PlatformNotSupportedException(); - } - } - - public Task CreateShortcutsForExecutableAsync(SnapOsShortcutDescription shortcutDescription, ILog logger = null, - CancellationToken cancellationToken = default) + OsImpl = new SnapOsWindows(snapFilesystem, snapOsProcessManager, isUnitTest ? + (ISnapOsSpecialFolders) new SnapOsSpecialFoldersUnitTest(snapFilesystem, workingDirectory) : new SnapOsSpecialFoldersWindows(), isUnitTest); + } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - return OsImpl.CreateShortcutsForExecutableAsync(shortcutDescription, logger, cancellationToken); + OsImpl = new SnapOsUnix(snapFilesystem, snapOsProcessManager, isUnitTest ? + (ISnapOsSpecialFolders) new SnapOsSpecialFoldersUnitTest(snapFilesystem, workingDirectory) : new SnapOsSpecialFoldersUnix()); } - - public bool EnsureConsole() + else { - return OsImpl.EnsureConsole(); + throw new PlatformNotSupportedException(); } + } - public List GetProcesses() - { - return OsImpl.GetProcesses(); - } + public Task CreateShortcutsForExecutableAsync(SnapOsShortcutDescription shortcutDescription, ILog logger = null, + CancellationToken cancellationToken = default) + { + return OsImpl.CreateShortcutsForExecutableAsync(shortcutDescription, logger, cancellationToken); + } - public List GetProcessesRunningInDirectory([NotNull] string workingDirectory) - { - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - var processes = GetProcesses(); + public bool EnsureConsole() + { + return OsImpl.EnsureConsole(); + } + + public List GetProcesses() + { + return OsImpl.GetProcesses(); + } + + public List GetProcessesRunningInDirectory([NotNull] string workingDirectory) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + var processes = GetProcesses(); - return processes.Where(x => x.Pid > 0 && x.WorkingDirectory != null && x.WorkingDirectory.StartsWith(workingDirectory, - DistroType == SnapOsDistroType.Windows ? - StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)).ToList(); - } + return processes.Where(x => x.Pid > 0 && x.WorkingDirectory != null && x.WorkingDirectory.StartsWith(workingDirectory, + DistroType == SnapOsDistroType.Windows ? + StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)).ToList(); + } - public void KillAllProcessesInsideDirectory(string workingDirectory) + public void KillAllProcessesInsideDirectory(string workingDirectory) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + var processes = GetProcessesRunningInDirectory(workingDirectory); + foreach (var process in processes) { - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - var processes = GetProcessesRunningInDirectory(workingDirectory); - foreach (var process in processes) - { - Kill(process); - } + Kill(process); } + } - public void Kill(int pid) - { - Process.GetProcessById(pid).Kill(); - } + public void Kill(int pid) + { + Process.GetProcessById(pid).Kill(); + } - public void Kill(SnapOsProcess process) - { - Kill(process.Pid); - } + public void Kill(SnapOsProcess process) + { + Kill(process.Pid); + } - public void Exit(int exitCode = 0) - { - Environment.Exit(exitCode); - } + public void Exit(int exitCode = 0) + { + Environment.Exit(exitCode); + } - public void InstallExitSignalHandler(Action onExit) - { - if (onExit == null) throw new ArgumentNullException(nameof(onExit)); - var exitSignalHandler = OsImpl.InstallExitSignalHandler(); - exitSignalHandler.Exit += (sender, args) => { onExit(); }; - } + public void InstallExitSignalHandler(Action onExit) + { + if (onExit == null) throw new ArgumentNullException(nameof(onExit)); + var exitSignalHandler = OsImpl.InstallExitSignalHandler(); + exitSignalHandler.Exit += (sender, args) => { onExit(); }; } -} +} \ No newline at end of file diff --git a/src/Snap/AnyOS/SnapOsProcessManager.cs b/src/Snap/AnyOS/SnapOsProcessManager.cs index b06b72a9..0c921257 100644 --- a/src/Snap/AnyOS/SnapOsProcessManager.cs +++ b/src/Snap/AnyOS/SnapOsProcessManager.cs @@ -7,171 +7,170 @@ using JetBrains.Annotations; using Snap.Extensions; -namespace Snap.AnyOS +namespace Snap.AnyOS; + +internal sealed class ProcessStartInfoBuilder { - internal sealed class ProcessStartInfoBuilder - { - public string Filename { get; } - public string WorkingDirectory { get; private set; } - public string Arguments => string.Join(" ", _arguments); + public string Filename { get; } + public string WorkingDirectory { get; private set; } + public string Arguments => string.Join(" ", _arguments); - readonly List _arguments; - - public ProcessStartInfoBuilder([NotNull] string filename) - { - if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(filename)); - Filename = filename; - _arguments = new List(); - } + readonly List _arguments; - public ProcessStartInfoBuilder Add([NotNull] string value) - { - if (value == null) throw new ArgumentNullException(nameof(value)); - _arguments.Add(value); - return this; - } - - public ProcessStartInfoBuilder AddRange([NotNull] IEnumerable values) - { - if (values == null) throw new ArgumentNullException(nameof(values)); - _arguments.AddRange(values); - return this; - } + public ProcessStartInfoBuilder([NotNull] string filename) + { + if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(filename)); + Filename = filename; + _arguments = new List(); + } - public ProcessStartInfoBuilder WithWorkingDirectory([NotNull] string value) - { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(value)); - WorkingDirectory = value; - return this; - } + public ProcessStartInfoBuilder Add([NotNull] string value) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + _arguments.Add(value); + return this; + } - public override string ToString() - { - return Arguments == string.Empty ? Filename : $"{Filename} {Arguments}"; - } + public ProcessStartInfoBuilder AddRange([NotNull] IEnumerable values) + { + if (values == null) throw new ArgumentNullException(nameof(values)); + _arguments.AddRange(values); + return this; } - internal struct SnapOsProcess + public ProcessStartInfoBuilder WithWorkingDirectory([NotNull] string value) { - public int Pid; - public string Name; - public string Filename; - public string WorkingDirectory; + if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(value)); + WorkingDirectory = value; + return this; } - internal interface ISnapOsProcessManager + public override string ToString() { - Process Current { get; } - SnapOsProcess Build(int pid, string name, string workingDirectory = default, string exeAbsoluteLocation = default); - Task<(int exitCode, string standardOutput)> RunAsync(ProcessStartInfoBuilder builder, CancellationToken cancellationToken); - Process StartNonBlocking(ProcessStartInfoBuilder builder); - Task ChmodExecuteAsync(string filename, CancellationToken cancellationToken); + return Arguments == string.Empty ? Filename : $"{Filename} {Arguments}"; } +} + +internal struct SnapOsProcess +{ + public int Pid; + public string Name; + public string Filename; + public string WorkingDirectory; +} + +internal interface ISnapOsProcessManager +{ + Process Current { get; } + SnapOsProcess Build(int pid, string name, string workingDirectory = default, string exeAbsoluteLocation = default); + Task<(int exitCode, string standardOutput)> RunAsync(ProcessStartInfoBuilder builder, CancellationToken cancellationToken); + Process StartNonBlocking(ProcessStartInfoBuilder builder); + Task ChmodExecuteAsync(string filename, CancellationToken cancellationToken); +} - internal sealed class SnapOsProcessManager : ISnapOsProcessManager - { - public Process Current => Process.GetCurrentProcess(); +internal sealed class SnapOsProcessManager : ISnapOsProcessManager +{ + public Process Current => Process.GetCurrentProcess(); - public SnapOsProcess Build(int pid, string name, string workingDirectory = default, string exeAbsoluteLocation = default) + public SnapOsProcess Build(int pid, string name, string workingDirectory = default, string exeAbsoluteLocation = default) + { + return new() { - return new() + Pid = pid, + Name = name, + WorkingDirectory = workingDirectory, + Filename = exeAbsoluteLocation + }; + } + + public Task<(int exitCode, string standardOutput)> RunAsync(ProcessStartInfoBuilder builder, CancellationToken cancellationToken) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + var processStartInfo = + new ProcessStartInfo(builder.Filename, builder.Arguments) { - Pid = pid, - Name = name, - WorkingDirectory = workingDirectory, - Filename = exeAbsoluteLocation + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + WorkingDirectory = builder.WorkingDirectory ?? string.Empty }; - } - public Task<(int exitCode, string standardOutput)> RunAsync(ProcessStartInfoBuilder builder, CancellationToken cancellationToken) + return RunAsync(processStartInfo, cancellationToken); + } + + static async Task<(int exitCode, string standardOutput)> RunAsync(ProcessStartInfo processStartInfo, CancellationToken cancellationToken) + { + var process = Process.Start(processStartInfo); + if (process == null) { - if (builder == null) throw new ArgumentNullException(nameof(builder)); - - var processStartInfo = - new ProcessStartInfo(builder.Filename, builder.Arguments) - { - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - WorkingDirectory = builder.WorkingDirectory ?? string.Empty - }; - - return RunAsync(processStartInfo, cancellationToken); + throw new Exception($"Error invoking process: {processStartInfo.FileName}. Arguments: {processStartInfo.Arguments}"); } - static async Task<(int exitCode, string standardOutput)> RunAsync(ProcessStartInfo processStartInfo, CancellationToken cancellationToken) + await Task.Run(() => { - var process = Process.Start(processStartInfo); - if (process == null) + while (!cancellationToken.IsCancellationRequested) { - throw new Exception($"Error invoking process: {processStartInfo.FileName}. Arguments: {processStartInfo.Arguments}"); - } - - await Task.Run(() => - { - while (!cancellationToken.IsCancellationRequested) - { - if (process.WaitForExit(2000)) - { - return; - } - } - - if (!cancellationToken.IsCancellationRequested) - { + if (process.WaitForExit(2000)) + { return; } + } - process.Kill(); - cancellationToken.ThrowIfCancellationRequested(); - }, cancellationToken); - - var textResult = await process.StandardOutput.ReadToEndAsync().WithCancellation(cancellationToken); - if (string.IsNullOrWhiteSpace(textResult) || process.ExitCode != 0) + if (!cancellationToken.IsCancellationRequested) { - var stdError = await process.StandardError.ReadToEndAsync().WithCancellation(cancellationToken); - textResult = $"{textResult ?? ""}\n{stdError}"; - - if (string.IsNullOrWhiteSpace(textResult)) - { - textResult = string.Empty; - } + return; } - return (process.ExitCode, textResult.Trim()); - } - - public Process StartNonBlocking([NotNull] ProcessStartInfoBuilder builder) - { - if (builder == null) throw new ArgumentNullException(nameof(builder)); + process.Kill(); + cancellationToken.ThrowIfCancellationRequested(); + }, cancellationToken); - var processStartInfo = - new ProcessStartInfo(builder.Filename, builder.Arguments) - { - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden, - ErrorDialog = false, - CreateNoWindow = true, - WorkingDirectory = builder.WorkingDirectory ?? string.Empty - }; - - return Process.Start(processStartInfo); - } - - public async Task ChmodExecuteAsync([NotNull] string filename, CancellationToken cancellationToken) + var textResult = await process.StandardOutput.ReadToEndAsync().WithCancellation(cancellationToken); + if (string.IsNullOrWhiteSpace(textResult) || process.ExitCode != 0) { - if (filename == null) throw new ArgumentNullException(nameof(filename)); + var stdError = await process.StandardError.ReadToEndAsync().WithCancellation(cancellationToken); + textResult = $"{textResult ?? ""}\n{stdError}"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (string.IsNullOrWhiteSpace(textResult)) { - throw new PlatformNotSupportedException(); + textResult = string.Empty; } + } + + return (process.ExitCode, textResult.Trim()); + } + + public Process StartNonBlocking([NotNull] ProcessStartInfoBuilder builder) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); - var (exitCode, _) = await RunAsync(new ProcessStartInfoBuilder("chmod").Add("+x").Add(filename), cancellationToken); - return exitCode == 0; + var processStartInfo = + new ProcessStartInfo(builder.Filename, builder.Arguments) + { + UseShellExecute = false, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false, + CreateNoWindow = true, + WorkingDirectory = builder.WorkingDirectory ?? string.Empty + }; + + return Process.Start(processStartInfo); + } + + public async Task ChmodExecuteAsync([NotNull] string filename, CancellationToken cancellationToken) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + throw new PlatformNotSupportedException(); } + + var (exitCode, _) = await RunAsync(new ProcessStartInfoBuilder("chmod").Add("+x").Add(filename), cancellationToken); + return exitCode == 0; } -} +} \ No newline at end of file diff --git a/src/Snap/AnyOS/SnapOsSpecialFolders.cs b/src/Snap/AnyOS/SnapOsSpecialFolders.cs index 6719b459..37f0e9ef 100644 --- a/src/Snap/AnyOS/SnapOsSpecialFolders.cs +++ b/src/Snap/AnyOS/SnapOsSpecialFolders.cs @@ -5,100 +5,99 @@ using Snap.Core; using Snap.Core.IO; -namespace Snap.AnyOS +namespace Snap.AnyOS; + +internal interface ISnapOsSpecialFolders { - internal interface ISnapOsSpecialFolders - { - string ApplicationData { get; } - string LocalApplicationData { get; } - string DesktopDirectory { get; } - string StartupDirectory { get; } - string StartMenu { get; } - string InstallerCacheDirectory { get; } - string NugetCacheDirectory { get; } - } + string ApplicationData { get; } + string LocalApplicationData { get; } + string DesktopDirectory { get; } + string StartupDirectory { get; } + string StartMenu { get; } + string InstallerCacheDirectory { get; } + string NugetCacheDirectory { get; } +} - internal abstract class SnapOsSpecialFolders : ISnapOsSpecialFolders - { - public virtual string ApplicationData => throw new PlatformNotSupportedException(); - public virtual string LocalApplicationData => throw new PlatformNotSupportedException(); - public virtual string DesktopDirectory => throw new PlatformNotSupportedException(); - public virtual string StartupDirectory => throw new PlatformNotSupportedException(); - public virtual string StartMenu => throw new PlatformNotSupportedException(); - public virtual string InstallerCacheDirectory => throw new PlatformNotSupportedException(); - public virtual string NugetCacheDirectory => throw new PlatformNotSupportedException(); +internal abstract class SnapOsSpecialFolders : ISnapOsSpecialFolders +{ + public virtual string ApplicationData => throw new PlatformNotSupportedException(); + public virtual string LocalApplicationData => throw new PlatformNotSupportedException(); + public virtual string DesktopDirectory => throw new PlatformNotSupportedException(); + public virtual string StartupDirectory => throw new PlatformNotSupportedException(); + public virtual string StartMenu => throw new PlatformNotSupportedException(); + public virtual string InstallerCacheDirectory => throw new PlatformNotSupportedException(); + public virtual string NugetCacheDirectory => throw new PlatformNotSupportedException(); - public static ISnapOsSpecialFolders AnyOs + public static ISnapOsSpecialFolders AnyOs + { + get { - get + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return new SnapOsSpecialFoldersWindows(); - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return new SnapOsSpecialFoldersUnix(); - } + return new SnapOsSpecialFoldersWindows(); + } - throw new PlatformNotSupportedException(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return new SnapOsSpecialFoldersUnix(); } + + throw new PlatformNotSupportedException(); } } +} - internal sealed class SnapOsSpecialFoldersWindows : SnapOsSpecialFolders - { - public override string ApplicationData { get; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - public override string LocalApplicationData { get; } = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - public override string DesktopDirectory { get; } = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); - public override string StartupDirectory { get; } = Environment.GetFolderPath(Environment.SpecialFolder.Startup); - public override string StartMenu { get; } = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu); - public override string InstallerCacheDirectory => $"{ApplicationData}\\snapx"; - public override string NugetCacheDirectory => $"{InstallerCacheDirectory}\\temp\\nuget"; - } +internal sealed class SnapOsSpecialFoldersWindows : SnapOsSpecialFolders +{ + public override string ApplicationData { get; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + public override string LocalApplicationData { get; } = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + public override string DesktopDirectory { get; } = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); + public override string StartupDirectory { get; } = Environment.GetFolderPath(Environment.SpecialFolder.Startup); + public override string StartMenu { get; } = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu); + public override string InstallerCacheDirectory => $"{ApplicationData}\\snapx"; + public override string NugetCacheDirectory => $"{InstallerCacheDirectory}\\temp\\nuget"; +} - internal sealed class SnapOsSpecialFoldersUnix : SnapOsSpecialFolders - { - public override string ApplicationData { get; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); - public override string LocalApplicationData { get; } = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - public override string DesktopDirectory { get; } = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); - public override string StartupDirectory => DesktopDirectory; - public override string StartMenu => DesktopDirectory; - public override string InstallerCacheDirectory => $"{ApplicationData}/snapx"; - public override string NugetCacheDirectory => $"{InstallerCacheDirectory}/temp/nuget"; - } +internal sealed class SnapOsSpecialFoldersUnix : SnapOsSpecialFolders +{ + public override string ApplicationData { get; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); + public override string LocalApplicationData { get; } = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + public override string DesktopDirectory { get; } = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); + public override string StartupDirectory => DesktopDirectory; + public override string StartMenu => DesktopDirectory; + public override string InstallerCacheDirectory => $"{ApplicationData}/snapx"; + public override string NugetCacheDirectory => $"{InstallerCacheDirectory}/temp/nuget"; +} - internal sealed class SnapOsSpecialFoldersUnitTest : SnapOsSpecialFolders, IAsyncDisposable - { - readonly ISnapFilesystem _snapFilesystem; - readonly DisposableDirectory _disposableDirectory; +internal sealed class SnapOsSpecialFoldersUnitTest : SnapOsSpecialFolders, IAsyncDisposable +{ + readonly ISnapFilesystem _snapFilesystem; + readonly DisposableDirectory _disposableDirectory; - public override string ApplicationData => _snapFilesystem.PathCombine(WorkingDirectory, nameof(ApplicationData)); - public override string LocalApplicationData => _snapFilesystem.PathCombine(WorkingDirectory, nameof(LocalApplicationData)); - public override string DesktopDirectory => _snapFilesystem.PathCombine(WorkingDirectory, nameof(DesktopDirectory)); - public override string StartupDirectory => _snapFilesystem.PathCombine(DesktopDirectory, nameof(StartupDirectory)); - public override string StartMenu => _snapFilesystem.PathCombine(DesktopDirectory, nameof(StartMenu)); - public override string InstallerCacheDirectory => _snapFilesystem.PathCombine(LocalApplicationData, nameof(InstallerCacheDirectory)); - public override string NugetCacheDirectory => _snapFilesystem.PathCombine(LocalApplicationData, nameof(NugetCacheDirectory)); - public string WorkingDirectory => _disposableDirectory.WorkingDirectory; + public override string ApplicationData => _snapFilesystem.PathCombine(WorkingDirectory, nameof(ApplicationData)); + public override string LocalApplicationData => _snapFilesystem.PathCombine(WorkingDirectory, nameof(LocalApplicationData)); + public override string DesktopDirectory => _snapFilesystem.PathCombine(WorkingDirectory, nameof(DesktopDirectory)); + public override string StartupDirectory => _snapFilesystem.PathCombine(DesktopDirectory, nameof(StartupDirectory)); + public override string StartMenu => _snapFilesystem.PathCombine(DesktopDirectory, nameof(StartMenu)); + public override string InstallerCacheDirectory => _snapFilesystem.PathCombine(LocalApplicationData, nameof(InstallerCacheDirectory)); + public override string NugetCacheDirectory => _snapFilesystem.PathCombine(LocalApplicationData, nameof(NugetCacheDirectory)); + public string WorkingDirectory => _disposableDirectory.WorkingDirectory; - public SnapOsSpecialFoldersUnitTest([NotNull] ISnapFilesystem snapFilesystem, [NotNull] string workingDirectory) - { - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - _snapFilesystem = snapFilesystem ?? throw new ArgumentNullException(nameof(snapFilesystem)); - _disposableDirectory = new DisposableDirectory(workingDirectory, _snapFilesystem); - _snapFilesystem.DirectoryCreateIfNotExists(ApplicationData); - _snapFilesystem.DirectoryCreateIfNotExists(LocalApplicationData); - _snapFilesystem.DirectoryCreateIfNotExists(StartupDirectory); - _snapFilesystem.DirectoryCreateIfNotExists(StartMenu); - _snapFilesystem.DirectoryCreateIfNotExists(InstallerCacheDirectory); - _snapFilesystem.DirectoryCreateIfNotExists(NugetCacheDirectory); - } + public SnapOsSpecialFoldersUnitTest([NotNull] ISnapFilesystem snapFilesystem, [NotNull] string workingDirectory) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + _snapFilesystem = snapFilesystem ?? throw new ArgumentNullException(nameof(snapFilesystem)); + _disposableDirectory = new DisposableDirectory(workingDirectory, _snapFilesystem); + _snapFilesystem.DirectoryCreateIfNotExists(ApplicationData); + _snapFilesystem.DirectoryCreateIfNotExists(LocalApplicationData); + _snapFilesystem.DirectoryCreateIfNotExists(StartupDirectory); + _snapFilesystem.DirectoryCreateIfNotExists(StartMenu); + _snapFilesystem.DirectoryCreateIfNotExists(InstallerCacheDirectory); + _snapFilesystem.DirectoryCreateIfNotExists(NugetCacheDirectory); + } - public ValueTask DisposeAsync() - { - return _disposableDirectory.DisposeAsync(); - } + public ValueTask DisposeAsync() + { + return _disposableDirectory.DisposeAsync(); } -} +} \ No newline at end of file diff --git a/src/Snap/AnyOS/Unix/SnapOS.Unix.cs b/src/Snap/AnyOS/Unix/SnapOS.Unix.cs index bb281c0d..c59c5fae 100644 --- a/src/Snap/AnyOS/Unix/SnapOS.Unix.cs +++ b/src/Snap/AnyOS/Unix/SnapOS.Unix.cs @@ -13,267 +13,256 @@ using Snap.Extensions; using Snap.Logging; -namespace Snap.AnyOS.Unix +namespace Snap.AnyOS.Unix; + +// https://stackoverflow.com/a/32716784/2470592 +internal sealed class SnapOsUnixExitSignal : ISnapOsExitSignal { - using Mono.Unix; + public event EventHandler Exit; - // https://stackoverflow.com/a/32716784/2470592 - internal sealed class SnapOsUnixExitSignal : ISnapOsExitSignal + public SnapOsUnixExitSignal() { - public event EventHandler Exit; + PosixSignalRegistration.Create(PosixSignal.SIGTERM, _ => OnExitSignalHandler()); + PosixSignalRegistration.Create(PosixSignal.SIGINT, _ => OnExitSignalHandler()); + } - readonly UnixSignal[] _signals = { - new(Mono.Unix.Native.Signum.SIGTERM), - new(Mono.Unix.Native.Signum.SIGINT), - new(Mono.Unix.Native.Signum.SIGUSR1) - }; + void OnExitSignalHandler() => Exit?.Invoke(null, EventArgs.Empty); +} - public SnapOsUnixExitSignal() - { - ThreadPool.QueueUserWorkItem(_ => - { - // blocking call to wait for any kill signal - UnixSignal.WaitAny(_signals, -1); +internal sealed class SnapOsUnix : ISnapOsImpl +{ + readonly ILog _logger = LogProvider.For(); + + public ISnapOsTaskbar Taskbar => throw new PlatformNotSupportedException("Todo: Implement taskbar progressbar"); + public OSPlatform OsPlatform => OSPlatform.Linux; + public ISnapFilesystem Filesystem { get; } + public ISnapOsProcessManager OsProcessManager { get; } + public SnapOsDistroType DistroType { get; private set; } = SnapOsDistroType.Unknown; + public ISnapOsSpecialFolders SpecialFolders { get; } + public string Username { get; private set; } + + public SnapOsUnix([NotNull] ISnapFilesystem filesystem, ISnapOsProcessManager snapOsProcessManager, + [NotNull] ISnapOsSpecialFolders snapOsSpecialFolders) + { + SpecialFolders = snapOsSpecialFolders ?? throw new ArgumentNullException(nameof(snapOsSpecialFolders)); + OsProcessManager = snapOsProcessManager; + Filesystem = filesystem ?? throw new ArgumentNullException(nameof(filesystem)); - Exit?.Invoke(null, EventArgs.Empty); - }); - } + SnapOsUnixInit(); } - internal sealed class SnapOsUnix : ISnapOsImpl + void SnapOsUnixInit() { - readonly ILog _logger = LogProvider.For(); - - public ISnapOsTaskbar Taskbar => throw new PlatformNotSupportedException("Todo: Implement taskbar progressbar"); - public OSPlatform OsPlatform => OSPlatform.Linux; - public ISnapFilesystem Filesystem { get; } - public ISnapOsProcessManager OsProcessManager { get; } - public SnapOsDistroType DistroType { get; private set; } = SnapOsDistroType.Unknown; - public ISnapOsSpecialFolders SpecialFolders { get; } - public string Username { get; private set; } - - public SnapOsUnix([NotNull] ISnapFilesystem filesystem, ISnapOsProcessManager snapOsProcessManager, - [NotNull] ISnapOsSpecialFolders snapOsSpecialFolders) - { - SpecialFolders = snapOsSpecialFolders ?? throw new ArgumentNullException(nameof(snapOsSpecialFolders)); - OsProcessManager = snapOsProcessManager; - Filesystem = filesystem ?? throw new ArgumentNullException(nameof(filesystem)); + Username = Environment.UserName; - SnapOsUnixInit(); - } - - void SnapOsUnixInit() + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - Username = Environment.UserName; + const string ubuntuReleaseFilename = "lsb_release"; + const string rasperryPiReleaseFilename = "/proc/device-tree/model"; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + try { - const string ubuntuReleaseFilename = "lsb_release"; - const string rasperryPiReleaseFilename = "/proc/device-tree/model"; - - try - { - var (lsbReleaseExitCode, lsbReleaseStdOutput) = TplHelper.RunSync(() => OsProcessManager - .RunAsync(new ProcessStartInfoBuilder(ubuntuReleaseFilename).Add("-a"), CancellationToken.None)); - if (lsbReleaseExitCode == 0 && lsbReleaseStdOutput != null) - { - var (distroId, _, _, _) = ParseLsbRelease(lsbReleaseStdOutput); - DistroType = distroId == "Ubuntu" ? SnapOsDistroType.Ubuntu : SnapOsDistroType.Unknown; - return; - } - } - catch (Exception e) - { - _logger.Warn("Exception thrown while executing 'lsb_release'", e); - } - - try + var (lsbReleaseExitCode, lsbReleaseStdOutput) = TplHelper.RunSync(() => OsProcessManager + .RunAsync(new ProcessStartInfoBuilder(ubuntuReleaseFilename).Add("-a"), CancellationToken.None)); + if (lsbReleaseExitCode == 0 && lsbReleaseStdOutput != null) { - using var stream = new FileStream(rasperryPiReleaseFilename, FileMode.Open, FileAccess.Read, FileShare.Read); - using var streamReader = new StreamReader(stream, Encoding.UTF8); - var content = streamReader.ReadToEnd(); - - DistroType = content.StartsWith("Raspberry Pi", StringComparison.InvariantCultureIgnoreCase) ? - SnapOsDistroType.RaspberryPi : SnapOsDistroType.Unknown; + var (distroId, _, _, _) = ParseLsbRelease(lsbReleaseStdOutput); + DistroType = distroId == "Ubuntu" ? SnapOsDistroType.Ubuntu : SnapOsDistroType.Unknown; return; } - catch (Exception e) - { - _logger.Warn($"Exception thrown while reading from '{rasperryPiReleaseFilename}'", e); - } + } + catch (Exception e) + { + _logger.Warn("Exception thrown while executing 'lsb_release'", e); + } - DistroType = SnapOsDistroType.Unknown; + try + { + using var stream = new FileStream(rasperryPiReleaseFilename, FileMode.Open, FileAccess.Read, FileShare.Read); + using var streamReader = new StreamReader(stream, Encoding.UTF8); + var content = streamReader.ReadToEnd(); + DistroType = content.StartsWith("Raspberry Pi", StringComparison.InvariantCultureIgnoreCase) ? + SnapOsDistroType.RaspberryPi : SnapOsDistroType.Unknown; return; } + catch (Exception e) + { + _logger.Warn($"Exception thrown while reading from '{rasperryPiReleaseFilename}'", e); + } + + DistroType = SnapOsDistroType.Unknown; - throw new PlatformNotSupportedException(); + return; } - public async Task CreateShortcutsForExecutableAsync(SnapOsShortcutDescription shortcutDescription, ILog logger = null, - CancellationToken cancellationToken = default) + throw new PlatformNotSupportedException(); + } + + public async Task CreateShortcutsForExecutableAsync(SnapOsShortcutDescription shortcutDescription, ILog logger = null, + CancellationToken cancellationToken = default) + { + if (shortcutDescription == null) throw new ArgumentNullException(nameof(shortcutDescription)); + var exeName = Filesystem.PathGetFileName(shortcutDescription.ExeAbsolutePath); + if (Username == null) { - if (shortcutDescription == null) throw new ArgumentNullException(nameof(shortcutDescription)); - var exeName = Filesystem.PathGetFileName(shortcutDescription.ExeAbsolutePath); - if (Username == null) - { - _logger?.Error($"Unable to create shortcut because username is null. Executable: {exeName}"); - return; - } + _logger?.Error($"Unable to create shortcut because username is null. Executable: {exeName}"); + return; + } - logger?.Info($"Creating shortcuts for executable: {shortcutDescription.ExeAbsolutePath}"); + logger?.Info($"Creating shortcuts for executable: {shortcutDescription.ExeAbsolutePath}"); - var autoStartEnabled = shortcutDescription.ShortcutLocations.HasFlag(SnapShortcutLocation.Startup); - var desktopEnabled = shortcutDescription.ShortcutLocations.HasFlag(SnapShortcutLocation.Desktop); - var startMenuEnabled = shortcutDescription.ShortcutLocations.HasFlag(SnapShortcutLocation.StartMenu); + var autoStartEnabled = shortcutDescription.ShortcutLocations.HasFlag(SnapShortcutLocation.Startup); + var desktopEnabled = shortcutDescription.ShortcutLocations.HasFlag(SnapShortcutLocation.Desktop); + var startMenuEnabled = shortcutDescription.ShortcutLocations.HasFlag(SnapShortcutLocation.StartMenu); - var desktopShortcutUtf8Content = BuildDesktopShortcut(shortcutDescription, shortcutDescription.NuspecReader.GetDescription()); - if (desktopShortcutUtf8Content == null) - { - _logger?.Warn( - $"Unknown error while building desktop shortcut for exe: {exeName}. Distro: {DistroType}. Maybe unsupported distro?"); - return; - } + var desktopShortcutUtf8Content = BuildDesktopShortcut(shortcutDescription, shortcutDescription.NuspecReader.GetDescription()); + if (desktopShortcutUtf8Content == null) + { + _logger?.Warn( + $"Unknown error while building desktop shortcut for exe: {exeName}. Distro: {DistroType}. Maybe unsupported distro?"); + return; + } - string applicationsDirectoryAbsolutePath; - string autoStartDirectoryAbsolutePath; - if (DistroType == SnapOsDistroType.Ubuntu) - { - applicationsDirectoryAbsolutePath = Filesystem.PathCombine($"/home/{Username}", ".local/share/applications"); - autoStartDirectoryAbsolutePath = Filesystem.PathCombine($"/home/{Username}", ".config/autostart"); - } else if (DistroType == SnapOsDistroType.RaspberryPi) - { - applicationsDirectoryAbsolutePath = Filesystem.PathCombine($"/home/{Username}", ".local/share/applications"); - autoStartDirectoryAbsolutePath = Filesystem.PathCombine($"/home/{Username}", ".config/autostart"); - } - else - { - _logger.Error($"Unable to create shortcuts. Unsupported distro type: {DistroType}."); - return; - } + string applicationsDirectoryAbsolutePath; + string autoStartDirectoryAbsolutePath; + if (DistroType == SnapOsDistroType.Ubuntu) + { + applicationsDirectoryAbsolutePath = Filesystem.PathCombine($"/home/{Username}", ".local/share/applications"); + autoStartDirectoryAbsolutePath = Filesystem.PathCombine($"/home/{Username}", ".config/autostart"); + } else if (DistroType == SnapOsDistroType.RaspberryPi) + { + applicationsDirectoryAbsolutePath = Filesystem.PathCombine($"/home/{Username}", ".local/share/applications"); + autoStartDirectoryAbsolutePath = Filesystem.PathCombine($"/home/{Username}", ".config/autostart"); + } + else + { + _logger.Error($"Unable to create shortcuts. Unsupported distro type: {DistroType}."); + return; + } + + var autoStartShortcutAbsolutePath = Filesystem.PathCombine(autoStartDirectoryAbsolutePath, $"{exeName}.desktop"); + var desktopShortcutAbsolutePath = Filesystem.PathCombine(applicationsDirectoryAbsolutePath, $"{exeName}.desktop"); - var autoStartShortcutAbsolutePath = Filesystem.PathCombine(autoStartDirectoryAbsolutePath, $"{exeName}.desktop"); - var desktopShortcutAbsolutePath = Filesystem.PathCombine(applicationsDirectoryAbsolutePath, $"{exeName}.desktop"); + if (startMenuEnabled) + { + _logger?.Warn("Creating start menu shortcuts is not supported on this OS."); + } - if (startMenuEnabled) + if (autoStartEnabled) + { + if (Filesystem.DirectoryCreateIfNotExists(autoStartDirectoryAbsolutePath)) { - _logger?.Warn("Creating start menu shortcuts is not supported on this OS."); + _logger?.Info($"Created autostart directory: {autoStartDirectoryAbsolutePath}"); } - if (autoStartEnabled) + if (Filesystem.FileDeleteIfExists(autoStartShortcutAbsolutePath)) { - if (Filesystem.DirectoryCreateIfNotExists(autoStartDirectoryAbsolutePath)) - { - _logger?.Info($"Created autostart directory: {autoStartDirectoryAbsolutePath}"); - } - - if (Filesystem.FileDeleteIfExists(autoStartShortcutAbsolutePath)) - { - _logger?.Info($"Deleted existing auto start shortcut: {autoStartShortcutAbsolutePath}"); - } + _logger?.Info($"Deleted existing auto start shortcut: {autoStartShortcutAbsolutePath}"); + } - _logger?.Info($"Creating autostart shortcut: {autoStartShortcutAbsolutePath}. " + - $"Absolute path: {shortcutDescription.ExeAbsolutePath}."); + _logger?.Info($"Creating autostart shortcut: {autoStartShortcutAbsolutePath}. " + + $"Absolute path: {shortcutDescription.ExeAbsolutePath}."); - await Filesystem.FileWriteUtf8StringAsync(desktopShortcutUtf8Content, - autoStartShortcutAbsolutePath, cancellationToken); + await Filesystem.FileWriteUtf8StringAsync(desktopShortcutUtf8Content, + autoStartShortcutAbsolutePath, cancellationToken); - _logger?.Info($"Attempting to mark shortcut as trusted: {autoStartShortcutAbsolutePath}."); - var trustedSuccess = await OsProcessManager.ChmodExecuteAsync(autoStartShortcutAbsolutePath, cancellationToken); - _logger?.Info($"Shortcut marked as trusted: {(trustedSuccess ? "yes" : "no")}"); - } + _logger?.Info($"Attempting to mark shortcut as trusted: {autoStartShortcutAbsolutePath}."); + var trustedSuccess = await OsProcessManager.ChmodExecuteAsync(autoStartShortcutAbsolutePath, cancellationToken); + _logger?.Info($"Shortcut marked as trusted: {(trustedSuccess ? "yes" : "no")}"); + } - if (desktopEnabled) + if (desktopEnabled) + { + if (!Filesystem.DirectoryExists(applicationsDirectoryAbsolutePath)) { - if (!Filesystem.DirectoryExists(applicationsDirectoryAbsolutePath)) - { - _logger?.Error($"Applications directory does not exist. Desktop shortcut will not be created. Path: {applicationsDirectoryAbsolutePath}"); - goto next; - } + _logger?.Error($"Applications directory does not exist. Desktop shortcut will not be created. Path: {applicationsDirectoryAbsolutePath}"); + goto next; + } - if (Filesystem.FileDeleteIfExists(desktopShortcutAbsolutePath)) - { - _logger?.Info($"Deleted existing shortcut: {desktopShortcutAbsolutePath}"); - } + if (Filesystem.FileDeleteIfExists(desktopShortcutAbsolutePath)) + { + _logger?.Info($"Deleted existing shortcut: {desktopShortcutAbsolutePath}"); + } - _logger?.Info($"Creating desktop shortcut: {desktopShortcutAbsolutePath}. " + - $"Auto startup: {autoStartEnabled}. " + - $"Absolute path: {shortcutDescription.ExeAbsolutePath}."); + _logger?.Info($"Creating desktop shortcut: {desktopShortcutAbsolutePath}. " + + $"Auto startup: {autoStartEnabled}. " + + $"Absolute path: {shortcutDescription.ExeAbsolutePath}."); - await Filesystem.FileWriteUtf8StringAsync(desktopShortcutUtf8Content, - desktopShortcutAbsolutePath, cancellationToken); + await Filesystem.FileWriteUtf8StringAsync(desktopShortcutUtf8Content, + desktopShortcutAbsolutePath, cancellationToken); - _logger?.Info($"Attempting to mark shortcut as trusted: {desktopShortcutAbsolutePath}."); - var trustedSuccess = await OsProcessManager.ChmodExecuteAsync(desktopShortcutAbsolutePath, cancellationToken); - _logger?.Info($"Shortcut marked as trusted: {(trustedSuccess ? "yes" : "no")}"); - } - - next: ; + _logger?.Info($"Attempting to mark shortcut as trusted: {desktopShortcutAbsolutePath}."); + var trustedSuccess = await OsProcessManager.ChmodExecuteAsync(desktopShortcutAbsolutePath, cancellationToken); + _logger?.Info($"Shortcut marked as trusted: {(trustedSuccess ? "yes" : "no")}"); } + + next: ; + } - public bool EnsureConsole() - { - return false; - } + public bool EnsureConsole() + { + return false; + } - public List GetProcesses() - { - var processes = Process.GetProcesses().Select(process => OsProcessManager.Build(process.Id, process.ProcessName)).ToList(); - return processes; - } + public List GetProcesses() + { + var processes = Process.GetProcesses().Select(process => OsProcessManager.Build(process.Id, process.ProcessName)).ToList(); + return processes; + } + + public ISnapOsExitSignal InstallExitSignalHandler() + { + return new SnapOsUnixExitSignal(); + } - public ISnapOsExitSignal InstallExitSignalHandler() + public (string distributorId, string description, string release, string codeName) ParseLsbRelease(string text) + { + string distributorId = default; + string description = default; + string release = default; + string codeName = default; + + if (string.IsNullOrWhiteSpace(text)) { - return new SnapOsUnixExitSignal(); + goto done; } - public (string distributorId, string description, string release, string codeName) ParseLsbRelease(string text) + static string Extract(string line, string identifier) { - string distributorId = default; - string description = default; - string release = default; - string codeName = default; - - if (string.IsNullOrWhiteSpace(text)) - { - goto done; - } + if (line == null) throw new ArgumentNullException(nameof(line)); + if (identifier == null) throw new ArgumentNullException(nameof(identifier)); - static string Extract(string line, string identifier) + if (!line.StartsWith(identifier, StringComparison.OrdinalIgnoreCase)) { - if (line == null) throw new ArgumentNullException(nameof(line)); - if (identifier == null) throw new ArgumentNullException(nameof(identifier)); - - if (!line.StartsWith(identifier, StringComparison.OrdinalIgnoreCase)) - { - return null; - } - - var value = line[identifier.Length..].TrimStart('\t'); - return value; + return null; } - foreach (var line in text.Split('\r', '\n').Where(x => !string.IsNullOrWhiteSpace(x))) - { - distributorId ??= Extract(line, "distributor id:"); - description ??= Extract(line, "description:"); - release ??= Extract(line, "release:"); - codeName ??= Extract(line, "codeName:"); - } - - done: - return (distributorId, description, release, codeName); + var value = line[identifier.Length..].TrimStart('\t'); + return value; } - string BuildDesktopShortcut([NotNull] SnapOsShortcutDescription shortcutDescription, string description) + foreach (var line in text.Split('\r', '\n').Where(x => !string.IsNullOrWhiteSpace(x))) { - if (shortcutDescription == null) throw new ArgumentNullException(nameof(shortcutDescription)); + distributorId ??= Extract(line, "distributor id:"); + description ??= Extract(line, "description:"); + release ??= Extract(line, "release:"); + codeName ??= Extract(line, "codeName:"); + } + + done: + return (distributorId, description, release, codeName); + } + + string BuildDesktopShortcut([NotNull] SnapOsShortcutDescription shortcutDescription, string description) + { + if (shortcutDescription == null) throw new ArgumentNullException(nameof(shortcutDescription)); - var workingDirectory = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath); + var workingDirectory = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath); - return DistroType switch - { - SnapOsDistroType.Ubuntu or SnapOsDistroType.RaspberryPi => $@"[Desktop Entry] + return DistroType switch + { + SnapOsDistroType.Ubuntu or SnapOsDistroType.RaspberryPi => $@"[Desktop Entry] Encoding=UTF-8 Version={shortcutDescription.SnapApp.Version} Type=Application @@ -282,10 +271,9 @@ string BuildDesktopShortcut([NotNull] SnapOsShortcutDescription shortcutDescript Icon={shortcutDescription.IconAbsolutePath} Name={shortcutDescription.SnapApp.Id} Comment={description}", - _ => null, - }; - } + _ => null, + }; + } - } } diff --git a/src/Snap/AnyOS/Windows/NativeMethodsWindows.cs b/src/Snap/AnyOS/Windows/NativeMethodsWindows.cs index 1600b386..f8be85aa 100644 --- a/src/Snap/AnyOS/Windows/NativeMethodsWindows.cs +++ b/src/Snap/AnyOS/Windows/NativeMethodsWindows.cs @@ -2,84 +2,83 @@ using System.Runtime.InteropServices; using System.Text; -namespace Snap.AnyOS.Windows -{ - [Flags] - internal enum ProcessAccess : uint { - All = 0x001F0FFF, - Terminate = 0x00000001, - CreateThread = 0x00000002, - VirtualMemoryOperation = 0x00000008, - VirtualMemoryRead = 0x00000010, - VirtualMemoryWrite = 0x00000020, - DuplicateHandle = 0x00000040, - CreateProcess = 0x000000080, - SetQuota = 0x00000100, - SetInformation = 0x00000200, - QueryInformation = 0x00000400, - QueryLimitedInformation = 0x00001000, - Synchronize = 0x00100000 - } +namespace Snap.AnyOS.Windows; - public enum StandardHandles - { - StdInputHandle = -10, - StdOutputHandle = -11, - StdErrorHandle = -12 - } +[Flags] +internal enum ProcessAccess : uint { + All = 0x001F0FFF, + Terminate = 0x00000001, + CreateThread = 0x00000002, + VirtualMemoryOperation = 0x00000008, + VirtualMemoryRead = 0x00000010, + VirtualMemoryWrite = 0x00000020, + DuplicateHandle = 0x00000040, + CreateProcess = 0x000000080, + SetQuota = 0x00000100, + SetInformation = 0x00000200, + QueryInformation = 0x00000400, + QueryLimitedInformation = 0x00001000, + Synchronize = 0x00100000 +} - internal static class NativeMethodsWindows - { - [DllImport("version.dll", SetLastError = true)] - [return:MarshalAs(UnmanagedType.Bool)] internal static extern bool GetFileVersionInfo( - string lpszFileName, - int dwHandleIgnored, - int dwLen, - [MarshalAs(UnmanagedType.LPArray)] byte[] lpData); +public enum StandardHandles +{ + StdInputHandle = -10, + StdOutputHandle = -11, + StdErrorHandle = -12 +} - [DllImport("version.dll", SetLastError = true)] - internal static extern int GetFileVersionInfoSize( - string lpszFileName, - IntPtr dwHandleIgnored); +internal static class NativeMethodsWindows +{ + [DllImport("version.dll", SetLastError = true)] + [return:MarshalAs(UnmanagedType.Bool)] internal static extern bool GetFileVersionInfo( + string lpszFileName, + int dwHandleIgnored, + int dwLen, + [MarshalAs(UnmanagedType.LPArray)] byte[] lpData); + + [DllImport("version.dll", SetLastError = true)] + internal static extern int GetFileVersionInfoSize( + string lpszFileName, + IntPtr dwHandleIgnored); - [DllImport("version.dll")] - [return:MarshalAs(UnmanagedType.Bool)] internal static extern bool VerQueryValue( - byte[] pBlock, - string pSubBlock, - out IntPtr pValue, - out int len); + [DllImport("version.dll")] + [return:MarshalAs(UnmanagedType.Bool)] internal static extern bool VerQueryValue( + byte[] pBlock, + string pSubBlock, + out IntPtr pValue, + out int len); - [DllImport("psapi.dll", SetLastError=true)] - internal static extern bool EnumProcesses( - IntPtr pProcessIds, // pointer to allocated DWORD array - int cb, - out int pBytesReturned); + [DllImport("psapi.dll", SetLastError=true)] + internal static extern bool EnumProcesses( + IntPtr pProcessIds, // pointer to allocated DWORD array + int cb, + out int pBytesReturned); - [DllImport("kernel32.dll", SetLastError=true)] - internal static extern bool QueryFullProcessImageName( - IntPtr hProcess, - [In] int justPassZeroHere, - [Out] StringBuilder lpImageFileName, - [In] [MarshalAs(UnmanagedType.U4)] ref int nSize); + [DllImport("kernel32.dll", SetLastError=true)] + internal static extern bool QueryFullProcessImageName( + IntPtr hProcess, + [In] int justPassZeroHere, + [Out] StringBuilder lpImageFileName, + [In] [MarshalAs(UnmanagedType.U4)] ref int nSize); - [DllImport("kernel32.dll", SetLastError=true)] - internal static extern IntPtr OpenProcess( - ProcessAccess processAccess, - bool bInheritHandle, - int processId); + [DllImport("kernel32.dll", SetLastError=true)] + internal static extern IntPtr OpenProcess( + ProcessAccess processAccess, + bool bInheritHandle, + int processId); - [DllImport("kernel32.dll", SetLastError = true)] - internal static extern bool CloseHandle(IntPtr hHandle); + [DllImport("kernel32.dll", SetLastError = true)] + internal static extern bool CloseHandle(IntPtr hHandle); - [DllImport("kernel32.dll", EntryPoint = "AllocConsole")] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool AllocConsole(); + [DllImport("kernel32.dll", EntryPoint = "AllocConsole")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool AllocConsole(); - [DllImport("kernel32.dll")] - internal static extern bool AttachConsole(int pid); + [DllImport("kernel32.dll")] + internal static extern bool AttachConsole(int pid); - [DllImport("kernel32.dll", EntryPoint = "GetStdHandle")] - internal static extern IntPtr GetStdHandle(StandardHandles nStdHandle); - } -} + [DllImport("kernel32.dll", EntryPoint = "GetStdHandle")] + internal static extern IntPtr GetStdHandle(StandardHandles nStdHandle); +} \ No newline at end of file diff --git a/src/Snap/AnyOS/Windows/PeUtility.cs b/src/Snap/AnyOS/Windows/PeUtility.cs index bd4262fa..7ecf18b4 100644 --- a/src/Snap/AnyOS/Windows/PeUtility.cs +++ b/src/Snap/AnyOS/Windows/PeUtility.cs @@ -7,553 +7,550 @@ using System.Runtime.InteropServices; using JetBrains.Annotations; -namespace Snap.AnyOS.Windows +namespace Snap.AnyOS.Windows; + +/// +/// Represents an Portable Executable format file. +/// +public class PeUtility : IDisposable { + #region File Header Structures - /// - /// Represents an Portable Executable format file. - /// - public class PeUtility : IDisposable + public enum SubSystemType : UInt16 { - #region File Header Structures - - public enum SubSystemType : UInt16 - { - IMAGE_SUBSYSTEM_UNKNOWN = 0, - IMAGE_SUBSYSTEM_NATIVE = 1, - IMAGE_SUBSYSTEM_WINDOWS_GUI = 2, - IMAGE_SUBSYSTEM_WINDOWS_CUI = 3, - IMAGE_SUBSYSTEM_POSIX_CUI = 7, - IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9, - IMAGE_SUBSYSTEM_EFI_APPLICATION = 10, - IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11, - IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12, - IMAGE_SUBSYSTEM_EFI_ROM = 13, - IMAGE_SUBSYSTEM_XBOX = 14 - - } + IMAGE_SUBSYSTEM_UNKNOWN = 0, + IMAGE_SUBSYSTEM_NATIVE = 1, + IMAGE_SUBSYSTEM_WINDOWS_GUI = 2, + IMAGE_SUBSYSTEM_WINDOWS_CUI = 3, + IMAGE_SUBSYSTEM_POSIX_CUI = 7, + IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9, + IMAGE_SUBSYSTEM_EFI_APPLICATION = 10, + IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11, + IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12, + IMAGE_SUBSYSTEM_EFI_ROM = 13, + IMAGE_SUBSYSTEM_XBOX = 14 - public struct IMAGE_DOS_HEADER - { // DOS .EXE header - public UInt16 e_magic; // Magic number - public UInt16 e_cblp; // Bytes on last page of file - public UInt16 e_cp; // Pages in file - public UInt16 e_crlc; // Relocations - public UInt16 e_cparhdr; // Size of header in paragraphs - public UInt16 e_minalloc; // Minimum extra paragraphs needed - public UInt16 e_maxalloc; // Maximum extra paragraphs needed - public UInt16 e_ss; // Initial (relative) SS value - public UInt16 e_sp; // Initial SP value - public UInt16 e_csum; // Checksum - public UInt16 e_ip; // Initial IP value - public UInt16 e_cs; // Initial (relative) CS value - public UInt16 e_lfarlc; // File address of relocation table - public UInt16 e_ovno; // Overlay number - public UInt16 e_res_0; // Reserved words - public UInt16 e_res_1; // Reserved words - public UInt16 e_res_2; // Reserved words - public UInt16 e_res_3; // Reserved words - public UInt16 e_oemid; // OEM identifier (for e_oeminfo) - public UInt16 e_oeminfo; // OEM information; e_oemid specific - public UInt16 e_res2_0; // Reserved words - public UInt16 e_res2_1; // Reserved words - public UInt16 e_res2_2; // Reserved words - public UInt16 e_res2_3; // Reserved words - public UInt16 e_res2_4; // Reserved words - public UInt16 e_res2_5; // Reserved words - public UInt16 e_res2_6; // Reserved words - public UInt16 e_res2_7; // Reserved words - public UInt16 e_res2_8; // Reserved words - public UInt16 e_res2_9; // Reserved words - public UInt32 e_lfanew; // File address of new exe header - } + } - [StructLayout(LayoutKind.Sequential)] - public struct IMAGE_DATA_DIRECTORY - { - public UInt32 VirtualAddress; - public UInt32 Size; - } + public struct IMAGE_DOS_HEADER + { // DOS .EXE header + public UInt16 e_magic; // Magic number + public UInt16 e_cblp; // Bytes on last page of file + public UInt16 e_cp; // Pages in file + public UInt16 e_crlc; // Relocations + public UInt16 e_cparhdr; // Size of header in paragraphs + public UInt16 e_minalloc; // Minimum extra paragraphs needed + public UInt16 e_maxalloc; // Maximum extra paragraphs needed + public UInt16 e_ss; // Initial (relative) SS value + public UInt16 e_sp; // Initial SP value + public UInt16 e_csum; // Checksum + public UInt16 e_ip; // Initial IP value + public UInt16 e_cs; // Initial (relative) CS value + public UInt16 e_lfarlc; // File address of relocation table + public UInt16 e_ovno; // Overlay number + public UInt16 e_res_0; // Reserved words + public UInt16 e_res_1; // Reserved words + public UInt16 e_res_2; // Reserved words + public UInt16 e_res_3; // Reserved words + public UInt16 e_oemid; // OEM identifier (for e_oeminfo) + public UInt16 e_oeminfo; // OEM information; e_oemid specific + public UInt16 e_res2_0; // Reserved words + public UInt16 e_res2_1; // Reserved words + public UInt16 e_res2_2; // Reserved words + public UInt16 e_res2_3; // Reserved words + public UInt16 e_res2_4; // Reserved words + public UInt16 e_res2_5; // Reserved words + public UInt16 e_res2_6; // Reserved words + public UInt16 e_res2_7; // Reserved words + public UInt16 e_res2_8; // Reserved words + public UInt16 e_res2_9; // Reserved words + public UInt32 e_lfanew; // File address of new exe header + } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_OPTIONAL_HEADER32 - { - public UInt16 Magic; - public Byte MajorLinkerVersion; - public Byte MinorLinkerVersion; - public UInt32 SizeOfCode; - public UInt32 SizeOfInitializedData; - public UInt32 SizeOfUninitializedData; - public UInt32 AddressOfEntryPoint; - public UInt32 BaseOfCode; - public UInt32 BaseOfData; - public UInt32 ImageBase; - public UInt32 SectionAlignment; - public UInt32 FileAlignment; - public UInt16 MajorOperatingSystemVersion; - public UInt16 MinorOperatingSystemVersion; - public UInt16 MajorImageVersion; - public UInt16 MinorImageVersion; - public UInt16 MajorSubsystemVersion; - public UInt16 MinorSubsystemVersion; - public UInt32 Win32VersionValue; - public UInt32 SizeOfImage; - public UInt32 SizeOfHeaders; - public UInt32 CheckSum; - public UInt16 Subsystem; - public UInt16 DllCharacteristics; - public UInt32 SizeOfStackReserve; - public UInt32 SizeOfStackCommit; - public UInt32 SizeOfHeapReserve; - public UInt32 SizeOfHeapCommit; - public UInt32 LoaderFlags; - public UInt32 NumberOfRvaAndSizes; - - public IMAGE_DATA_DIRECTORY ExportTable; - public IMAGE_DATA_DIRECTORY ImportTable; - public IMAGE_DATA_DIRECTORY ResourceTable; - public IMAGE_DATA_DIRECTORY ExceptionTable; - public IMAGE_DATA_DIRECTORY CertificateTable; - public IMAGE_DATA_DIRECTORY BaseRelocationTable; - public IMAGE_DATA_DIRECTORY Debug; - public IMAGE_DATA_DIRECTORY Architecture; - public IMAGE_DATA_DIRECTORY GlobalPtr; - public IMAGE_DATA_DIRECTORY TLSTable; - public IMAGE_DATA_DIRECTORY LoadConfigTable; - public IMAGE_DATA_DIRECTORY BoundImport; - public IMAGE_DATA_DIRECTORY IAT; - public IMAGE_DATA_DIRECTORY DelayImportDescriptor; - public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; - public IMAGE_DATA_DIRECTORY Reserved; - } + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DATA_DIRECTORY + { + public UInt32 VirtualAddress; + public UInt32 Size; + } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_OPTIONAL_HEADER64 - { - public UInt16 Magic; - public Byte MajorLinkerVersion; - public Byte MinorLinkerVersion; - public UInt32 SizeOfCode; - public UInt32 SizeOfInitializedData; - public UInt32 SizeOfUninitializedData; - public UInt32 AddressOfEntryPoint; - public UInt32 BaseOfCode; - public UInt64 ImageBase; - public UInt32 SectionAlignment; - public UInt32 FileAlignment; - public UInt16 MajorOperatingSystemVersion; - public UInt16 MinorOperatingSystemVersion; - public UInt16 MajorImageVersion; - public UInt16 MinorImageVersion; - public UInt16 MajorSubsystemVersion; - public UInt16 MinorSubsystemVersion; - public UInt32 Win32VersionValue; - public UInt32 SizeOfImage; - public UInt32 SizeOfHeaders; - public UInt32 CheckSum; - public UInt16 Subsystem; - public UInt16 DllCharacteristics; - public UInt64 SizeOfStackReserve; - public UInt64 SizeOfStackCommit; - public UInt64 SizeOfHeapReserve; - public UInt64 SizeOfHeapCommit; - public UInt32 LoaderFlags; - public UInt32 NumberOfRvaAndSizes; - - public IMAGE_DATA_DIRECTORY ExportTable; - public IMAGE_DATA_DIRECTORY ImportTable; - public IMAGE_DATA_DIRECTORY ResourceTable; - public IMAGE_DATA_DIRECTORY ExceptionTable; - public IMAGE_DATA_DIRECTORY CertificateTable; - public IMAGE_DATA_DIRECTORY BaseRelocationTable; - public IMAGE_DATA_DIRECTORY Debug; - public IMAGE_DATA_DIRECTORY Architecture; - public IMAGE_DATA_DIRECTORY GlobalPtr; - public IMAGE_DATA_DIRECTORY TLSTable; - public IMAGE_DATA_DIRECTORY LoadConfigTable; - public IMAGE_DATA_DIRECTORY BoundImport; - public IMAGE_DATA_DIRECTORY IAT; - public IMAGE_DATA_DIRECTORY DelayImportDescriptor; - public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; - public IMAGE_DATA_DIRECTORY Reserved; - } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER32 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt32 BaseOfData; + public UInt32 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt32 SizeOfStackReserve; + public UInt32 SizeOfStackCommit; + public UInt32 SizeOfHeapReserve; + public UInt32 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + + public IMAGE_DATA_DIRECTORY ExportTable; + public IMAGE_DATA_DIRECTORY ImportTable; + public IMAGE_DATA_DIRECTORY ResourceTable; + public IMAGE_DATA_DIRECTORY ExceptionTable; + public IMAGE_DATA_DIRECTORY CertificateTable; + public IMAGE_DATA_DIRECTORY BaseRelocationTable; + public IMAGE_DATA_DIRECTORY Debug; + public IMAGE_DATA_DIRECTORY Architecture; + public IMAGE_DATA_DIRECTORY GlobalPtr; + public IMAGE_DATA_DIRECTORY TLSTable; + public IMAGE_DATA_DIRECTORY LoadConfigTable; + public IMAGE_DATA_DIRECTORY BoundImport; + public IMAGE_DATA_DIRECTORY IAT; + public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + public IMAGE_DATA_DIRECTORY Reserved; + } - [StructLayout(LayoutKind.Sequential, Pack = 1)] - public struct IMAGE_FILE_HEADER - { - public UInt16 Machine; - public UInt16 NumberOfSections; - public UInt32 TimeDateStamp; - public UInt32 PointerToSymbolTable; - public UInt32 NumberOfSymbols; - public UInt16 SizeOfOptionalHeader; - public UInt16 Characteristics; - } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_OPTIONAL_HEADER64 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt64 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt64 SizeOfStackReserve; + public UInt64 SizeOfStackCommit; + public UInt64 SizeOfHeapReserve; + public UInt64 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + + public IMAGE_DATA_DIRECTORY ExportTable; + public IMAGE_DATA_DIRECTORY ImportTable; + public IMAGE_DATA_DIRECTORY ResourceTable; + public IMAGE_DATA_DIRECTORY ExceptionTable; + public IMAGE_DATA_DIRECTORY CertificateTable; + public IMAGE_DATA_DIRECTORY BaseRelocationTable; + public IMAGE_DATA_DIRECTORY Debug; + public IMAGE_DATA_DIRECTORY Architecture; + public IMAGE_DATA_DIRECTORY GlobalPtr; + public IMAGE_DATA_DIRECTORY TLSTable; + public IMAGE_DATA_DIRECTORY LoadConfigTable; + public IMAGE_DATA_DIRECTORY BoundImport; + public IMAGE_DATA_DIRECTORY IAT; + public IMAGE_DATA_DIRECTORY DelayImportDescriptor; + public IMAGE_DATA_DIRECTORY CLRRuntimeHeader; + public IMAGE_DATA_DIRECTORY Reserved; + } - // Grabbed the following 2 definitions from http://www.pinvoke.net/default.aspx/Structures/IMAGE_SECTION_HEADER.html + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct IMAGE_FILE_HEADER + { + public UInt16 Machine; + public UInt16 NumberOfSections; + public UInt32 TimeDateStamp; + public UInt32 PointerToSymbolTable; + public UInt32 NumberOfSymbols; + public UInt16 SizeOfOptionalHeader; + public UInt16 Characteristics; + } - [StructLayout(LayoutKind.Explicit)] - public struct IMAGE_SECTION_HEADER - { - [FieldOffset(0)] - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public char[] Name; - [FieldOffset(8)] - public UInt32 VirtualSize; - [FieldOffset(12)] - public UInt32 VirtualAddress; - [FieldOffset(16)] - public UInt32 SizeOfRawData; - [FieldOffset(20)] - public UInt32 PointerToRawData; - [FieldOffset(24)] - public UInt32 PointerToRelocations; - [FieldOffset(28)] - public UInt32 PointerToLinenumbers; - [FieldOffset(32)] - public UInt16 NumberOfRelocations; - [FieldOffset(34)] - public UInt16 NumberOfLinenumbers; - [FieldOffset(36)] - public DataSectionFlags Characteristics; - - public string Section - { - get { return new(Name); } - } - } + // Grabbed the following 2 definitions from http://www.pinvoke.net/default.aspx/Structures/IMAGE_SECTION_HEADER.html - [Flags] - public enum DataSectionFlags : uint + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_SECTION_HEADER + { + [FieldOffset(0)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] + public char[] Name; + [FieldOffset(8)] + public UInt32 VirtualSize; + [FieldOffset(12)] + public UInt32 VirtualAddress; + [FieldOffset(16)] + public UInt32 SizeOfRawData; + [FieldOffset(20)] + public UInt32 PointerToRawData; + [FieldOffset(24)] + public UInt32 PointerToRelocations; + [FieldOffset(28)] + public UInt32 PointerToLinenumbers; + [FieldOffset(32)] + public UInt16 NumberOfRelocations; + [FieldOffset(34)] + public UInt16 NumberOfLinenumbers; + [FieldOffset(36)] + public DataSectionFlags Characteristics; + + public string Section { - /// - /// Reserved for future use. - /// - TypeReg = 0x00000000, - /// - /// Reserved for future use. - /// - TypeDsect = 0x00000001, - /// - /// Reserved for future use. - /// - TypeNoLoad = 0x00000002, - /// - /// Reserved for future use. - /// - TypeGroup = 0x00000004, - /// - /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. - /// - TypeNoPadded = 0x00000008, - /// - /// Reserved for future use. - /// - TypeCopy = 0x00000010, - /// - /// The section contains executable code. - /// - ContentCode = 0x00000020, - /// - /// The section contains initialized data. - /// - ContentInitializedData = 0x00000040, - /// - /// The section contains uninitialized data. - /// - ContentUninitializedData = 0x00000080, - /// - /// Reserved for future use. - /// - LinkOther = 0x00000100, - /// - /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. - /// - LinkInfo = 0x00000200, - /// - /// Reserved for future use. - /// - TypeOver = 0x00000400, - /// - /// The section will not become part of the image. This is valid only for object files. - /// - LinkRemove = 0x00000800, - /// - /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. - /// - LinkComDat = 0x00001000, - /// - /// Reset speculative exceptions handling bits in the TLB entries for this section. - /// - NoDeferSpecExceptions = 0x00004000, - /// - /// The section contains data referenced through the global pointer (GP). - /// - RelativeGP = 0x00008000, - /// - /// Reserved for future use. - /// - MemPurgeable = 0x00020000, - /// - /// Reserved for future use. - /// - Memory16Bit = 0x00020000, - /// - /// Reserved for future use. - /// - MemoryLocked = 0x00040000, - /// - /// Reserved for future use. - /// - MemoryPreload = 0x00080000, - /// - /// Align data on a 1-byte boundary. Valid only for object files. - /// - Align1Bytes = 0x00100000, - /// - /// Align data on a 2-byte boundary. Valid only for object files. - /// - Align2Bytes = 0x00200000, - /// - /// Align data on a 4-byte boundary. Valid only for object files. - /// - Align4Bytes = 0x00300000, - /// - /// Align data on an 8-byte boundary. Valid only for object files. - /// - Align8Bytes = 0x00400000, - /// - /// Align data on a 16-byte boundary. Valid only for object files. - /// - Align16Bytes = 0x00500000, - /// - /// Align data on a 32-byte boundary. Valid only for object files. - /// - Align32Bytes = 0x00600000, - /// - /// Align data on a 64-byte boundary. Valid only for object files. - /// - Align64Bytes = 0x00700000, - /// - /// Align data on a 128-byte boundary. Valid only for object files. - /// - Align128Bytes = 0x00800000, - /// - /// Align data on a 256-byte boundary. Valid only for object files. - /// - Align256Bytes = 0x00900000, - /// - /// Align data on a 512-byte boundary. Valid only for object files. - /// - Align512Bytes = 0x00A00000, - /// - /// Align data on a 1024-byte boundary. Valid only for object files. - /// - Align1024Bytes = 0x00B00000, - /// - /// Align data on a 2048-byte boundary. Valid only for object files. - /// - Align2048Bytes = 0x00C00000, - /// - /// Align data on a 4096-byte boundary. Valid only for object files. - /// - Align4096Bytes = 0x00D00000, - /// - /// Align data on an 8192-byte boundary. Valid only for object files. - /// - Align8192Bytes = 0x00E00000, - /// - /// The section contains extended relocations. - /// - LinkExtendedRelocationOverflow = 0x01000000, - /// - /// The section can be discarded as needed. - /// - MemoryDiscardable = 0x02000000, - /// - /// The section cannot be cached. - /// - MemoryNotCached = 0x04000000, - /// - /// The section is not pageable. - /// - MemoryNotPaged = 0x08000000, - /// - /// The section can be shared in memory. - /// - MemoryShared = 0x10000000, - /// - /// The section can be executed as code. - /// - MemoryExecute = 0x20000000, - /// - /// The section can be read. - /// - MemoryRead = 0x40000000, - /// - /// The section can be written to. - /// - MemoryWrite = 0x80000000 + get { return new(Name); } } + } - #endregion File Header Structures - - #region Private Fields - + [Flags] + public enum DataSectionFlags : uint + { /// - /// The DOS header + /// Reserved for future use. /// - private IMAGE_DOS_HEADER dosHeader; + TypeReg = 0x00000000, /// - /// The file header + /// Reserved for future use. /// - private IMAGE_FILE_HEADER fileHeader; - private long fileHeaderOffset; - + TypeDsect = 0x00000001, + /// + /// Reserved for future use. + /// + TypeNoLoad = 0x00000002, + /// + /// Reserved for future use. + /// + TypeGroup = 0x00000004, + /// + /// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files. + /// + TypeNoPadded = 0x00000008, + /// + /// Reserved for future use. + /// + TypeCopy = 0x00000010, + /// + /// The section contains executable code. + /// + ContentCode = 0x00000020, + /// + /// The section contains initialized data. + /// + ContentInitializedData = 0x00000040, + /// + /// The section contains uninitialized data. + /// + ContentUninitializedData = 0x00000080, + /// + /// Reserved for future use. + /// + LinkOther = 0x00000100, + /// + /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. + /// + LinkInfo = 0x00000200, + /// + /// Reserved for future use. + /// + TypeOver = 0x00000400, + /// + /// The section will not become part of the image. This is valid only for object files. + /// + LinkRemove = 0x00000800, + /// + /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. + /// + LinkComDat = 0x00001000, + /// + /// Reset speculative exceptions handling bits in the TLB entries for this section. + /// + NoDeferSpecExceptions = 0x00004000, + /// + /// The section contains data referenced through the global pointer (GP). + /// + RelativeGP = 0x00008000, + /// + /// Reserved for future use. + /// + MemPurgeable = 0x00020000, + /// + /// Reserved for future use. + /// + Memory16Bit = 0x00020000, + /// + /// Reserved for future use. + /// + MemoryLocked = 0x00040000, + /// + /// Reserved for future use. + /// + MemoryPreload = 0x00080000, + /// + /// Align data on a 1-byte boundary. Valid only for object files. + /// + Align1Bytes = 0x00100000, + /// + /// Align data on a 2-byte boundary. Valid only for object files. + /// + Align2Bytes = 0x00200000, + /// + /// Align data on a 4-byte boundary. Valid only for object files. + /// + Align4Bytes = 0x00300000, + /// + /// Align data on an 8-byte boundary. Valid only for object files. + /// + Align8Bytes = 0x00400000, + /// + /// Align data on a 16-byte boundary. Valid only for object files. + /// + Align16Bytes = 0x00500000, + /// + /// Align data on a 32-byte boundary. Valid only for object files. + /// + Align32Bytes = 0x00600000, + /// + /// Align data on a 64-byte boundary. Valid only for object files. + /// + Align64Bytes = 0x00700000, + /// + /// Align data on a 128-byte boundary. Valid only for object files. + /// + Align128Bytes = 0x00800000, + /// + /// Align data on a 256-byte boundary. Valid only for object files. + /// + Align256Bytes = 0x00900000, + /// + /// Align data on a 512-byte boundary. Valid only for object files. + /// + Align512Bytes = 0x00A00000, + /// + /// Align data on a 1024-byte boundary. Valid only for object files. + /// + Align1024Bytes = 0x00B00000, /// - /// Optional 32 bit file header + /// Align data on a 2048-byte boundary. Valid only for object files. /// - private IMAGE_OPTIONAL_HEADER32 optionalHeader32; + Align2048Bytes = 0x00C00000, /// - /// Optional 64 bit file header + /// Align data on a 4096-byte boundary. Valid only for object files. /// - private IMAGE_OPTIONAL_HEADER64 optionalHeader64; + Align4096Bytes = 0x00D00000, /// - /// Image Section headers. Number of sections is in the file header. + /// Align data on an 8192-byte boundary. Valid only for object files. /// - private IMAGE_SECTION_HEADER[] imageSectionHeaders; + Align8192Bytes = 0x00E00000, + /// + /// The section contains extended relocations. + /// + LinkExtendedRelocationOverflow = 0x01000000, + /// + /// The section can be discarded as needed. + /// + MemoryDiscardable = 0x02000000, + /// + /// The section cannot be cached. + /// + MemoryNotCached = 0x04000000, + /// + /// The section is not pageable. + /// + MemoryNotPaged = 0x08000000, + /// + /// The section can be shared in memory. + /// + MemoryShared = 0x10000000, + /// + /// The section can be executed as code. + /// + MemoryExecute = 0x20000000, + /// + /// The section can be read. + /// + MemoryRead = 0x40000000, + /// + /// The section can be written to. + /// + MemoryWrite = 0x80000000 + } - private Stream curFileStream; + #endregion File Header Structures - #endregion Private Fields + #region Private Fields - #region Public Methods + /// + /// The DOS header + /// + private IMAGE_DOS_HEADER dosHeader; + /// + /// The file header + /// + private IMAGE_FILE_HEADER fileHeader; + private long fileHeaderOffset; - public PeUtility([NotNull] Stream srcStream) - { - curFileStream = srcStream ?? throw new ArgumentNullException(nameof(srcStream)); + /// + /// Optional 32 bit file header + /// + private IMAGE_OPTIONAL_HEADER32 optionalHeader32; + /// + /// Optional 64 bit file header + /// + private IMAGE_OPTIONAL_HEADER64 optionalHeader64; + /// + /// Image Section headers. Number of sections is in the file header. + /// + private IMAGE_SECTION_HEADER[] imageSectionHeaders; - var reader = new BinaryReader(curFileStream); - dosHeader = FromBinaryReader(reader); + private Stream curFileStream; - // Add 4 bytes to the offset - srcStream.Seek(dosHeader.e_lfanew, SeekOrigin.Begin); + #endregion Private Fields - var ntHeadersSignature = reader.ReadUInt32(); - fileHeader = FromBinaryReader(reader); - this.fileHeaderOffset = srcStream.Position; - if (this.Is32BitHeader) - { - optionalHeader32 = FromBinaryReader(reader); + #region Public Methods - } - else - { - optionalHeader64 = FromBinaryReader(reader); - } + public PeUtility([NotNull] Stream srcStream) + { + curFileStream = srcStream ?? throw new ArgumentNullException(nameof(srcStream)); - imageSectionHeaders = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections]; - for (var headerNo = 0; headerNo < imageSectionHeaders.Length; ++headerNo) - { - imageSectionHeaders[headerNo] = FromBinaryReader(reader); - } + var reader = new BinaryReader(curFileStream); + dosHeader = FromBinaryReader(reader); - } + // Add 4 bytes to the offset + srcStream.Seek(dosHeader.e_lfanew, SeekOrigin.Begin); - public PeUtility([NotNull] string filePath) : this(new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite)) + var ntHeadersSignature = reader.ReadUInt32(); + fileHeader = FromBinaryReader(reader); + this.fileHeaderOffset = srcStream.Position; + if (this.Is32BitHeader) { + optionalHeader32 = FromBinaryReader(reader); } - - /// - /// Reads in a block from a file and converts it to the struct - /// type specified by the template parameter - /// - /// - /// - /// - public static T FromBinaryReader(BinaryReader reader) + else { - // Read in a byte array - var bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); - - // Pin the managed memory while, copy it out the data, then unpin it - var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); - var theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); - handle.Free(); - - return theStructure; + optionalHeader64 = FromBinaryReader(reader); } - public void Dispose() + imageSectionHeaders = new IMAGE_SECTION_HEADER[fileHeader.NumberOfSections]; + for (var headerNo = 0; headerNo < imageSectionHeaders.Length; ++headerNo) { - curFileStream.Dispose(); + imageSectionHeaders[headerNo] = FromBinaryReader(reader); } - #endregion Public Methods + } - #region Properties + public PeUtility([NotNull] string filePath) : this(new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite)) + { - /// - /// Gets if the file header is 32 bit or not - /// - public bool Is32BitHeader - { - get - { - UInt16 IMAGE_FILE_32BIT_MACHINE = 0x0100; - return (IMAGE_FILE_32BIT_MACHINE & FileHeader.Characteristics) == IMAGE_FILE_32BIT_MACHINE; - } - } + } - /// - /// Gets the file header - /// - public IMAGE_FILE_HEADER FileHeader - { - get - { - return fileHeader; - } - } + /// + /// Reads in a block from a file and converts it to the struct + /// type specified by the template parameter + /// + /// + /// + /// + public static T FromBinaryReader(BinaryReader reader) + { + // Read in a byte array + var bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); - /// - /// Gets the optional header - /// - public IMAGE_OPTIONAL_HEADER32 OptionalHeader32 - { - get => optionalHeader32; - } + // Pin the managed memory while, copy it out the data, then unpin it + var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + var theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + handle.Free(); - /// - /// Gets the optional header - /// - public IMAGE_OPTIONAL_HEADER64 OptionalHeader64 - { - get => optionalHeader64; - } + return theStructure; + } - /// - /// Gets the PE file stream for R/W functions. - /// - public Stream Stream - { - get => curFileStream; - } + public void Dispose() + { + curFileStream.Dispose(); + } + + #endregion Public Methods - public long MainHeaderOffset + #region Properties + + /// + /// Gets if the file header is 32 bit or not + /// + public bool Is32BitHeader + { + get { - get => fileHeaderOffset; + UInt16 IMAGE_FILE_32BIT_MACHINE = 0x0100; + return (IMAGE_FILE_32BIT_MACHINE & FileHeader.Characteristics) == IMAGE_FILE_32BIT_MACHINE; } + } - public IMAGE_SECTION_HEADER[] ImageSectionHeaders + /// + /// Gets the file header + /// + public IMAGE_FILE_HEADER FileHeader + { + get { - get => imageSectionHeaders; + return fileHeader; } + } + + /// + /// Gets the optional header + /// + public IMAGE_OPTIONAL_HEADER32 OptionalHeader32 + { + get => optionalHeader32; + } + + /// + /// Gets the optional header + /// + public IMAGE_OPTIONAL_HEADER64 OptionalHeader64 + { + get => optionalHeader64; + } - #endregion Properties + /// + /// Gets the PE file stream for R/W functions. + /// + public Stream Stream + { + get => curFileStream; + } + + public long MainHeaderOffset + { + get => fileHeaderOffset; + } + + public IMAGE_SECTION_HEADER[] ImageSectionHeaders + { + get => imageSectionHeaders; } + #endregion Properties } // Resharper enable all diff --git a/src/Snap/AnyOS/Windows/ShellFile.cs b/src/Snap/AnyOS/Windows/ShellFile.cs index e0eeaaee..52293c09 100644 --- a/src/Snap/AnyOS/Windows/ShellFile.cs +++ b/src/Snap/AnyOS/Windows/ShellFile.cs @@ -7,979 +7,978 @@ // All of this code is from http://vbaccelerator.com/home/NET/Code/Libraries/Shell_Projects/Creating_and_Modifying_Shortcuts/article.asp -namespace Snap.AnyOS.Windows +namespace Snap.AnyOS.Windows; + +/// +/// Summary description for ShellLink. +/// +public class ShellLink : IDisposable { - /// - /// Summary description for ShellLink. - /// - public class ShellLink : IDisposable + [ComImport()] + [Guid("0000010C-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IPersist { - [ComImport()] - [Guid("0000010C-0000-0000-C000-000000000046")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - interface IPersist - { - [PreserveSig] - //[helpstring("Returns the class identifier for the component object")] - void GetClassID(out Guid pClassID); - } + [PreserveSig] + //[helpstring("Returns the class identifier for the component object")] + void GetClassID(out Guid pClassID); + } - [ComImport()] - [Guid("0000010B-0000-0000-C000-000000000046")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - interface IPersistFile - { - // can't get this to go if I extend IPersist, so put it here: - [PreserveSig] - void GetClassID(out Guid pClassID); + [ComImport()] + [Guid("0000010B-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IPersistFile + { + // can't get this to go if I extend IPersist, so put it here: + [PreserveSig] + void GetClassID(out Guid pClassID); + + //[helpstring("Checks for changes since last file write")] + void IsDirty(); + + //[helpstring("Opens the specified file and initializes the object from its contents")] + void Load( + [MarshalAs(UnmanagedType.LPWStr)] string pszFileName, + uint dwMode); + + //[helpstring("Saves the object into the specified file")] + void Save( + [MarshalAs(UnmanagedType.LPWStr)] string pszFileName, + [MarshalAs(UnmanagedType.Bool)] bool fRemember); + + //[helpstring("Notifies the object that save is completed")] + void SaveCompleted( + [MarshalAs(UnmanagedType.LPWStr)] string pszFileName); + + //[helpstring("Gets the current name of the file associated with the object")] + void GetCurFile( + [MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName); + } - //[helpstring("Checks for changes since last file write")] - void IsDirty(); + [StructLayout(LayoutKind.Sequential)] + public struct PropVariant + { + public short variantType; + public short Reserved1, Reserved2, Reserved3; + public IntPtr pointerValue; - //[helpstring("Opens the specified file and initializes the object from its contents")] - void Load( - [MarshalAs(UnmanagedType.LPWStr)] string pszFileName, - uint dwMode); + public static PropVariant FromString(string str) + { + var pv = new PropVariant() { + variantType = 31, // VT_LPWSTR + pointerValue = Marshal.StringToCoTaskMemUni(str), + }; - //[helpstring("Saves the object into the specified file")] - void Save( - [MarshalAs(UnmanagedType.LPWStr)] string pszFileName, - [MarshalAs(UnmanagedType.Bool)] bool fRemember); + return pv; + } - //[helpstring("Notifies the object that save is completed")] - void SaveCompleted( - [MarshalAs(UnmanagedType.LPWStr)] string pszFileName); + public static PropVariant FromGuid(Guid guid) + { + var bytes = guid.ToByteArray(); + var pv = new PropVariant() { + variantType = 72, // VT_CLSID + pointerValue = Marshal.AllocCoTaskMem(bytes.Length), + }; + Marshal.Copy(bytes, 0, pv.pointerValue, bytes.Length); - //[helpstring("Gets the current name of the file associated with the object")] - void GetCurFile( - [MarshalAs(UnmanagedType.LPWStr)] out string ppszFileName); + return pv; } - [StructLayout(LayoutKind.Sequential)] - public struct PropVariant - { - public short variantType; - public short Reserved1, Reserved2, Reserved3; - public IntPtr pointerValue; + /// + /// Called to properly clean up the memory referenced by a PropVariant instance. + /// + [DllImport("ole32.dll")] + private extern static int PropVariantClear(ref PropVariant pvar); - public static PropVariant FromString(string str) - { - var pv = new PropVariant() { - variantType = 31, // VT_LPWSTR - pointerValue = Marshal.StringToCoTaskMemUni(str), - }; + /// + /// Called to clear the PropVariant's referenced and local memory. + /// + /// + /// You must call Clear to avoid memory leaks. + /// + public void Clear() + { + // Can't pass "this" by ref, so make a copy to call PropVariantClear with + var tmp = this; + PropVariantClear(ref tmp); + + // Since we couldn't pass "this" by ref, we need to clear the member fields manually + // NOTE: PropVariantClear already freed heap data for us, so we are just setting + // our references to null. + variantType = (short)VarEnum.VT_EMPTY; + Reserved1 = Reserved2 = Reserved3 = 0; + pointerValue = IntPtr.Zero; + } + } - return pv; + [StructLayout(LayoutKind.Sequential)] + public struct PROPERTYKEY + { + public Guid fmtid; + public UIntPtr pid; + + public static PROPERTYKEY PKEY_AppUserModel_ID { + get { + return new() { + fmtid = Guid.ParseExact("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}", "B"), + pid = new UIntPtr(5), + }; } + } - public static PropVariant FromGuid(Guid guid) - { - var bytes = guid.ToByteArray(); - var pv = new PropVariant() { - variantType = 72, // VT_CLSID - pointerValue = Marshal.AllocCoTaskMem(bytes.Length), + public static PROPERTYKEY PKEY_AppUserModel_ToastActivatorCLSID { + get { + return new() { + fmtid = Guid.ParseExact("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}", "B"), + pid = new UIntPtr(26), }; - Marshal.Copy(bytes, 0, pv.pointerValue, bytes.Length); - - return pv; } + } + } - /// - /// Called to properly clean up the memory referenced by a PropVariant instance. - /// - [DllImport("ole32.dll")] - private extern static int PropVariantClear(ref PropVariant pvar); + [ComImport] + [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IPropertyStore + { + [PreserveSig] + int GetCount([Out] out uint cProps); + [PreserveSig] + int GetAt([In] uint iProp, out PROPERTYKEY pkey); + [PreserveSig] + int GetValue([In] ref PROPERTYKEY key, out PropVariant pv); + [PreserveSig] + int SetValue([In] ref PROPERTYKEY key, [In] ref PropVariant pv); + [PreserveSig] + int Commit(); + } - /// - /// Called to clear the PropVariant's referenced and local memory. - /// - /// - /// You must call Clear to avoid memory leaks. - /// - public void Clear() - { - // Can't pass "this" by ref, so make a copy to call PropVariantClear with - var tmp = this; - PropVariantClear(ref tmp); + [ComImport()] + [Guid("000214EE-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IShellLinkA + { + //[helpstring("Retrieves the path and filename of a shell link object")] + void GetPath( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszFile, + int cchMaxPath, + ref _WIN32_FIND_DATAA pfd, + uint fFlags); + + //[helpstring("Retrieves the list of shell link item identifiers")] + void GetIDList(out IntPtr ppidl); + + //[helpstring("Sets the list of shell link item identifiers")] + void SetIDList(IntPtr pidl); + + //[helpstring("Retrieves the shell link description string")] + void GetDescription( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszFile, + int cchMaxName); + + //[helpstring("Sets the shell link description string")] + void SetDescription( + [MarshalAs(UnmanagedType.LPStr)] string pszName); + + //[helpstring("Retrieves the name of the shell link working directory")] + void GetWorkingDirectory( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszDir, + int cchMaxPath); + + //[helpstring("Sets the name of the shell link working directory")] + void SetWorkingDirectory( + [MarshalAs(UnmanagedType.LPStr)] string pszDir); + + //[helpstring("Retrieves the shell link command-line arguments")] + void GetArguments( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszArgs, + int cchMaxPath); + + //[helpstring("Sets the shell link command-line arguments")] + void SetArguments( + [MarshalAs(UnmanagedType.LPStr)] string pszArgs); + + //[propget, helpstring("Retrieves or sets the shell link hot key")] + void GetHotkey(out short pwHotkey); + //[propput, helpstring("Retrieves or sets the shell link hot key")] + void SetHotkey(short pwHotkey); + + //[propget, helpstring("Retrieves or sets the shell link show command")] + void GetShowCmd(out uint piShowCmd); + //[propput, helpstring("Retrieves or sets the shell link show command")] + void SetShowCmd(uint piShowCmd); + + //[helpstring("Retrieves the location (path and index) of the shell link icon")] + void GetIconLocation( + [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszIconPath, + int cchIconPath, + out int piIcon); + + //[helpstring("Sets the location (path and index) of the shell link icon")] + void SetIconLocation( + [MarshalAs(UnmanagedType.LPStr)] string pszIconPath, + int iIcon); + + //[helpstring("Sets the shell link relative path")] + void SetRelativePath( + [MarshalAs(UnmanagedType.LPStr)] string pszPathRel, + uint dwReserved); + + //[helpstring("Resolves a shell link. The system searches for the shell link object and updates the shell link path and its list of identifiers (if necessary)")] + void Resolve( + IntPtr hWnd, + uint fFlags); - // Since we couldn't pass "this" by ref, we need to clear the member fields manually - // NOTE: PropVariantClear already freed heap data for us, so we are just setting - // our references to null. - variantType = (short)VarEnum.VT_EMPTY; - Reserved1 = Reserved2 = Reserved3 = 0; - pointerValue = IntPtr.Zero; - } - } + //[helpstring("Sets the shell link path and filename")] + void SetPath( + [MarshalAs(UnmanagedType.LPStr)] string pszFile); + } - [StructLayout(LayoutKind.Sequential)] - public struct PROPERTYKEY - { - public Guid fmtid; - public UIntPtr pid; + [ComImport()] + [Guid("000214F9-0000-0000-C000-000000000046")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + interface IShellLinkW + { + //[helpstring("Retrieves the path and filename of a shell link object")] + void GetPath( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, + int cchMaxPath, + ref _WIN32_FIND_DATAW pfd, + uint fFlags); + + //[helpstring("Retrieves the list of shell link item identifiers")] + void GetIDList(out IntPtr ppidl); + + //[helpstring("Sets the list of shell link item identifiers")] + void SetIDList(IntPtr pidl); + + //[helpstring("Retrieves the shell link description string")] + void GetDescription( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, + int cchMaxName); + + //[helpstring("Sets the shell link description string")] + void SetDescription( + [MarshalAs(UnmanagedType.LPWStr)] string pszName); + + //[helpstring("Retrieves the name of the shell link working directory")] + void GetWorkingDirectory( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, + int cchMaxPath); + + //[helpstring("Sets the name of the shell link working directory")] + void SetWorkingDirectory( + [MarshalAs(UnmanagedType.LPWStr)] string pszDir); + + //[helpstring("Retrieves the shell link command-line arguments")] + void GetArguments( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, + int cchMaxPath); + + //[helpstring("Sets the shell link command-line arguments")] + void SetArguments( + [MarshalAs(UnmanagedType.LPWStr)] string pszArgs); + + //[propget, helpstring("Retrieves or sets the shell link hot key")] + void GetHotkey(out short pwHotkey); + //[propput, helpstring("Retrieves or sets the shell link hot key")] + void SetHotkey(short pwHotkey); + + //[propget, helpstring("Retrieves or sets the shell link show command")] + void GetShowCmd(out uint piShowCmd); + //[propput, helpstring("Retrieves or sets the shell link show command")] + void SetShowCmd(uint piShowCmd); + + //[helpstring("Retrieves the location (path and index) of the shell link icon")] + void GetIconLocation( + [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, + int cchIconPath, + out int piIcon); + + //[helpstring("Sets the location (path and index) of the shell link icon")] + void SetIconLocation( + [MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, + int iIcon); + + //[helpstring("Sets the shell link relative path")] + void SetRelativePath( + [MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, + uint dwReserved); + + //[helpstring("Resolves a shell link. The system searches for the shell link object and updates the shell link path and its list of identifiers (if necessary)")] + void Resolve( + IntPtr hWnd, + uint fFlags); - public static PROPERTYKEY PKEY_AppUserModel_ID { - get { - return new() { - fmtid = Guid.ParseExact("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}", "B"), - pid = new UIntPtr(5), - }; - } - } + //[helpstring("Sets the shell link path and filename")] + void SetPath( + [MarshalAs(UnmanagedType.LPWStr)] string pszFile); + } - public static PROPERTYKEY PKEY_AppUserModel_ToastActivatorCLSID { - get { - return new() { - fmtid = Guid.ParseExact("{9F4C2855-9F79-4B39-A8D0-E1D42DE1D5F3}", "B"), - pid = new UIntPtr(26), - }; - } - } - } + [Guid("00021401-0000-0000-C000-000000000046")] + [ClassInterface(ClassInterfaceType.None)] + [ComImport()] + class CShellLink + { + } - [ComImport] - [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - interface IPropertyStore - { - [PreserveSig] - int GetCount([Out] out uint cProps); - [PreserveSig] - int GetAt([In] uint iProp, out PROPERTYKEY pkey); - [PreserveSig] - int GetValue([In] ref PROPERTYKEY key, out PropVariant pv); - [PreserveSig] - int SetValue([In] ref PROPERTYKEY key, [In] ref PropVariant pv); - [PreserveSig] - int Commit(); - } - - [ComImport()] - [Guid("000214EE-0000-0000-C000-000000000046")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - interface IShellLinkA - { - //[helpstring("Retrieves the path and filename of a shell link object")] - void GetPath( - [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszFile, - int cchMaxPath, - ref _WIN32_FIND_DATAA pfd, - uint fFlags); - - //[helpstring("Retrieves the list of shell link item identifiers")] - void GetIDList(out IntPtr ppidl); - - //[helpstring("Sets the list of shell link item identifiers")] - void SetIDList(IntPtr pidl); - - //[helpstring("Retrieves the shell link description string")] - void GetDescription( - [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszFile, - int cchMaxName); - - //[helpstring("Sets the shell link description string")] - void SetDescription( - [MarshalAs(UnmanagedType.LPStr)] string pszName); - - //[helpstring("Retrieves the name of the shell link working directory")] - void GetWorkingDirectory( - [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszDir, - int cchMaxPath); - - //[helpstring("Sets the name of the shell link working directory")] - void SetWorkingDirectory( - [MarshalAs(UnmanagedType.LPStr)] string pszDir); - - //[helpstring("Retrieves the shell link command-line arguments")] - void GetArguments( - [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszArgs, - int cchMaxPath); - - //[helpstring("Sets the shell link command-line arguments")] - void SetArguments( - [MarshalAs(UnmanagedType.LPStr)] string pszArgs); - - //[propget, helpstring("Retrieves or sets the shell link hot key")] - void GetHotkey(out short pwHotkey); - //[propput, helpstring("Retrieves or sets the shell link hot key")] - void SetHotkey(short pwHotkey); - - //[propget, helpstring("Retrieves or sets the shell link show command")] - void GetShowCmd(out uint piShowCmd); - //[propput, helpstring("Retrieves or sets the shell link show command")] - void SetShowCmd(uint piShowCmd); - - //[helpstring("Retrieves the location (path and index) of the shell link icon")] - void GetIconLocation( - [Out(), MarshalAs(UnmanagedType.LPStr)] StringBuilder pszIconPath, - int cchIconPath, - out int piIcon); - - //[helpstring("Sets the location (path and index) of the shell link icon")] - void SetIconLocation( - [MarshalAs(UnmanagedType.LPStr)] string pszIconPath, - int iIcon); - - //[helpstring("Sets the shell link relative path")] - void SetRelativePath( - [MarshalAs(UnmanagedType.LPStr)] string pszPathRel, - uint dwReserved); - - //[helpstring("Resolves a shell link. The system searches for the shell link object and updates the shell link path and its list of identifiers (if necessary)")] - void Resolve( - IntPtr hWnd, - uint fFlags); - - //[helpstring("Sets the shell link path and filename")] - void SetPath( - [MarshalAs(UnmanagedType.LPStr)] string pszFile); - } - - [ComImport()] - [Guid("000214F9-0000-0000-C000-000000000046")] - [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - interface IShellLinkW - { - //[helpstring("Retrieves the path and filename of a shell link object")] - void GetPath( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, - int cchMaxPath, - ref _WIN32_FIND_DATAW pfd, - uint fFlags); - - //[helpstring("Retrieves the list of shell link item identifiers")] - void GetIDList(out IntPtr ppidl); - - //[helpstring("Sets the list of shell link item identifiers")] - void SetIDList(IntPtr pidl); - - //[helpstring("Retrieves the shell link description string")] - void GetDescription( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, - int cchMaxName); - - //[helpstring("Sets the shell link description string")] - void SetDescription( - [MarshalAs(UnmanagedType.LPWStr)] string pszName); - - //[helpstring("Retrieves the name of the shell link working directory")] - void GetWorkingDirectory( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, - int cchMaxPath); - - //[helpstring("Sets the name of the shell link working directory")] - void SetWorkingDirectory( - [MarshalAs(UnmanagedType.LPWStr)] string pszDir); - - //[helpstring("Retrieves the shell link command-line arguments")] - void GetArguments( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, - int cchMaxPath); - - //[helpstring("Sets the shell link command-line arguments")] - void SetArguments( - [MarshalAs(UnmanagedType.LPWStr)] string pszArgs); - - //[propget, helpstring("Retrieves or sets the shell link hot key")] - void GetHotkey(out short pwHotkey); - //[propput, helpstring("Retrieves or sets the shell link hot key")] - void SetHotkey(short pwHotkey); - - //[propget, helpstring("Retrieves or sets the shell link show command")] - void GetShowCmd(out uint piShowCmd); - //[propput, helpstring("Retrieves or sets the shell link show command")] - void SetShowCmd(uint piShowCmd); - - //[helpstring("Retrieves the location (path and index) of the shell link icon")] - void GetIconLocation( - [Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, - int cchIconPath, - out int piIcon); - - //[helpstring("Sets the location (path and index) of the shell link icon")] - void SetIconLocation( - [MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, - int iIcon); - - //[helpstring("Sets the shell link relative path")] - void SetRelativePath( - [MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, - uint dwReserved); - - //[helpstring("Resolves a shell link. The system searches for the shell link object and updates the shell link path and its list of identifiers (if necessary)")] - void Resolve( - IntPtr hWnd, - uint fFlags); - - //[helpstring("Sets the shell link path and filename")] - void SetPath( - [MarshalAs(UnmanagedType.LPWStr)] string pszFile); - } - - [Guid("00021401-0000-0000-C000-000000000046")] - [ClassInterface(ClassInterfaceType.None)] - [ComImport()] - class CShellLink - { - } + enum EShellLinkGP : uint + { + SLGP_SHORTPATH = 1, + SLGP_UNCPRIORITY = 2 + } - enum EShellLinkGP : uint - { - SLGP_SHORTPATH = 1, - SLGP_UNCPRIORITY = 2 - } + [Flags] + enum EShowWindowFlags : uint + { + SW_HIDE = 0, + SW_SHOWNORMAL = 1, + SW_NORMAL = 1, + SW_SHOWMINIMIZED = 2, + SW_SHOWMAXIMIZED = 3, + SW_MAXIMIZE = 3, + SW_SHOWNOACTIVATE = 4, + SW_SHOW = 5, + SW_MINIMIZE = 6, + SW_SHOWMINNOACTIVE = 7, + SW_SHOWNA = 8, + SW_RESTORE = 9, + SW_SHOWDEFAULT = 10, + SW_MAX = 10 + } - [Flags] - enum EShowWindowFlags : uint - { - SW_HIDE = 0, - SW_SHOWNORMAL = 1, - SW_NORMAL = 1, - SW_SHOWMINIMIZED = 2, - SW_SHOWMAXIMIZED = 3, - SW_MAXIMIZE = 3, - SW_SHOWNOACTIVATE = 4, - SW_SHOW = 5, - SW_MINIMIZE = 6, - SW_SHOWMINNOACTIVE = 7, - SW_SHOWNA = 8, - SW_RESTORE = 9, - SW_SHOWDEFAULT = 10, - SW_MAX = 10 - } - - [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0, - CharSet = CharSet.Unicode)] - struct _WIN32_FIND_DATAW - { - public uint dwFileAttributes; - public _FILETIME ftCreationTime; - public _FILETIME ftLastAccessTime; - public _FILETIME ftLastWriteTime; - public uint nFileSizeHigh; - public uint nFileSizeLow; - public uint dwReserved0; - public uint dwReserved1; + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0, + CharSet = CharSet.Unicode)] + struct _WIN32_FIND_DATAW + { + public uint dwFileAttributes; + public _FILETIME ftCreationTime; + public _FILETIME ftLastAccessTime; + public _FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] // MAX_PATH + public string cFileName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] // MAX_PATH - public string cFileName; + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0, + CharSet = CharSet.Ansi)] + struct _WIN32_FIND_DATAA + { + public uint dwFileAttributes; + public _FILETIME ftCreationTime; + public _FILETIME ftLastAccessTime; + public _FILETIME ftLastWriteTime; + public uint nFileSizeHigh; + public uint nFileSizeLow; + public uint dwReserved0; + public uint dwReserved1; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] // MAX_PATH + public string cFileName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] + public string cAlternateFileName; + } - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] - public string cAlternateFileName; - } + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0)] + struct _FILETIME + { + public uint dwLowDateTime; + public uint dwHighDateTime; + } - [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0, - CharSet = CharSet.Ansi)] - struct _WIN32_FIND_DATAA - { - public uint dwFileAttributes; - public _FILETIME ftCreationTime; - public _FILETIME ftLastAccessTime; - public _FILETIME ftLastWriteTime; - public uint nFileSizeHigh; - public uint nFileSizeLow; - public uint dwReserved0; - public uint dwReserved1; + class UnManagedMethods + { + [DllImport("Shell32", CharSet = CharSet.Auto)] + internal static extern int ExtractIconEx( + [MarshalAs(UnmanagedType.LPWStr)] string lpszFile, + int nIconIndex, + IntPtr[] phIconLarge, + IntPtr[] phIconSmall, + int nIcons); + + [DllImport("user32")] + internal static extern int DestroyIcon(IntPtr hIcon); + } - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] // MAX_PATH - public string cFileName; + /// + /// Flags determining how the links with missing + /// targets are resolved. + /// + [Flags] + public enum EShellLinkResolveFlags : uint + { + /// + /// Allow any match during resolution. Has no effect + /// on ME/2000 or above, use the other flags instead. + /// + SLR_ANY_MATCH = 0x2, - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] - public string cAlternateFileName; - } + /// + /// Call the Microsoft Windows Installer. + /// + SLR_INVOKE_MSI = 0x80, - [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0)] - struct _FILETIME - { - public uint dwLowDateTime; - public uint dwHighDateTime; - } + /// + /// Disable distributed link tracking. By default, + /// distributed link tracking tracks removable media + /// across multiple devices based on the volume name. + /// It also uses the UNC path to track remote file + /// systems whose drive letter has changed. Setting + /// SLR_NOLINKINFO disables both types of tracking. + /// + SLR_NOLINKINFO = 0x40, - class UnManagedMethods - { - [DllImport("Shell32", CharSet = CharSet.Auto)] - internal static extern int ExtractIconEx( - [MarshalAs(UnmanagedType.LPWStr)] string lpszFile, - int nIconIndex, - IntPtr[] phIconLarge, - IntPtr[] phIconSmall, - int nIcons); + /// + /// Do not display a dialog box if the link cannot be resolved. + /// When SLR_NO_UI is set, a time-out value that specifies the + /// maximum amount of time to be spent resolving the link can + /// be specified in milliseconds. The function returns if the + /// link cannot be resolved within the time-out duration. + /// If the timeout is not set, the time-out duration will be + /// set to the default value of 3,000 milliseconds (3 seconds). + /// + SLR_NO_UI = 0x1, - [DllImport("user32")] - internal static extern int DestroyIcon(IntPtr hIcon); - } + /// + /// Not documented in SDK. Assume same as SLR_NO_UI but + /// intended for applications without a hWnd. + /// + SLR_NO_UI_WITH_MSG_PUMP = 0x101, /// - /// Flags determining how the links with missing - /// targets are resolved. + /// Do not update the link information. /// - [Flags] - public enum EShellLinkResolveFlags : uint - { - /// - /// Allow any match during resolution. Has no effect - /// on ME/2000 or above, use the other flags instead. - /// - SLR_ANY_MATCH = 0x2, - - /// - /// Call the Microsoft Windows Installer. - /// - SLR_INVOKE_MSI = 0x80, - - /// - /// Disable distributed link tracking. By default, - /// distributed link tracking tracks removable media - /// across multiple devices based on the volume name. - /// It also uses the UNC path to track remote file - /// systems whose drive letter has changed. Setting - /// SLR_NOLINKINFO disables both types of tracking. - /// - SLR_NOLINKINFO = 0x40, - - /// - /// Do not display a dialog box if the link cannot be resolved. - /// When SLR_NO_UI is set, a time-out value that specifies the - /// maximum amount of time to be spent resolving the link can - /// be specified in milliseconds. The function returns if the - /// link cannot be resolved within the time-out duration. - /// If the timeout is not set, the time-out duration will be - /// set to the default value of 3,000 milliseconds (3 seconds). - /// - SLR_NO_UI = 0x1, - - /// - /// Not documented in SDK. Assume same as SLR_NO_UI but - /// intended for applications without a hWnd. - /// - SLR_NO_UI_WITH_MSG_PUMP = 0x101, - - /// - /// Do not update the link information. - /// - SLR_NOUPDATE = 0x8, - - /// - /// Do not execute the search heuristics. - /// - SLR_NOSEARCH = 0x10, - - /// - /// Do not use distributed link tracking. - /// - SLR_NOTRACK = 0x20, - - /// - /// If the link object has changed, update its path and list - /// of identifiers. If SLR_UPDATE is set, you do not need to - /// call IPersistFile::IsDirty to determine whether or not - /// the link object has changed. - /// - SLR_UPDATE = 0x4 - } - - public enum LinkDisplayMode : uint - { - edmNormal = EShowWindowFlags.SW_NORMAL, - edmMinimized = EShowWindowFlags.SW_SHOWMINNOACTIVE, - edmMaximized = EShowWindowFlags.SW_MAXIMIZE - } + SLR_NOUPDATE = 0x8, - // Use Unicode (W) under NT, otherwise use ANSI - IShellLinkW linkW; - IShellLinkA linkA; - string shortcutFile = ""; + /// + /// Do not execute the search heuristics. + /// + SLR_NOSEARCH = 0x10, /// - /// Creates an instance of the Shell Link object. + /// Do not use distributed link tracking. /// - public ShellLink() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - linkW = (IShellLinkW)new CShellLink(); - } - else - { - linkA = (IShellLinkA)new CShellLink(); - } - } + SLR_NOTRACK = 0x20, /// - /// Creates an instance of a Shell Link object - /// from the specified link file + /// If the link object has changed, update its path and list + /// of identifiers. If SLR_UPDATE is set, you do not need to + /// call IPersistFile::IsDirty to determine whether or not + /// the link object has changed. /// - /// The Shortcut file to open - public ShellLink(string linkFile) : this() + SLR_UPDATE = 0x4 + } + + public enum LinkDisplayMode : uint + { + edmNormal = EShowWindowFlags.SW_NORMAL, + edmMinimized = EShowWindowFlags.SW_SHOWMINNOACTIVE, + edmMaximized = EShowWindowFlags.SW_MAXIMIZE + } + + // Use Unicode (W) under NT, otherwise use ANSI + IShellLinkW linkW; + IShellLinkA linkA; + string shortcutFile = ""; + + /// + /// Creates an instance of the Shell Link object. + /// + public ShellLink() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - Open(linkFile); + linkW = (IShellLinkW)new CShellLink(); } - - /// - /// Call dispose just in case it hasn't happened yet - /// - ~ShellLink() + else { - Dispose(); + linkA = (IShellLinkA)new CShellLink(); } + } - /// - /// Dispose the object, releasing the COM ShellLink object - /// - public void Dispose() + /// + /// Creates an instance of a Shell Link object + /// from the specified link file + /// + /// The Shortcut file to open + public ShellLink(string linkFile) : this() + { + Open(linkFile); + } + + /// + /// Call dispose just in case it hasn't happened yet + /// + ~ShellLink() + { + Dispose(); + } + + /// + /// Dispose the object, releasing the COM ShellLink object + /// + public void Dispose() + { + if (linkW != null) { - if (linkW != null) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Marshal.ReleaseComObject(linkW); - linkW = null; - } + Marshal.ReleaseComObject(linkW); + linkW = null; } - if (linkA != null) + } + if (linkA != null) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - Marshal.ReleaseComObject(linkA); - } - linkA = null; + Marshal.ReleaseComObject(linkA); } + linkA = null; } + } - public string ShortCutFile - { - get { return this.shortcutFile; } - set { this.shortcutFile = value; } - } + public string ShortCutFile + { + get { return this.shortcutFile; } + set { this.shortcutFile = value; } + } - /// - /// Gets the path to the file containing the icon for this shortcut. - /// - public string IconPath + /// + /// Gets the path to the file containing the icon for this shortcut. + /// + public string IconPath + { + get { - get - { - var iconPath = new StringBuilder(260, 260); - var iconIndex = 0; - if (linkA == null) - { - linkW.GetIconLocation(iconPath, iconPath.Capacity, out - iconIndex); - } - else - { - linkA.GetIconLocation(iconPath, iconPath.Capacity, out - iconIndex); - } - return iconPath.ToString(); - } - set - { - var iconPath = new StringBuilder(260, 260); - var iconIndex = 0; - if (linkA == null) - { - linkW.GetIconLocation(iconPath, iconPath.Capacity, out - iconIndex); - } - else - { - linkA.GetIconLocation(iconPath, iconPath.Capacity, out - iconIndex); - } - if (linkA == null) - { - linkW.SetIconLocation(value, iconIndex); - } - else - { - linkA.SetIconLocation(value, iconIndex); - } + var iconPath = new StringBuilder(260, 260); + var iconIndex = 0; + if (linkA == null) + { + linkW.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); } - } - - /// - /// Gets the index of this icon within the icon path's resources - /// - public int IconIndex - { - get - { - var iconPath = new StringBuilder(260, 260); - var iconIndex = 0; - if (linkA == null) - { - linkW.GetIconLocation(iconPath, iconPath.Capacity, out - iconIndex); - } - else - { - linkA.GetIconLocation(iconPath, iconPath.Capacity, out - iconIndex); - } - return iconIndex; - } - set - { - var iconPath = new StringBuilder(260, 260); - var iconIndex = 0; - if (linkA == null) - { - linkW.GetIconLocation(iconPath, iconPath.Capacity, out - iconIndex); - } - else - { - linkA.GetIconLocation(iconPath, iconPath.Capacity, out - iconIndex); - } - if (linkA == null) - { - linkW.SetIconLocation(iconPath.ToString(), value); - } - else - { - linkA.SetIconLocation(iconPath.ToString(), value); - } + else + { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); } + return iconPath.ToString(); } - - /// - /// Gets/sets the fully qualified path to the link's target - /// - public string Target + set { - get - { - var target = new StringBuilder(260, 260); - if (linkA == null) - { - var fd = new _WIN32_FIND_DATAW(); - linkW.GetPath(target, target.Capacity, ref fd, - (uint)EShellLinkGP.SLGP_UNCPRIORITY); - } - else - { - var fd = new _WIN32_FIND_DATAA(); - linkA.GetPath(target, target.Capacity, ref fd, - (uint)EShellLinkGP.SLGP_UNCPRIORITY); - } - return target.ToString(); - } - set - { - if (linkA == null) - { - linkW.SetPath(value); - } - else - { - linkA.SetPath(value); - } + var iconPath = new StringBuilder(260, 260); + var iconIndex = 0; + if (linkA == null) + { + linkW.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + else + { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + if (linkA == null) + { + linkW.SetIconLocation(value, iconIndex); + } + else + { + linkA.SetIconLocation(value, iconIndex); } } + } - /// - /// Gets/sets the Working Directory for the Link - /// - public string WorkingDirectory + /// + /// Gets the index of this icon within the icon path's resources + /// + public int IconIndex + { + get { - get + var iconPath = new StringBuilder(260, 260); + var iconIndex = 0; + if (linkA == null) { - var path = new StringBuilder(260, 260); - if (linkA == null) - { - linkW.GetWorkingDirectory(path, path.Capacity); - } - else - { - linkA.GetWorkingDirectory(path, path.Capacity); - } - return path.ToString(); + linkW.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); } - set + else { - if (linkA == null) - { - linkW.SetWorkingDirectory(value); - } - else - { - linkA.SetWorkingDirectory(value); - } + linkA.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); } + return iconIndex; } - - /// - /// Gets/sets the description of the link - /// - public string Description + set { - get + var iconPath = new StringBuilder(260, 260); + var iconIndex = 0; + if (linkA == null) { - var description = new StringBuilder(1024, 1024); - if (linkA == null) - { - linkW.GetDescription(description, description.Capacity); - } - else - { - linkA.GetDescription(description, description.Capacity); - } - return description.ToString(); + linkW.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); } - set + else + { + linkA.GetIconLocation(iconPath, iconPath.Capacity, out + iconIndex); + } + if (linkA == null) + { + linkW.SetIconLocation(iconPath.ToString(), value); + } + else { - if (linkA == null) - { - linkW.SetDescription(value); - } - else - { - linkA.SetDescription(value); - } + linkA.SetIconLocation(iconPath.ToString(), value); } } + } - /// - /// Gets/sets any command line arguments associated with the link - /// - public string Arguments + /// + /// Gets/sets the fully qualified path to the link's target + /// + public string Target + { + get { - get + var target = new StringBuilder(260, 260); + if (linkA == null) { - var arguments = new StringBuilder(260, 260); - if (linkA == null) - { - linkW.GetArguments(arguments, arguments.Capacity); - } - else - { - linkA.GetArguments(arguments, arguments.Capacity); - } - return arguments.ToString(); + var fd = new _WIN32_FIND_DATAW(); + linkW.GetPath(target, target.Capacity, ref fd, + (uint)EShellLinkGP.SLGP_UNCPRIORITY); } - set + else { - if (linkA == null) - { - linkW.SetArguments(value); - } - else - { - linkA.SetArguments(value); - } + var fd = new _WIN32_FIND_DATAA(); + linkA.GetPath(target, target.Capacity, ref fd, + (uint)EShellLinkGP.SLGP_UNCPRIORITY); } + return target.ToString(); } - - /// - /// Gets/sets the initial display mode when the shortcut is - /// run - /// - public LinkDisplayMode DisplayMode + set { - get + if (linkA == null) { - uint cmd = 0; - if (linkA == null) - { - linkW.GetShowCmd(out cmd); - } - else - { - linkA.GetShowCmd(out cmd); - } - return (LinkDisplayMode)cmd; + linkW.SetPath(value); } - set + else { - if (linkA == null) - { - linkW.SetShowCmd((uint)value); - } - else - { - linkA.SetShowCmd((uint)value); - } + linkA.SetPath(value); } } + } - /// - /// Gets/sets the HotKey to start the shortcut (if any) - /// - public short HotKey + /// + /// Gets/sets the Working Directory for the Link + /// + public string WorkingDirectory + { + get { - get + var path = new StringBuilder(260, 260); + if (linkA == null) { - short key = 0; - if (linkA == null) - { - linkW.GetHotkey(out key); - } - else - { - linkA.GetHotkey(out key); - } - return key; + linkW.GetWorkingDirectory(path, path.Capacity); } - set + else { - if (linkA == null) - { - linkW.SetHotkey(value); - } - else - { - linkA.SetHotkey(value); - } + linkA.GetWorkingDirectory(path, path.Capacity); } + return path.ToString(); } - - /// - /// Sets the appUserModelId - /// - public void SetAppUserModelId(string appId) + set { - var propStore = (IPropertyStore)linkW; - var pkey = PROPERTYKEY.PKEY_AppUserModel_ID; - var str = PropVariant.FromString (appId); - propStore.SetValue(ref pkey, ref str); + if (linkA == null) + { + linkW.SetWorkingDirectory(value); + } + else + { + linkA.SetWorkingDirectory(value); + } } + } - /// - /// Sets the ToastActivatorCLSID - /// - public void SetToastActivatorCLSID(string clsid) + /// + /// Gets/sets the description of the link + /// + public string Description + { + get { - var guid = Guid.Parse(clsid); - SetToastActivatorCLSID(guid); + var description = new StringBuilder(1024, 1024); + if (linkA == null) + { + linkW.GetDescription(description, description.Capacity); + } + else + { + linkA.GetDescription(description, description.Capacity); + } + return description.ToString(); } - - /// - /// Sets the ToastActivatorCLSID - /// - public void SetToastActivatorCLSID(Guid clsid) + set { - var propStore = (IPropertyStore)linkW; - - var pkey = PROPERTYKEY.PKEY_AppUserModel_ToastActivatorCLSID; - - var varGuid = PropVariant.FromGuid(clsid); - try { - var errCode = propStore.SetValue(ref pkey, ref varGuid); - Marshal.ThrowExceptionForHR(errCode); - - errCode = propStore.Commit(); - Marshal.ThrowExceptionForHR(errCode); - } finally { - varGuid.Clear(); + if (linkA == null) + { + linkW.SetDescription(value); + } + else + { + linkA.SetDescription(value); } } + } - /// - /// Saves the shortcut to ShortCutFile. - /// - public void Save() + /// + /// Gets/sets any command line arguments associated with the link + /// + public string Arguments + { + get { - Save(shortcutFile); + var arguments = new StringBuilder(260, 260); + if (linkA == null) + { + linkW.GetArguments(arguments, arguments.Capacity); + } + else + { + linkA.GetArguments(arguments, arguments.Capacity); + } + return arguments.ToString(); } - - /// - /// Saves the shortcut to the specified file - /// - /// The shortcut file (.lnk) - public void Save( - string linkFile - ) + set { - // Save the object to disk if (linkA == null) { - ((IPersistFile)linkW).Save(linkFile, true); - shortcutFile = linkFile; + linkW.SetArguments(value); } else { - ((IPersistFile)linkA).Save(linkFile, true); - shortcutFile = linkFile; + linkA.SetArguments(value); } } + } - /// - /// Loads a shortcut from the specified file - /// - /// The shortcut file (.lnk) to load - public void Open( - string linkFile - ) + /// + /// Gets/sets the initial display mode when the shortcut is + /// run + /// + public LinkDisplayMode DisplayMode + { + get { - Open(linkFile, - IntPtr.Zero, - (EShellLinkResolveFlags.SLR_ANY_MATCH | - EShellLinkResolveFlags.SLR_NO_UI), - 1); + uint cmd = 0; + if (linkA == null) + { + linkW.GetShowCmd(out cmd); + } + else + { + linkA.GetShowCmd(out cmd); + } + return (LinkDisplayMode)cmd; } - - /// - /// Loads a shortcut from the specified file, and allows flags controlling - /// the UI behaviour if the shortcut's target isn't found to be set. - /// - /// The shortcut file (.lnk) to load - /// The window handle of the application's UI, if any - /// Flags controlling resolution behaviour - public void Open( - string linkFile, - IntPtr hWnd, - EShellLinkResolveFlags resolveFlags - ) + set { - Open(linkFile, - hWnd, - resolveFlags, - 1); + if (linkA == null) + { + linkW.SetShowCmd((uint)value); + } + else + { + linkA.SetShowCmd((uint)value); + } } + } - /// - /// Loads a shortcut from the specified file, and allows flags controlling - /// the UI behaviour if the shortcut's target isn't found to be set. If - /// no SLR_NO_UI is specified, you can also specify a timeout. - /// - /// The shortcut file (.lnk) to load - /// The window handle of the application's UI, if any - /// Flags controlling resolution behaviour - /// Timeout if SLR_NO_UI is specified, in ms. - public void Open( - string linkFile, - IntPtr hWnd, - EShellLinkResolveFlags resolveFlags, - ushort timeOut - ) + /// + /// Gets/sets the HotKey to start the shortcut (if any) + /// + public short HotKey + { + get { - uint flags; - - if ((resolveFlags & EShellLinkResolveFlags.SLR_NO_UI) - == EShellLinkResolveFlags.SLR_NO_UI) + short key = 0; + if (linkA == null) { - flags = (uint)((int)resolveFlags | (timeOut << 16)); + linkW.GetHotkey(out key); } else { - flags = (uint)resolveFlags; + linkA.GetHotkey(out key); } - + return key; + } + set + { if (linkA == null) { - ((IPersistFile)linkW).Load(linkFile, 0); //STGM_DIRECT) - linkW.Resolve(hWnd, flags); - this.shortcutFile = linkFile; + linkW.SetHotkey(value); } else { - ((IPersistFile)linkA).Load(linkFile, 0); //STGM_DIRECT) - linkA.Resolve(hWnd, flags); - this.shortcutFile = linkFile; + linkA.SetHotkey(value); } } } + + /// + /// Sets the appUserModelId + /// + public void SetAppUserModelId(string appId) + { + var propStore = (IPropertyStore)linkW; + var pkey = PROPERTYKEY.PKEY_AppUserModel_ID; + var str = PropVariant.FromString (appId); + propStore.SetValue(ref pkey, ref str); + } + + /// + /// Sets the ToastActivatorCLSID + /// + public void SetToastActivatorCLSID(string clsid) + { + var guid = Guid.Parse(clsid); + SetToastActivatorCLSID(guid); + } + + /// + /// Sets the ToastActivatorCLSID + /// + public void SetToastActivatorCLSID(Guid clsid) + { + var propStore = (IPropertyStore)linkW; + + var pkey = PROPERTYKEY.PKEY_AppUserModel_ToastActivatorCLSID; + + var varGuid = PropVariant.FromGuid(clsid); + try { + var errCode = propStore.SetValue(ref pkey, ref varGuid); + Marshal.ThrowExceptionForHR(errCode); + + errCode = propStore.Commit(); + Marshal.ThrowExceptionForHR(errCode); + } finally { + varGuid.Clear(); + } + } + + /// + /// Saves the shortcut to ShortCutFile. + /// + public void Save() + { + Save(shortcutFile); + } + + /// + /// Saves the shortcut to the specified file + /// + /// The shortcut file (.lnk) + public void Save( + string linkFile + ) + { + // Save the object to disk + if (linkA == null) + { + ((IPersistFile)linkW).Save(linkFile, true); + shortcutFile = linkFile; + } + else + { + ((IPersistFile)linkA).Save(linkFile, true); + shortcutFile = linkFile; + } + } + + /// + /// Loads a shortcut from the specified file + /// + /// The shortcut file (.lnk) to load + public void Open( + string linkFile + ) + { + Open(linkFile, + IntPtr.Zero, + (EShellLinkResolveFlags.SLR_ANY_MATCH | + EShellLinkResolveFlags.SLR_NO_UI), + 1); + } + + /// + /// Loads a shortcut from the specified file, and allows flags controlling + /// the UI behaviour if the shortcut's target isn't found to be set. + /// + /// The shortcut file (.lnk) to load + /// The window handle of the application's UI, if any + /// Flags controlling resolution behaviour + public void Open( + string linkFile, + IntPtr hWnd, + EShellLinkResolveFlags resolveFlags + ) + { + Open(linkFile, + hWnd, + resolveFlags, + 1); + } + + /// + /// Loads a shortcut from the specified file, and allows flags controlling + /// the UI behaviour if the shortcut's target isn't found to be set. If + /// no SLR_NO_UI is specified, you can also specify a timeout. + /// + /// The shortcut file (.lnk) to load + /// The window handle of the application's UI, if any + /// Flags controlling resolution behaviour + /// Timeout if SLR_NO_UI is specified, in ms. + public void Open( + string linkFile, + IntPtr hWnd, + EShellLinkResolveFlags resolveFlags, + ushort timeOut + ) + { + uint flags; + + if ((resolveFlags & EShellLinkResolveFlags.SLR_NO_UI) + == EShellLinkResolveFlags.SLR_NO_UI) + { + flags = (uint)((int)resolveFlags | (timeOut << 16)); + } + else + { + flags = (uint)resolveFlags; + } + + if (linkA == null) + { + ((IPersistFile)linkW).Load(linkFile, 0); //STGM_DIRECT) + linkW.Resolve(hWnd, flags); + this.shortcutFile = linkFile; + } + else + { + ((IPersistFile)linkA).Load(linkFile, 0); //STGM_DIRECT) + linkA.Resolve(hWnd, flags); + this.shortcutFile = linkFile; + } + } } // Resharper enable all diff --git a/src/Snap/AnyOS/Windows/SnapOS.Windows.cs b/src/Snap/AnyOS/Windows/SnapOS.Windows.cs index 10c5acb9..acd19881 100644 --- a/src/Snap/AnyOS/Windows/SnapOS.Windows.cs +++ b/src/Snap/AnyOS/Windows/SnapOS.Windows.cs @@ -16,400 +16,399 @@ using Snap.Extensions; using Snap.Logging; -namespace Snap.AnyOS.Windows +namespace Snap.AnyOS.Windows; + +// https://stackoverflow.com/a/32716784/2470592 +internal class SnapOsWindowsExitSignal : ISnapOsExitSignal { - // https://stackoverflow.com/a/32716784/2470592 - internal class SnapOsWindowsExitSignal : ISnapOsExitSignal - { - public event EventHandler Exit; + public event EventHandler Exit; - [DllImport("kernel32.dll")] - static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add); + [DllImport("kernel32.dll")] + static extern bool SetConsoleCtrlHandler(HandlerRoutine handler, bool add); - // A delegate type to be used as the handler routine - // for SetConsoleCtrlHandler. - [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - public delegate bool HandlerRoutine(CtrlTypes ctrlType); + // A delegate type to be used as the handler routine + // for SetConsoleCtrlHandler. + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] + public delegate bool HandlerRoutine(CtrlTypes ctrlType); - // An enumerated type for the control messages - // sent to the handler routine. - public enum CtrlTypes - { - CtrlCEvent = 0, - CtrlBreakEvent, - CtrlCloseEvent, - CtrlLogoffEvent = 5, - CtrlShutdownEvent - } + // An enumerated type for the control messages + // sent to the handler routine. + public enum CtrlTypes + { + CtrlCEvent = 0, + CtrlBreakEvent, + CtrlCloseEvent, + CtrlLogoffEvent = 5, + CtrlShutdownEvent + } - readonly HandlerRoutine _handlerRoutine; + readonly HandlerRoutine _handlerRoutine; - public SnapOsWindowsExitSignal() - { - _handlerRoutine = ConsoleCtrlCheck; + public SnapOsWindowsExitSignal() + { + _handlerRoutine = ConsoleCtrlCheck; - SetConsoleCtrlHandler(_handlerRoutine, true); + SetConsoleCtrlHandler(_handlerRoutine, true); - } + } - /// - /// Handle the ctrl types - /// - /// - /// - bool ConsoleCtrlCheck(CtrlTypes ctrlType) + /// + /// Handle the ctrl types + /// + /// + /// + bool ConsoleCtrlCheck(CtrlTypes ctrlType) + { + switch (ctrlType) { - switch (ctrlType) - { - case CtrlTypes.CtrlCEvent: - case CtrlTypes.CtrlBreakEvent: - case CtrlTypes.CtrlCloseEvent: - case CtrlTypes.CtrlLogoffEvent: - case CtrlTypes.CtrlShutdownEvent: - Exit?.Invoke(this, EventArgs.Empty); - break; - } - return true; + case CtrlTypes.CtrlCEvent: + case CtrlTypes.CtrlBreakEvent: + case CtrlTypes.CtrlCloseEvent: + case CtrlTypes.CtrlLogoffEvent: + case CtrlTypes.CtrlShutdownEvent: + Exit?.Invoke(this, EventArgs.Empty); + break; } - + return true; } - internal interface ISnapOsWindows : ISnapOsImpl - { +} - } +internal interface ISnapOsWindows : ISnapOsImpl +{ + +} + +internal sealed class SnapOsShortcutDescription +{ + public SnapApp SnapApp { get; set; } + public NuspecReader NuspecReader { get; set; } + public string ExeAbsolutePath { get; set; } + public string ExeProgramArguments { get; set; } + public string IconAbsolutePath { get; set; } + public SnapShortcutLocation ShortcutLocations { get; set; } + public bool UpdateOnly { get; set; } +} + +internal sealed class SnapOsWindows : ISnapOsWindows +{ + static long _consoleCreated; + + readonly bool _isUnitTest; - internal sealed class SnapOsShortcutDescription + public ISnapOsTaskbar Taskbar => throw new PlatformNotSupportedException("Todo: Implement taskbar progressbar"); + public OSPlatform OsPlatform => OSPlatform.Windows; + public ISnapFilesystem Filesystem { get; } + public ISnapOsProcessManager OsProcessManager { get; } + public SnapOsDistroType DistroType => SnapOsDistroType.Windows; + public ISnapOsSpecialFolders SpecialFolders { get; } + + public SnapOsWindows(ISnapFilesystem snapFilesystem, [JetBrains.Annotations.NotNull] ISnapOsProcessManager snapOsProcessManager, + [JetBrains.Annotations.NotNull] ISnapOsSpecialFolders snapOsSpecialFolders, bool isUnitTest = false) { - public SnapApp SnapApp { get; set; } - public NuspecReader NuspecReader { get; set; } - public string ExeAbsolutePath { get; set; } - public string ExeProgramArguments { get; set; } - public string IconAbsolutePath { get; set; } - public SnapShortcutLocation ShortcutLocations { get; set; } - public bool UpdateOnly { get; set; } + Filesystem = snapFilesystem ?? throw new ArgumentNullException(nameof(snapFilesystem)); + OsProcessManager = snapOsProcessManager ?? throw new ArgumentNullException(nameof(snapOsProcessManager)); + SpecialFolders = snapOsSpecialFolders ?? throw new ArgumentNullException(nameof(snapOsSpecialFolders)); + _isUnitTest = isUnitTest; } - internal sealed class SnapOsWindows : ISnapOsWindows + public Task CreateShortcutsForExecutableAsync(SnapOsShortcutDescription shortcutDescription, ILog logger = null, CancellationToken cancellationToken = default) { - static long _consoleCreated; - - readonly bool _isUnitTest; + if (shortcutDescription == null) throw new ArgumentNullException(nameof(shortcutDescription)); - public ISnapOsTaskbar Taskbar => throw new PlatformNotSupportedException("Todo: Implement taskbar progressbar"); - public OSPlatform OsPlatform => OSPlatform.Windows; - public ISnapFilesystem Filesystem { get; } - public ISnapOsProcessManager OsProcessManager { get; } - public SnapOsDistroType DistroType => SnapOsDistroType.Windows; - public ISnapOsSpecialFolders SpecialFolders { get; } + var baseDirectory = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath); + var exeName = Filesystem.PathGetFileName(shortcutDescription.ExeAbsolutePath); + var packageTitle = shortcutDescription.NuspecReader.GetTitle(); + var authors = shortcutDescription.NuspecReader.GetAuthors(); + var packageId = shortcutDescription.NuspecReader.GetIdentity().Id; + var packageVersion = shortcutDescription.NuspecReader.GetIdentity().Version; + var packageDescription = shortcutDescription.NuspecReader.GetDescription(); - public SnapOsWindows(ISnapFilesystem snapFilesystem, [JetBrains.Annotations.NotNull] ISnapOsProcessManager snapOsProcessManager, - [JetBrains.Annotations.NotNull] ISnapOsSpecialFolders snapOsSpecialFolders, bool isUnitTest = false) + string LinkTargetForVersionInfo(SnapShortcutLocation location, FileVersionInfo versionInfo) { - Filesystem = snapFilesystem ?? throw new ArgumentNullException(nameof(snapFilesystem)); - OsProcessManager = snapOsProcessManager ?? throw new ArgumentNullException(nameof(snapOsProcessManager)); - SpecialFolders = snapOsSpecialFolders ?? throw new ArgumentNullException(nameof(snapOsSpecialFolders)); - _isUnitTest = isUnitTest; + var possibleProductNames = new[] { + versionInfo.ProductName, + packageTitle, + versionInfo.FileDescription, + Filesystem.PathGetFileNameWithoutExtension(versionInfo.FileName) + }; + + var possibleCompanyNames = new[] { + versionInfo.CompanyName, + authors ?? packageId + }; + + var productName = possibleCompanyNames.First(x => !string.IsNullOrWhiteSpace(x)); + var packageName = possibleProductNames.First(x => !string.IsNullOrWhiteSpace(x)); + + return GetLinkTarget(location, packageName, productName); } - public Task CreateShortcutsForExecutableAsync(SnapOsShortcutDescription shortcutDescription, ILog logger = null, CancellationToken cancellationToken = default) + string GetLinkTarget(SnapShortcutLocation location, string title, string applicationName, bool createDirectoryIfNecessary = true) { - if (shortcutDescription == null) throw new ArgumentNullException(nameof(shortcutDescription)); - - var baseDirectory = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath); - var exeName = Filesystem.PathGetFileName(shortcutDescription.ExeAbsolutePath); - var packageTitle = shortcutDescription.NuspecReader.GetTitle(); - var authors = shortcutDescription.NuspecReader.GetAuthors(); - var packageId = shortcutDescription.NuspecReader.GetIdentity().Id; - var packageVersion = shortcutDescription.NuspecReader.GetIdentity().Version; - var packageDescription = shortcutDescription.NuspecReader.GetDescription(); - - string LinkTargetForVersionInfo(SnapShortcutLocation location, FileVersionInfo versionInfo) + var targetDirectory = location switch { - var possibleProductNames = new[] { - versionInfo.ProductName, - packageTitle, - versionInfo.FileDescription, - Filesystem.PathGetFileNameWithoutExtension(versionInfo.FileName) - }; + SnapShortcutLocation.Desktop => SpecialFolders.DesktopDirectory, + SnapShortcutLocation.StartMenu => Filesystem.PathCombine(SpecialFolders.StartMenu, "Programs", + applicationName), + SnapShortcutLocation.Startup => SpecialFolders.StartupDirectory, + _ => throw new ArgumentOutOfRangeException(nameof(location), location, null) + }; + + if (createDirectoryIfNecessary) + { + Filesystem.DirectoryCreateIfNotExists(targetDirectory); + } - var possibleCompanyNames = new[] { - versionInfo.CompanyName, - authors ?? packageId - }; + return Filesystem.PathCombine(targetDirectory, title + ".lnk"); + } - var productName = possibleCompanyNames.First(x => !string.IsNullOrWhiteSpace(x)); - var packageName = possibleProductNames.First(x => !string.IsNullOrWhiteSpace(x)); + logger?.Info($"About to create shortcuts for {exeName}, base directory {baseDirectory}"); - return GetLinkTarget(location, packageName, productName); - } + var fileVerInfo = FileVersionInfo.GetVersionInfo(shortcutDescription.ExeAbsolutePath); - string GetLinkTarget(SnapShortcutLocation location, string title, string applicationName, bool createDirectoryIfNecessary = true) + foreach (var flag in (SnapShortcutLocation[])Enum.GetValues(typeof(SnapShortcutLocation))) + { + if (!shortcutDescription.ShortcutLocations.HasFlag(flag)) { - var targetDirectory = location switch - { - SnapShortcutLocation.Desktop => SpecialFolders.DesktopDirectory, - SnapShortcutLocation.StartMenu => Filesystem.PathCombine(SpecialFolders.StartMenu, "Programs", - applicationName), - SnapShortcutLocation.Startup => SpecialFolders.StartupDirectory, - _ => throw new ArgumentOutOfRangeException(nameof(location), location, null) - }; + continue; + } - if (createDirectoryIfNecessary) - { - Filesystem.DirectoryCreateIfNotExists(targetDirectory); - } + var file = LinkTargetForVersionInfo(flag, fileVerInfo); + var fileExists = Filesystem.FileExists(file); - return Filesystem.PathCombine(targetDirectory, title + ".lnk"); + // NB: If we've already installed the app, but the shortcut + // is no longer there, we have to assume that the user didn't + // want it there and explicitly deleted it, so we shouldn't + // annoy them by recreating it. + if (!fileExists && shortcutDescription.UpdateOnly) + { + logger?.Warn($"Wanted to update shortcut {file} but it appears user deleted it"); + continue; } - logger?.Info($"About to create shortcuts for {exeName}, base directory {baseDirectory}"); - - var fileVerInfo = FileVersionInfo.GetVersionInfo(shortcutDescription.ExeAbsolutePath); + logger?.Info($"Creating shortcut for {exeName} => {file}"); - foreach (var flag in (SnapShortcutLocation[])Enum.GetValues(typeof(SnapShortcutLocation))) + ShellLink shellLink; + logger?.ErrorIfThrows(() => SnapUtility.Retry(() => { - if (!shortcutDescription.ShortcutLocations.HasFlag(flag)) - { - continue; - } + Filesystem.FileDelete(file); - var file = LinkTargetForVersionInfo(flag, fileVerInfo); - var fileExists = Filesystem.FileExists(file); + shellLink = new ShellLink + { + Target = shortcutDescription.ExeAbsolutePath, + IconPath = shortcutDescription.IconAbsolutePath ?? shortcutDescription.ExeAbsolutePath, + IconIndex = 0, + WorkingDirectory = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath), + Description = packageDescription + }; - // NB: If we've already installed the app, but the shortcut - // is no longer there, we have to assume that the user didn't - // want it there and explicitly deleted it, so we shouldn't - // annoy them by recreating it. - if (!fileExists && shortcutDescription.UpdateOnly) + if (!string.IsNullOrWhiteSpace(shortcutDescription.ExeProgramArguments)) { - logger?.Warn($"Wanted to update shortcut {file} but it appears user deleted it"); - continue; + shellLink.Arguments += $" -a \"{shortcutDescription.ExeProgramArguments}\""; } - logger?.Info($"Creating shortcut for {exeName} => {file}"); + var appUserModelId = $"com.snap.{packageId.Replace(" ", "")}.{exeName.Replace(".exe", string.Empty).Replace(" ", string.Empty)}"; + var toastActivatorClsid = SnapUtility.CreateGuidFromHash(appUserModelId).ToString(); - ShellLink shellLink; - logger?.ErrorIfThrows(() => SnapUtility.Retry(() => - { - Filesystem.FileDelete(file); + shellLink.SetAppUserModelId(appUserModelId); + shellLink.SetToastActivatorCLSID(toastActivatorClsid); - shellLink = new ShellLink - { - Target = shortcutDescription.ExeAbsolutePath, - IconPath = shortcutDescription.IconAbsolutePath ?? shortcutDescription.ExeAbsolutePath, - IconIndex = 0, - WorkingDirectory = Filesystem.PathGetDirectoryName(shortcutDescription.ExeAbsolutePath), - Description = packageDescription - }; - - if (!string.IsNullOrWhiteSpace(shortcutDescription.ExeProgramArguments)) - { - shellLink.Arguments += $" -a \"{shortcutDescription.ExeProgramArguments}\""; - } + logger.Info($"Saving shortcut: {file}. " + + $"Target: {shellLink.Target}. " + + $"Working directory: {shellLink.WorkingDirectory}. " + + $"Arguments: {shellLink.Arguments}. " + + $"ToastActivatorCSLID: {toastActivatorClsid}."); - var appUserModelId = $"com.snap.{packageId.Replace(" ", "")}.{exeName.Replace(".exe", string.Empty).Replace(" ", string.Empty)}"; - var toastActivatorClsid = SnapUtility.CreateGuidFromHash(appUserModelId).ToString(); + if (_isUnitTest == false) + { + shellLink.Save(file); + } - shellLink.SetAppUserModelId(appUserModelId); - shellLink.SetToastActivatorCLSID(toastActivatorClsid); + }, 4), $"Can't write shortcut: {file}"); + } - logger.Info($"Saving shortcut: {file}. " + - $"Target: {shellLink.Target}. " + - $"Working directory: {shellLink.WorkingDirectory}. " + - $"Arguments: {shellLink.Arguments}. " + - $"ToastActivatorCSLID: {toastActivatorClsid}."); + FixPinnedExecutables(baseDirectory, packageVersion, logger: logger); - if (_isUnitTest == false) - { - shellLink.Save(file); - } + return Task.CompletedTask; + } - }, 4), $"Can't write shortcut: {file}"); - } + void FixPinnedExecutables(string baseDirectory, SemanticVersion newCurrentVersion, bool removeAll = false, ILog logger = null) + { + if (Environment.OSVersion.Version < new Version(6, 1)) + { + logger?.Warn($"fixPinnedExecutables: Found OS Version '{Environment.OSVersion.VersionString}', exiting"); + return; + } + + var newCurrentFolder = "app-" + newCurrentVersion; + var newAppPath = Filesystem.PathCombine(baseDirectory, newCurrentFolder); - FixPinnedExecutables(baseDirectory, packageVersion, logger: logger); + var taskbarPath = Filesystem.PathCombine(SpecialFolders.ApplicationData, + "Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar"); - return Task.CompletedTask; + if (!Filesystem.DirectoryExists(taskbarPath)) + { + logger?.Info("fixPinnedExecutables: PinnedExecutables directory doesn't exist, skipping"); + return; } - void FixPinnedExecutables(string baseDirectory, SemanticVersion newCurrentVersion, bool removeAll = false, ILog logger = null) + var resolveLink = new Func(file => { - if (Environment.OSVersion.Version < new Version(6, 1)) + try { - logger?.Warn($"fixPinnedExecutables: Found OS Version '{Environment.OSVersion.VersionString}', exiting"); - return; + logger?.Debug("Examining Pin: " + file); + return new ShellLink(file.FullName); } - - var newCurrentFolder = "app-" + newCurrentVersion; - var newAppPath = Filesystem.PathCombine(baseDirectory, newCurrentFolder); - - var taskbarPath = Filesystem.PathCombine(SpecialFolders.ApplicationData, - "Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar"); - - if (!Filesystem.DirectoryExists(taskbarPath)) + catch (Exception ex) { - logger?.Info("fixPinnedExecutables: PinnedExecutables directory doesn't exist, skipping"); - return; + var message = $"File '{file.FullName}' could not be converted into a valid ShellLink"; + logger?.WarnException(message, ex); + return null; } + }); - var resolveLink = new Func(file => + var shellLinks = new DirectoryInfo(taskbarPath).GetFiles("*.lnk").Select(resolveLink).ToArray(); + + foreach (var shortcut in shellLinks) + { + try { - try + if (shortcut == null) continue; + if (string.IsNullOrWhiteSpace(shortcut.Target)) continue; + if (!shortcut.Target.StartsWith(baseDirectory, StringComparison.OrdinalIgnoreCase)) continue; + + if (removeAll) { - logger?.Debug("Examining Pin: " + file); - return new ShellLink(file.FullName); + Filesystem.FileDeleteWithRetries(shortcut.ShortCutFile); } - catch (Exception ex) + else { - var message = $"File '{file.FullName}' could not be converted into a valid ShellLink"; - logger?.WarnException(message, ex); - return null; + UpdateShellLink(baseDirectory, shortcut, newAppPath, logger); } - }); - var shellLinks = new DirectoryInfo(taskbarPath).GetFiles("*.lnk").Select(resolveLink).ToArray(); - - foreach (var shortcut in shellLinks) + } + catch (Exception ex) { - try - { - if (shortcut == null) continue; - if (string.IsNullOrWhiteSpace(shortcut.Target)) continue; - if (!shortcut.Target.StartsWith(baseDirectory, StringComparison.OrdinalIgnoreCase)) continue; - - if (removeAll) - { - Filesystem.FileDeleteWithRetries(shortcut.ShortCutFile); - } - else - { - UpdateShellLink(baseDirectory, shortcut, newAppPath, logger); - } - - } - catch (Exception ex) - { - var message = $"fixPinnedExecutables: shortcut failed: {shortcut?.Target}"; - logger?.ErrorException(message, ex); - } + var message = $"fixPinnedExecutables: shortcut failed: {shortcut?.Target}"; + logger?.ErrorException(message, ex); } } + } - void UpdateShellLink([JetBrains.Annotations.NotNull] string baseDirectory, [JetBrains.Annotations.NotNull] ShellLink shortcut, [JetBrains.Annotations.NotNull] string newAppPath, ILog logger = null) - { - if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - if (shortcut == null) throw new ArgumentNullException(nameof(shortcut)); - if (newAppPath == null) throw new ArgumentNullException(nameof(newAppPath)); + void UpdateShellLink([JetBrains.Annotations.NotNull] string baseDirectory, [JetBrains.Annotations.NotNull] ShellLink shortcut, [JetBrains.Annotations.NotNull] string newAppPath, ILog logger = null) + { + if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); + if (shortcut == null) throw new ArgumentNullException(nameof(shortcut)); + if (newAppPath == null) throw new ArgumentNullException(nameof(newAppPath)); - logger?.Info($"Processing shortcut '{shortcut.ShortCutFile}'"); + logger?.Info($"Processing shortcut '{shortcut.ShortCutFile}'"); - var target = Environment.ExpandEnvironmentVariables(shortcut.Target); - var targetIsUpdateDotExe = target.EndsWith("update.exe", StringComparison.OrdinalIgnoreCase); + var target = Environment.ExpandEnvironmentVariables(shortcut.Target); + var targetIsUpdateDotExe = target.EndsWith("update.exe", StringComparison.OrdinalIgnoreCase); - logger?.Info($"Old shortcut target: '{target}'"); + logger?.Info($"Old shortcut target: '{target}'"); - target = Filesystem.PathCombine(baseDirectory, Filesystem.PathGetFileName(targetIsUpdateDotExe ? shortcut.Target : shortcut.IconPath)); + target = Filesystem.PathCombine(baseDirectory, Filesystem.PathGetFileName(targetIsUpdateDotExe ? shortcut.Target : shortcut.IconPath)); - logger?.Info($"New shortcut target: '{target}'"); + logger?.Info($"New shortcut target: '{target}'"); - shortcut.WorkingDirectory = newAppPath; - shortcut.Target = target; + shortcut.WorkingDirectory = newAppPath; + shortcut.Target = target; - logger?.Info($"Old iconPath is: '{shortcut.IconPath}'"); - shortcut.IconPath = target; - shortcut.IconIndex = 0; + logger?.Info($"Old iconPath is: '{shortcut.IconPath}'"); + shortcut.IconPath = target; + shortcut.IconIndex = 0; - logger?.ErrorIfThrows(() => SnapUtility.Retry(shortcut.Save), $"Couldn't write shortcut {shortcut.ShortCutFile}"); - logger?.Info("Finished shortcut successfully"); - } + logger?.ErrorIfThrows(() => SnapUtility.Retry(shortcut.Save), $"Couldn't write shortcut {shortcut.ShortCutFile}"); + logger?.Info("Finished shortcut successfully"); + } - public List GetProcesses() + public List GetProcesses() + { + var processes = EnumerateProcesses().Select(x => { - var processes = EnumerateProcesses().Select(x => - { - var processNameValid = !string.IsNullOrWhiteSpace(x.processName); - return OsProcessManager.Build(x.pid, x.processName, - !processNameValid ? null : Filesystem.PathGetDirectoryName(x.processName), - !processNameValid ? null : Filesystem.PathGetFileName(x.processName)); - }).ToList(); + var processNameValid = !string.IsNullOrWhiteSpace(x.processName); + return OsProcessManager.Build(x.pid, x.processName, + !processNameValid ? null : Filesystem.PathGetDirectoryName(x.processName), + !processNameValid ? null : Filesystem.PathGetFileName(x.processName)); + }).ToList(); - return processes; - } + return processes; + } + + public ISnapOsExitSignal InstallExitSignalHandler() + { + return new SnapOsWindowsExitSignal(); + } - public ISnapOsExitSignal InstallExitSignalHandler() + public bool EnsureConsole() + { + if (Environment.OSVersion.Platform != PlatformID.Win32NT) { - return new SnapOsWindowsExitSignal(); + return false; } - public bool EnsureConsole() + if (Interlocked.CompareExchange(ref _consoleCreated, 1, 0) == 1) { - if (Environment.OSVersion.Platform != PlatformID.Win32NT) - { - return false; - } + return false; + } - if (Interlocked.CompareExchange(ref _consoleCreated, 1, 0) == 1) - { - return false; - } + if (!NativeMethodsWindows.AttachConsole(-1)) + { + NativeMethodsWindows.AllocConsole(); + } - if (!NativeMethodsWindows.AttachConsole(-1)) - { - NativeMethodsWindows.AllocConsole(); - } + NativeMethodsWindows.GetStdHandle(StandardHandles.StdErrorHandle); + NativeMethodsWindows.GetStdHandle(StandardHandles.StdOutputHandle); - NativeMethodsWindows.GetStdHandle(StandardHandles.StdErrorHandle); - NativeMethodsWindows.GetStdHandle(StandardHandles.StdOutputHandle); + return true; + } - return true; - } + static unsafe IEnumerable<(string processName, int pid)> EnumerateProcesses() + { + int bytesReturned; + var processIds = new int[2048]; - static unsafe IEnumerable<(string processName, int pid)> EnumerateProcesses() + fixed (int* p = processIds) { - int bytesReturned; - var processIds = new int[2048]; - - fixed (int* p = processIds) + if (!NativeMethodsWindows.EnumProcesses((IntPtr)p, sizeof(int) * processIds.Length, out bytesReturned)) { - if (!NativeMethodsWindows.EnumProcesses((IntPtr)p, sizeof(int) * processIds.Length, out bytesReturned)) - { - throw new Win32Exception("Failed to enumerate processes"); - } - - if (bytesReturned < 1) throw new Exception("Failed to enumerate processes"); + throw new Win32Exception("Failed to enumerate processes"); } - return Enumerable.Range(0, bytesReturned / sizeof(int)) - .Where(i => processIds[i] > 0) - .Select(i => + if (bytesReturned < 1) throw new Exception("Failed to enumerate processes"); + } + + return Enumerable.Range(0, bytesReturned / sizeof(int)) + .Where(i => processIds[i] > 0) + .Select(i => + { + try { - try + var hProcess = NativeMethodsWindows.OpenProcess(ProcessAccess.QueryLimitedInformation, false, processIds[i]); + if (hProcess == IntPtr.Zero) { - var hProcess = NativeMethodsWindows.OpenProcess(ProcessAccess.QueryLimitedInformation, false, processIds[i]); - if (hProcess == IntPtr.Zero) - { - throw new Win32Exception(); - } - - var sb = new StringBuilder(256); - var capacity = sb.Capacity; - if (!NativeMethodsWindows.QueryFullProcessImageName(hProcess, 0, sb, ref capacity)) - { - throw new Win32Exception(); - } - - NativeMethodsWindows.CloseHandle(hProcess); - - return (sb.ToString(), processIds[i]); + throw new Win32Exception(); } - catch (Exception) + + var sb = new StringBuilder(256); + var capacity = sb.Capacity; + if (!NativeMethodsWindows.QueryFullProcessImageName(hProcess, 0, sb, ref capacity)) { - return (default, processIds[i]); + throw new Win32Exception(); } - }) - .ToList(); - } + NativeMethodsWindows.CloseHandle(hProcess); + + return (sb.ToString(), processIds[i]); + } + catch (Exception) + { + return (default, processIds[i]); + } + }) + .ToList(); } -} + +} \ No newline at end of file diff --git a/src/Snap/Attributes/SnapAppReleaseDetailsAttribute.cs b/src/Snap/Attributes/SnapAppReleaseDetailsAttribute.cs index bfbc97e0..1a0b2225 100644 --- a/src/Snap/Attributes/SnapAppReleaseDetailsAttribute.cs +++ b/src/Snap/Attributes/SnapAppReleaseDetailsAttribute.cs @@ -1,10 +1,9 @@ using System; -namespace Snap.Attributes +namespace Snap.Attributes; + +[AttributeUsage(AttributeTargets.Assembly)] +internal sealed class SnapAppReleaseDetailsAttribute : Attribute { - [AttributeUsage(AttributeTargets.Assembly)] - internal sealed class SnapAppReleaseDetailsAttribute : Attribute - { - } -} +} \ No newline at end of file diff --git a/src/Snap/Core/IO/DisposableDirectory.cs b/src/Snap/Core/IO/DisposableDirectory.cs index 684d6bdd..da78e45b 100644 --- a/src/Snap/Core/IO/DisposableDirectory.cs +++ b/src/Snap/Core/IO/DisposableDirectory.cs @@ -2,27 +2,26 @@ using System.Threading.Tasks; using Snap.Extensions; -namespace Snap.Core.IO +namespace Snap.Core.IO; + +internal sealed class DisposableDirectory : IAsyncDisposable { - internal sealed class DisposableDirectory : IAsyncDisposable - { - readonly ISnapFilesystem _filesystem; + readonly ISnapFilesystem _filesystem; - public string WorkingDirectory { get; } + public string WorkingDirectory { get; } - public static implicit operator string(DisposableDirectory directory) => directory.WorkingDirectory; + public static implicit operator string(DisposableDirectory directory) => directory.WorkingDirectory; - public DisposableDirectory(string workingDirectory, ISnapFilesystem filesystem, bool createRandomSubdirectory = true) - { - _filesystem = filesystem; - WorkingDirectory = !createRandomSubdirectory ? workingDirectory : filesystem.PathCombine(workingDirectory, Guid.NewGuid().ToString()); + public DisposableDirectory(string workingDirectory, ISnapFilesystem filesystem, bool createRandomSubdirectory = true) + { + _filesystem = filesystem; + WorkingDirectory = !createRandomSubdirectory ? workingDirectory : filesystem.PathCombine(workingDirectory, Guid.NewGuid().ToString()); - filesystem.DirectoryCreateIfNotExists(WorkingDirectory); - } + filesystem.DirectoryCreateIfNotExists(WorkingDirectory); + } - public async ValueTask DisposeAsync() - { - await _filesystem.DirectoryDeleteAsync(WorkingDirectory); - } + public async ValueTask DisposeAsync() + { + await _filesystem.DirectoryDeleteAsync(WorkingDirectory); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Logging/ColoredConsoleLogProvider.cs b/src/Snap/Core/Logging/ColoredConsoleLogProvider.cs index 81cd2762..b3125d5d 100644 --- a/src/Snap/Core/Logging/ColoredConsoleLogProvider.cs +++ b/src/Snap/Core/Logging/ColoredConsoleLogProvider.cs @@ -6,93 +6,92 @@ using Snap.Logging; using Snap.Logging.LogProviders; -namespace Snap.Core.Logging +namespace Snap.Core.Logging; + +internal class ColoredConsoleLogProvider : LogProviderBase { - internal class ColoredConsoleLogProvider : LogProviderBase - { - readonly LogLevel _logLevel; + readonly LogLevel _logLevel; - static readonly Dictionary Colors = new() - { - {LogLevel.Fatal, ConsoleColor.Red}, - {LogLevel.Error, ConsoleColor.Red}, - {LogLevel.Warn, ConsoleColor.Magenta}, - {LogLevel.Info, ConsoleColor.White}, - {LogLevel.Debug, ConsoleColor.Gray}, - {LogLevel.Trace, ConsoleColor.DarkGray} - }; + static readonly Dictionary Colors = new() + { + {LogLevel.Fatal, ConsoleColor.Red}, + {LogLevel.Error, ConsoleColor.Red}, + {LogLevel.Warn, ConsoleColor.Magenta}, + {LogLevel.Info, ConsoleColor.White}, + {LogLevel.Debug, ConsoleColor.Gray}, + {LogLevel.Trace, ConsoleColor.DarkGray} + }; - public ColoredConsoleLogProvider(LogLevel logLevel) - { - _logLevel = logLevel; - } + public ColoredConsoleLogProvider(LogLevel logLevel) + { + _logLevel = logLevel; + } - public override Logger GetLogger(string name) + public override Logger GetLogger(string name) + { + return (logLevel, messageFunc, exception, formatParameters) => { - return (logLevel, messageFunc, exception, formatParameters) => + if (messageFunc == null) { - if (messageFunc == null) - { - return true; // All log levels are enabled - } + return true; // All log levels are enabled + } - if (Colors.TryGetValue(logLevel, out var consoleColor)) + if (Colors.TryGetValue(logLevel, out var consoleColor)) + { + var originalForground = Console.ForegroundColor; + try { - var originalForground = Console.ForegroundColor; - try - { - Console.ForegroundColor = consoleColor; - WriteMessage(logLevel, name, messageFunc, formatParameters, exception); - } - finally - { - Console.ForegroundColor = originalForground; - } + Console.ForegroundColor = consoleColor; + WriteMessage(logLevel, name, messageFunc, formatParameters, exception); } - else + finally { - WriteMessage(logLevel, name, messageFunc, formatParameters, exception); + Console.ForegroundColor = originalForground; } + } + else + { + WriteMessage(logLevel, name, messageFunc, formatParameters, exception); + } - return true; - }; - } + return true; + }; + } - void WriteMessage( - LogLevel logLevel, - string name, - Func messageFunc, - object[] formatParameters, - Exception exception) + void WriteMessage( + LogLevel logLevel, + string name, + Func messageFunc, + object[] formatParameters, + Exception exception) + { + if (logLevel < _logLevel) { - if (logLevel < _logLevel) - { - return; - } + return; + } - var exceptionsEnabled = Debugger.IsAttached - || Environment.GetEnvironmentVariable("SNAPX_LOG_EXCEPTIONS").IsTrue(); + var exceptionsEnabled = Debugger.IsAttached + || Environment.GetEnvironmentVariable("SNAPX_LOG_EXCEPTIONS").IsTrue(); - var message = string.Format(CultureInfo.InvariantCulture, messageFunc(), formatParameters); - if (exception != null) + var message = string.Format(CultureInfo.InvariantCulture, messageFunc(), formatParameters); + if (exception != null) + { + if (exceptionsEnabled) { - if (exceptionsEnabled) - { - message = message + " | " + exception; - } - else - { - message = message + " | " + exception.Message; - } + message = message + " | " + exception; } - - if (exception != null) + else { - Console.Error.WriteLine(message); - return; + message = message + " | " + exception.Message; } + } - Console.WriteLine(message); + if (exception != null) + { + Console.Error.WriteLine(message); + return; } + + Console.WriteLine(message); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Logging/LogForwarder.cs b/src/Snap/Core/Logging/LogForwarder.cs index ef3243b4..35a48203 100644 --- a/src/Snap/Core/Logging/LogForwarder.cs +++ b/src/Snap/Core/Logging/LogForwarder.cs @@ -3,38 +3,37 @@ using Snap.Logging; using LogLevel = Snap.Logging.LogLevel; -namespace Snap.Core.Logging +namespace Snap.Core.Logging; + +internal sealed class LogForwarder : ILog { - internal sealed class LogForwarder : ILog - { - readonly LogLevel _logLevel; - readonly ILog _logger; - readonly LogDelegate _logDelegate; + readonly LogLevel _logLevel; + readonly ILog _logger; + readonly LogDelegate _logDelegate; - internal delegate void LogDelegate(LogLevel logLevel, - Func messageFunc, Exception exception = null, params object[] formatParameters); + internal delegate void LogDelegate(LogLevel logLevel, + Func messageFunc, Exception exception = null, params object[] formatParameters); - public LogForwarder(LogLevel logLevel, [NotNull] ILog logger, [NotNull] LogDelegate logDelegate) - { - _logLevel = logLevel; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _logDelegate = logDelegate ?? throw new ArgumentNullException(nameof(logDelegate)); - } + public LogForwarder(LogLevel logLevel, [NotNull] ILog logger, [NotNull] LogDelegate logDelegate) + { + _logLevel = logLevel; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _logDelegate = logDelegate ?? throw new ArgumentNullException(nameof(logDelegate)); + } - public bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters) + public bool Log(LogLevel logLevel, Func messageFunc, Exception exception = null, params object[] formatParameters) + { + if (logLevel < _logLevel) { - if (logLevel < _logLevel) - { - return _logger.Log(logLevel, messageFunc, exception, formatParameters); - } + return _logger.Log(logLevel, messageFunc, exception, formatParameters); + } - var message = messageFunc?.Invoke(); - if (message != null) - { - _logDelegate.Invoke(logLevel, () => message, exception, formatParameters); - } - - return _logger.Log(logLevel, () => message, exception, formatParameters); + var message = messageFunc?.Invoke(); + if (message != null) + { + _logDelegate.Invoke(logLevel, () => message, exception, formatParameters); } + + return _logger.Log(logLevel, () => message, exception, formatParameters); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/MessagePack/Formatters/OSPlatformMessagePackFormatter.cs b/src/Snap/Core/MessagePack/Formatters/OSPlatformMessagePackFormatter.cs index 8f9fa1e2..9b1e3b31 100644 --- a/src/Snap/Core/MessagePack/Formatters/OSPlatformMessagePackFormatter.cs +++ b/src/Snap/Core/MessagePack/Formatters/OSPlatformMessagePackFormatter.cs @@ -2,19 +2,18 @@ using MessagePack; using MessagePack.Formatters; -namespace Snap.Core.MessagePack.Formatters +namespace Snap.Core.MessagePack.Formatters; + +public sealed class OsPlatformMessagePackFormatter : IMessagePackFormatter { - public sealed class OsPlatformMessagePackFormatter : IMessagePackFormatter + public void Serialize(ref MessagePackWriter writer, OSPlatform value, MessagePackSerializerOptions options) { - public void Serialize(ref MessagePackWriter writer, OSPlatform value, MessagePackSerializerOptions options) - { - options.Resolver.GetFormatterWithVerify().Serialize(ref writer, value.ToString(), options); - } + options.Resolver.GetFormatterWithVerify().Serialize(ref writer, value.ToString(), options); + } - public OSPlatform Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) - { - var osPlatform = options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options); - return OSPlatform.Create(osPlatform); - } + public OSPlatform Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + { + var osPlatform = options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options); + return OSPlatform.Create(osPlatform); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/MessagePack/Formatters/SemanticVersionMessagePackFormatter.cs b/src/Snap/Core/MessagePack/Formatters/SemanticVersionMessagePackFormatter.cs index 902ce622..99fda780 100644 --- a/src/Snap/Core/MessagePack/Formatters/SemanticVersionMessagePackFormatter.cs +++ b/src/Snap/Core/MessagePack/Formatters/SemanticVersionMessagePackFormatter.cs @@ -2,19 +2,18 @@ using MessagePack.Formatters; using NuGet.Versioning; -namespace Snap.Core.MessagePack.Formatters +namespace Snap.Core.MessagePack.Formatters; + +public sealed class SemanticVersionMessagePackFormatter : IMessagePackFormatter { - public sealed class SemanticVersionMessagePackFormatter : IMessagePackFormatter + public void Serialize(ref MessagePackWriter writer, SemanticVersion value, MessagePackSerializerOptions options) { - public void Serialize(ref MessagePackWriter writer, SemanticVersion value, MessagePackSerializerOptions options) - { - options.Resolver.GetFormatterWithVerify().Serialize(ref writer, value.ToString(), options); - } + options.Resolver.GetFormatterWithVerify().Serialize(ref writer, value.ToString(), options); + } - public SemanticVersion Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) - { - var version = options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options); - return SemanticVersion.Parse(version); - } + public SemanticVersion Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) + { + var version = options.Resolver.GetFormatterWithVerify().Deserialize(ref reader, options); + return SemanticVersion.Parse(version); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Models/SnapAppChannelReleases.cs b/src/Snap/Core/Models/SnapAppChannelReleases.cs index 13f4823f..596f5b26 100644 --- a/src/Snap/Core/Models/SnapAppChannelReleases.cs +++ b/src/Snap/Core/Models/SnapAppChannelReleases.cs @@ -5,138 +5,137 @@ using JetBrains.Annotations; using NuGet.Versioning; -namespace Snap.Core.Models +namespace Snap.Core.Models; + +public interface ISnapAppChannelReleases : IEnumerable +{ + SnapApp App { get; } + SnapChannel Channel { get; } + bool HasGenesisRelease(); + bool HasDeltaReleases(); + bool HasReleases(); + SnapRelease GetMostRecentRelease(); + SnapRelease GetGenesisRelease(); + IEnumerable GetFullReleases(); + IEnumerable GetDeltaReleases(); + IEnumerable GetReleasesNewerThan([NotNull] SemanticVersion version); + IEnumerable GetFullReleasesNewerThan([NotNull] SemanticVersion version); + IEnumerable GetFullReleasesNewerThanOrEqualTo([NotNull] SemanticVersion version); + IEnumerable GetFullReleasesOlderThanOrEqualTo([NotNull] SemanticVersion version); + IEnumerable GetDeltaReleasesNewerThanOrEqualTo([NotNull] SemanticVersion version); + IEnumerable GetDeltaReleasesNewerThan([NotNull] SemanticVersion version); + IEnumerable GetDeltaReleasesOlderThanOrEqualTo([NotNull] SemanticVersion version); +} + +internal sealed class SnapAppChannelReleases : ISnapAppChannelReleases { - public interface ISnapAppChannelReleases : IEnumerable - { - SnapApp App { get; } - SnapChannel Channel { get; } - bool HasGenesisRelease(); - bool HasDeltaReleases(); - bool HasReleases(); - SnapRelease GetMostRecentRelease(); - SnapRelease GetGenesisRelease(); - IEnumerable GetFullReleases(); - IEnumerable GetDeltaReleases(); - IEnumerable GetReleasesNewerThan([NotNull] SemanticVersion version); - IEnumerable GetFullReleasesNewerThan([NotNull] SemanticVersion version); - IEnumerable GetFullReleasesNewerThanOrEqualTo([NotNull] SemanticVersion version); - IEnumerable GetFullReleasesOlderThanOrEqualTo([NotNull] SemanticVersion version); - IEnumerable GetDeltaReleasesNewerThanOrEqualTo([NotNull] SemanticVersion version); - IEnumerable GetDeltaReleasesNewerThan([NotNull] SemanticVersion version); - IEnumerable GetDeltaReleasesOlderThanOrEqualTo([NotNull] SemanticVersion version); - } - - internal sealed class SnapAppChannelReleases : ISnapAppChannelReleases - { - List Releases { get; } - - public SnapApp App { get; } - public SnapChannel Channel { get; } - - public SnapAppChannelReleases([NotNull] SnapApp snapApp, [NotNull] SnapChannel snapChannel, [NotNull] IEnumerable snapReleases) - { - if (snapReleases == null) throw new ArgumentNullException(nameof(snapReleases)); - App = snapApp ?? throw new ArgumentNullException(nameof(snapApp)); - Channel = snapChannel ?? throw new ArgumentNullException(nameof(snapChannel)); - Releases = snapReleases.OrderBy(x => x.Version).ToList(); - } + List Releases { get; } - public SnapAppChannelReleases([NotNull] ISnapAppChannelReleases snapAppChannelReleases, [NotNull] IEnumerable snapReleases) : - this(snapAppChannelReleases.App, snapAppChannelReleases.Channel, snapReleases) - { - if (snapAppChannelReleases == null) throw new ArgumentNullException(nameof(snapAppChannelReleases)); - if (snapReleases == null) throw new ArgumentNullException(nameof(snapReleases)); - } + public SnapApp App { get; } + public SnapChannel Channel { get; } + + public SnapAppChannelReleases([NotNull] SnapApp snapApp, [NotNull] SnapChannel snapChannel, [NotNull] IEnumerable snapReleases) + { + if (snapReleases == null) throw new ArgumentNullException(nameof(snapReleases)); + App = snapApp ?? throw new ArgumentNullException(nameof(snapApp)); + Channel = snapChannel ?? throw new ArgumentNullException(nameof(snapChannel)); + Releases = snapReleases.OrderBy(x => x.Version).ToList(); + } + + public SnapAppChannelReleases([NotNull] ISnapAppChannelReleases snapAppChannelReleases, [NotNull] IEnumerable snapReleases) : + this(snapAppChannelReleases.App, snapAppChannelReleases.Channel, snapReleases) + { + if (snapAppChannelReleases == null) throw new ArgumentNullException(nameof(snapAppChannelReleases)); + if (snapReleases == null) throw new ArgumentNullException(nameof(snapReleases)); + } - public bool HasGenesisRelease() - { - return Releases.FirstOrDefault()?.IsGenesis ?? false; - } + public bool HasGenesisRelease() + { + return Releases.FirstOrDefault()?.IsGenesis ?? false; + } - public bool HasDeltaReleases() - { - return Releases.Any(x => x.IsDelta); - } + public bool HasDeltaReleases() + { + return Releases.Any(x => x.IsDelta); + } - public bool HasReleases() - { - return Releases.Any(); - } + public bool HasReleases() + { + return Releases.Any(); + } - public SnapRelease GetMostRecentRelease() - { - return Releases.LastOrDefault(); - } + public SnapRelease GetMostRecentRelease() + { + return Releases.LastOrDefault(); + } - public SnapRelease GetGenesisRelease() - { - return HasGenesisRelease() ? Releases?.First() : null; - } + public SnapRelease GetGenesisRelease() + { + return HasGenesisRelease() ? Releases?.First() : null; + } - public IEnumerable GetFullReleases() - { - return Releases.Where(x => x.IsFull); - } + public IEnumerable GetFullReleases() + { + return Releases.Where(x => x.IsFull); + } - public IEnumerable GetDeltaReleases() - { - return Releases.Where(x => x.IsDelta); - } + public IEnumerable GetDeltaReleases() + { + return Releases.Where(x => x.IsDelta); + } - public IEnumerable GetReleasesNewerThan(SemanticVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - return Releases.Where(x => x.Version > version); - } + public IEnumerable GetReleasesNewerThan(SemanticVersion version) + { + if (version == null) throw new ArgumentNullException(nameof(version)); + return Releases.Where(x => x.Version > version); + } - public IEnumerable GetFullReleasesNewerThan(SemanticVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - return Releases.Where(x => x.IsFull && x.Version > version); - } + public IEnumerable GetFullReleasesNewerThan(SemanticVersion version) + { + if (version == null) throw new ArgumentNullException(nameof(version)); + return Releases.Where(x => x.IsFull && x.Version > version); + } - public IEnumerable GetFullReleasesNewerThanOrEqualTo(SemanticVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - return Releases.Where(x => x.IsFull && x.Version >= version); - } + public IEnumerable GetFullReleasesNewerThanOrEqualTo(SemanticVersion version) + { + if (version == null) throw new ArgumentNullException(nameof(version)); + return Releases.Where(x => x.IsFull && x.Version >= version); + } - public IEnumerable GetFullReleasesOlderThanOrEqualTo(SemanticVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - return Releases.Where(x => x.IsFull && x.Version <= version); - } + public IEnumerable GetFullReleasesOlderThanOrEqualTo(SemanticVersion version) + { + if (version == null) throw new ArgumentNullException(nameof(version)); + return Releases.Where(x => x.IsFull && x.Version <= version); + } - public IEnumerable GetDeltaReleasesNewerThanOrEqualTo(SemanticVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - return Releases.Where(x => x.IsDelta && x.Version >= version); - } + public IEnumerable GetDeltaReleasesNewerThanOrEqualTo(SemanticVersion version) + { + if (version == null) throw new ArgumentNullException(nameof(version)); + return Releases.Where(x => x.IsDelta && x.Version >= version); + } - public IEnumerable GetDeltaReleasesNewerThan(SemanticVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - return Releases.Where(x => x.IsDelta && x.Version > version); - } + public IEnumerable GetDeltaReleasesNewerThan(SemanticVersion version) + { + if (version == null) throw new ArgumentNullException(nameof(version)); + return Releases.Where(x => x.IsDelta && x.Version > version); + } - public IEnumerable GetDeltaReleasesOlderThanOrEqualTo(SemanticVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - return Releases.Where(x => x.IsDelta && x.Version <= version); - } + public IEnumerable GetDeltaReleasesOlderThanOrEqualTo(SemanticVersion version) + { + if (version == null) throw new ArgumentNullException(nameof(version)); + return Releases.Where(x => x.IsDelta && x.Version <= version); + } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() + { + foreach(var release in Releases) { - foreach(var release in Releases) - { - yield return release; - } + yield return release; } + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Models/SnapAppReleases.cs b/src/Snap/Core/Models/SnapAppReleases.cs index 27ff84f2..c9f70ac4 100644 --- a/src/Snap/Core/Models/SnapAppReleases.cs +++ b/src/Snap/Core/Models/SnapAppReleases.cs @@ -5,201 +5,200 @@ using JetBrains.Annotations; using NuGet.Versioning; -namespace Snap.Core.Models +namespace Snap.Core.Models; + +public interface ISnapAppReleases : IEnumerable { - public interface ISnapAppReleases : IEnumerable - { - SnapApp SnapApp { get; } - bool HasReleasesIn([NotNull] SnapChannel channel); - bool HasReleasesIn([NotNull] string channelName); - bool HasDeltaReleasesIn([NotNull] SnapChannel channel); - bool HasDeltaReleasesIn([NotNull] string channelName); - SnapRelease GetMostRecentRelease([NotNull] SnapChannel channel); - SnapRelease GetMostRecentRelease([NotNull] string channelName); - SnapRelease GetMostRecentDeltaRelease([NotNull] SnapChannel channel); - SnapRelease GetMostRecentDeltaRelease([NotNull] string channelName); - SnapRelease GetGenesisRelease([NotNull] SnapChannel channel); - SnapRelease GetGenesisRelease([NotNull] string channelName); - SnapRelease GetPreviousRelease([NotNull] SnapChannel channel, SemanticVersion version); - SnapRelease GetPreviousRelease([NotNull] string channelName, SemanticVersion version); - ISnapAppChannelReleases GetDeltaReleasesNewerThan([NotNull] SnapChannel channel, [NotNull] SemanticVersion version); - ISnapAppChannelReleases GetDeltaReleasesNewerThan([NotNull] string channel, [NotNull] SemanticVersion version); - ISnapAppChannelReleases GetDeltaReleasesOlderThanOrEqualTo([NotNull] SnapChannel channel, [NotNull] SemanticVersion version); - ISnapAppChannelReleases GetDeltaReleasesOlderThanOrEqualTo([NotNull] string channelName, [NotNull] SemanticVersion version); - ISnapAppChannelReleases GetReleases([NotNull] SnapChannel snapChannel); - ISnapAppChannelReleases GetReleases([NotNull] string channelName); - } - - internal sealed class SnapAppReleases : ISnapAppReleases - { - List Releases { get; } - - public SnapApp SnapApp { get; } - - public SnapAppReleases([NotNull] SnapApp snapApp, [NotNull] IEnumerable snapReleases) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (snapReleases == null) throw new ArgumentNullException(nameof(snapReleases)); + SnapApp SnapApp { get; } + bool HasReleasesIn([NotNull] SnapChannel channel); + bool HasReleasesIn([NotNull] string channelName); + bool HasDeltaReleasesIn([NotNull] SnapChannel channel); + bool HasDeltaReleasesIn([NotNull] string channelName); + SnapRelease GetMostRecentRelease([NotNull] SnapChannel channel); + SnapRelease GetMostRecentRelease([NotNull] string channelName); + SnapRelease GetMostRecentDeltaRelease([NotNull] SnapChannel channel); + SnapRelease GetMostRecentDeltaRelease([NotNull] string channelName); + SnapRelease GetGenesisRelease([NotNull] SnapChannel channel); + SnapRelease GetGenesisRelease([NotNull] string channelName); + SnapRelease GetPreviousRelease([NotNull] SnapChannel channel, SemanticVersion version); + SnapRelease GetPreviousRelease([NotNull] string channelName, SemanticVersion version); + ISnapAppChannelReleases GetDeltaReleasesNewerThan([NotNull] SnapChannel channel, [NotNull] SemanticVersion version); + ISnapAppChannelReleases GetDeltaReleasesNewerThan([NotNull] string channel, [NotNull] SemanticVersion version); + ISnapAppChannelReleases GetDeltaReleasesOlderThanOrEqualTo([NotNull] SnapChannel channel, [NotNull] SemanticVersion version); + ISnapAppChannelReleases GetDeltaReleasesOlderThanOrEqualTo([NotNull] string channelName, [NotNull] SemanticVersion version); + ISnapAppChannelReleases GetReleases([NotNull] SnapChannel snapChannel); + ISnapAppChannelReleases GetReleases([NotNull] string channelName); +} - SnapApp = new SnapApp(snapApp); - Releases = snapReleases.Select(x => new SnapRelease(x)).OrderBy(x => x.Version).ToList(); - } +internal sealed class SnapAppReleases : ISnapAppReleases +{ + List Releases { get; } - public bool HasReleasesIn(SnapChannel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return HasReleasesIn(channel.Name); - } + public SnapApp SnapApp { get; } + + public SnapAppReleases([NotNull] SnapApp snapApp, [NotNull] IEnumerable snapReleases) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (snapReleases == null) throw new ArgumentNullException(nameof(snapReleases)); + + SnapApp = new SnapApp(snapApp); + Releases = snapReleases.Select(x => new SnapRelease(x)).OrderBy(x => x.Version).ToList(); + } + + public bool HasReleasesIn(SnapChannel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return HasReleasesIn(channel.Name); + } - public bool HasReleasesIn(string channelName) + public bool HasReleasesIn(string channelName) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + foreach (var release in this) { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - foreach (var release in this) + if (release.Channels.Contains(channelName)) { - if (release.Channels.Contains(channelName)) - { - return true; - } + return true; } - return false; } + return false; + } - public bool HasDeltaReleasesIn(SnapChannel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return HasReleasesIn(channel.Name); - } + public bool HasDeltaReleasesIn(SnapChannel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return HasReleasesIn(channel.Name); + } - public bool HasDeltaReleasesIn(string channelName) - { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - return this.Where(x => x.IsDelta).Any(release => release.Channels.Contains(channelName)); - } + public bool HasDeltaReleasesIn(string channelName) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + return this.Where(x => x.IsDelta).Any(release => release.Channels.Contains(channelName)); + } - public SnapRelease GetMostRecentRelease(SnapChannel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return GetMostRecentRelease(channel.Name); - } + public SnapRelease GetMostRecentRelease(SnapChannel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return GetMostRecentRelease(channel.Name); + } - public SnapRelease GetMostRecentRelease(string channelName) - { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - return this.LastOrDefault(release => release.Channels.Contains(channelName)); - } + public SnapRelease GetMostRecentRelease(string channelName) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + return this.LastOrDefault(release => release.Channels.Contains(channelName)); + } - public SnapRelease GetMostRecentDeltaRelease(SnapChannel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return GetMostRecentDeltaRelease(channel.Name); - } + public SnapRelease GetMostRecentDeltaRelease(SnapChannel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return GetMostRecentDeltaRelease(channel.Name); + } - public SnapRelease GetMostRecentDeltaRelease(string channelName) - { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - return this.LastOrDefault(release => release.IsDelta && release.Channels.Contains(channelName)); - } + public SnapRelease GetMostRecentDeltaRelease(string channelName) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + return this.LastOrDefault(release => release.IsDelta && release.Channels.Contains(channelName)); + } - public SnapRelease GetGenesisRelease(SnapChannel channel) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return GetGenesisRelease(channel.Name); - } + public SnapRelease GetGenesisRelease(SnapChannel channel) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return GetGenesisRelease(channel.Name); + } - public SnapRelease GetGenesisRelease(string channelName) - { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - return this.FirstOrDefault(x => x.IsFull && x.IsGenesis && x.Channels.Contains(channelName)); - } + public SnapRelease GetGenesisRelease(string channelName) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + return this.FirstOrDefault(x => x.IsFull && x.IsGenesis && x.Channels.Contains(channelName)); + } - public ISnapAppChannelReleases GetDeltaReleasesNewerThan(SnapChannel channel, SemanticVersion version) - { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - if (version == null) throw new ArgumentNullException(nameof(version)); - return GetDeltaReleasesNewerThan(channel.Name, version); - } - - public ISnapAppChannelReleases GetDeltaReleasesNewerThan(string channelName, SemanticVersion version) - { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - if (version == null) throw new ArgumentNullException(nameof(version)); - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + public ISnapAppChannelReleases GetDeltaReleasesNewerThan(SnapChannel channel, SemanticVersion version) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (version == null) throw new ArgumentNullException(nameof(version)); + return GetDeltaReleasesNewerThan(channel.Name, version); + } - var channel = SnapApp.Channels.SingleOrDefault(x => x.Name == channelName); - if (channel == null) - { - throw new Exception($"Unknown channel: {channelName}"); - } - - var deltaReleasesNewerThan = this.Where(x => x.IsDelta && x.Channels.Contains(channelName) && x.Version > version); - return new SnapAppChannelReleases(SnapApp, channel, deltaReleasesNewerThan); - } + public ISnapAppChannelReleases GetDeltaReleasesNewerThan(string channelName, SemanticVersion version) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + if (version == null) throw new ArgumentNullException(nameof(version)); + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - public ISnapAppChannelReleases GetDeltaReleasesOlderThanOrEqualTo(SnapChannel channel, SemanticVersion version) + var channel = SnapApp.Channels.SingleOrDefault(x => x.Name == channelName); + if (channel == null) { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - if (version == null) throw new ArgumentNullException(nameof(version)); - return GetDeltaReleasesOlderThanOrEqualTo(channel.Name, version); + throw new Exception($"Unknown channel: {channelName}"); } + + var deltaReleasesNewerThan = this.Where(x => x.IsDelta && x.Channels.Contains(channelName) && x.Version > version); + return new SnapAppChannelReleases(SnapApp, channel, deltaReleasesNewerThan); + } - public ISnapAppChannelReleases GetDeltaReleasesOlderThanOrEqualTo(string channelName, SemanticVersion version) - { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - if (version == null) throw new ArgumentNullException(nameof(version)); - var channel = SnapApp.Channels.SingleOrDefault(x => x.Name == channelName); - if (channel == null) - { - throw new Exception($"Unknown channel: {channelName}"); - } - - var deltaReleasesOlderThanOrEqualTo = this.Where(x => x.IsDelta && x.Channels.Contains(channelName) && x.Version <= version); - return new SnapAppChannelReleases(SnapApp, channel, deltaReleasesOlderThanOrEqualTo); - } + public ISnapAppChannelReleases GetDeltaReleasesOlderThanOrEqualTo(SnapChannel channel, SemanticVersion version) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (version == null) throw new ArgumentNullException(nameof(version)); + return GetDeltaReleasesOlderThanOrEqualTo(channel.Name, version); + } - public ISnapAppChannelReleases GetReleases(SnapChannel snapChannel) + public ISnapAppChannelReleases GetDeltaReleasesOlderThanOrEqualTo(string channelName, SemanticVersion version) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + if (version == null) throw new ArgumentNullException(nameof(version)); + var channel = SnapApp.Channels.SingleOrDefault(x => x.Name == channelName); + if (channel == null) { - if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); - return GetReleases(snapChannel.Name); + throw new Exception($"Unknown channel: {channelName}"); } - public ISnapAppChannelReleases GetReleases(string channelName) - { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - var channel = SnapApp.Channels.SingleOrDefault(x => x.Name == channelName); - if (channel == null) - { - throw new Exception($"Unknown channel: {channelName}"); - } + var deltaReleasesOlderThanOrEqualTo = this.Where(x => x.IsDelta && x.Channels.Contains(channelName) && x.Version <= version); + return new SnapAppChannelReleases(SnapApp, channel, deltaReleasesOlderThanOrEqualTo); + } - var snapReleases = Releases.Where(x => x.Channels.Contains(channelName)); - return new SnapAppChannelReleases(SnapApp, channel, snapReleases); - } + public ISnapAppChannelReleases GetReleases(SnapChannel snapChannel) + { + if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); + return GetReleases(snapChannel.Name); + } - public SnapRelease GetPreviousRelease(SnapChannel channel, [NotNull] SemanticVersion version) + public ISnapAppChannelReleases GetReleases(string channelName) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + var channel = SnapApp.Channels.SingleOrDefault(x => x.Name == channelName); + if (channel == null) { - if (channel == null) throw new ArgumentNullException(nameof(channel)); - if (version == null) throw new ArgumentNullException(nameof(version)); - return GetPreviousRelease(channel.Name, version); + throw new Exception($"Unknown channel: {channelName}"); } - public SnapRelease GetPreviousRelease(string channelName, [NotNull] SemanticVersion version) - { - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - if (version == null) throw new ArgumentNullException(nameof(version)); - return this.LastOrDefault(x => x.Channels.Contains(channelName) && x.Version < version); - } + var snapReleases = Releases.Where(x => x.Channels.Contains(channelName)); + return new SnapAppChannelReleases(SnapApp, channel, snapReleases); + } - public IEnumerator GetEnumerator() - { - foreach (var release in Releases) - { - yield return new SnapRelease(release); - } - } + public SnapRelease GetPreviousRelease(SnapChannel channel, [NotNull] SemanticVersion version) + { + if (channel == null) throw new ArgumentNullException(nameof(channel)); + if (version == null) throw new ArgumentNullException(nameof(version)); + return GetPreviousRelease(channel.Name, version); + } + + public SnapRelease GetPreviousRelease(string channelName, [NotNull] SemanticVersion version) + { + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + if (version == null) throw new ArgumentNullException(nameof(version)); + return this.LastOrDefault(x => x.Channels.Contains(channelName) && x.Version < version); + } - IEnumerator IEnumerable.GetEnumerator() + public IEnumerator GetEnumerator() + { + foreach (var release in Releases) { - return GetEnumerator(); + yield return new SnapRelease(release); } } -} + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} \ No newline at end of file diff --git a/src/Snap/Core/Models/SnapApps.cs b/src/Snap/Core/Models/SnapApps.cs index 12ec11eb..61662212 100644 --- a/src/Snap/Core/Models/SnapApps.cs +++ b/src/Snap/Core/Models/SnapApps.cs @@ -5,313 +5,312 @@ using JetBrains.Annotations; using YamlDotNet.Serialization; -namespace Snap.Core.Models +namespace Snap.Core.Models; + +public abstract class SnapsFeed +{ +} + +public sealed class SnapsNugetFeed : SnapsFeed { - public abstract class SnapsFeed + public string Name { get; set; } + + [UsedImplicitly] + public SnapsNugetFeed() { + } - public sealed class SnapsNugetFeed : SnapsFeed + public SnapsNugetFeed([NotNull] SnapsNugetFeed feed) { - public string Name { get; set; } + if (feed == null) throw new ArgumentNullException(nameof(feed)); + Name = feed.Name; + } - [UsedImplicitly] - public SnapsNugetFeed() - { - - } + public SnapsNugetFeed([NotNull] SnapNugetFeed feed) : this(new SnapsNugetFeed + { + Name = feed.Name + }) + { + if (feed == null) throw new ArgumentNullException(nameof(feed)); + } +} - public SnapsNugetFeed([NotNull] SnapsNugetFeed feed) - { - if (feed == null) throw new ArgumentNullException(nameof(feed)); - Name = feed.Name; - } +public sealed class SnapsHttpFeed : SnapsFeed +{ + public Uri Source { get; set; } - public SnapsNugetFeed([NotNull] SnapNugetFeed feed) : this(new SnapsNugetFeed - { - Name = feed.Name - }) - { - if (feed == null) throw new ArgumentNullException(nameof(feed)); - } + [UsedImplicitly] + public SnapsHttpFeed() + { + } - public sealed class SnapsHttpFeed : SnapsFeed + public SnapsHttpFeed([NotNull] SnapsHttpFeed feed) { - public Uri Source { get; set; } + if (feed == null) throw new ArgumentNullException(nameof(feed)); + Source = feed.Source; + } - [UsedImplicitly] - public SnapsHttpFeed() - { - - } + public SnapsHttpFeed([NotNull] SnapHttpFeed feed) : this(new SnapsHttpFeed + { + Source = feed.Source + }) + { + if (feed == null) throw new ArgumentNullException(nameof(feed)); + } +} - public SnapsHttpFeed([NotNull] SnapsHttpFeed feed) - { - if (feed == null) throw new ArgumentNullException(nameof(feed)); - Source = feed.Source; - } +public sealed class SnapsChannel +{ + public string Name { get; set; } + public SnapsNugetFeed PushFeed { get; set; } + public SnapsFeed UpdateFeed { get; set; } - public SnapsHttpFeed([NotNull] SnapHttpFeed feed) : this(new SnapsHttpFeed - { - Source = feed.Source - }) - { - if (feed == null) throw new ArgumentNullException(nameof(feed)); - } + [UsedImplicitly] + public SnapsChannel() + { + } - public sealed class SnapsChannel + internal SnapsChannel([NotNull] SnapChannel snapChannel) { - public string Name { get; set; } - public SnapsNugetFeed PushFeed { get; set; } - public SnapsFeed UpdateFeed { get; set; } + if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); + Name = snapChannel.Name; + PushFeed = new SnapsNugetFeed(snapChannel.PushFeed); - [UsedImplicitly] - public SnapsChannel() + UpdateFeed = snapChannel.UpdateFeed switch { - - } + SnapNugetFeed snapNugetFeed => new SnapsNugetFeed(snapNugetFeed), + SnapHttpFeed snapHttpFeed => new SnapsHttpFeed(snapHttpFeed), + _ => throw new NotSupportedException($"Unknown update feed type: {snapChannel.UpdateFeed?.GetType()}.") + }; + } - internal SnapsChannel([NotNull] SnapChannel snapChannel) - { - if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); - Name = snapChannel.Name; - PushFeed = new SnapsNugetFeed(snapChannel.PushFeed); - - UpdateFeed = snapChannel.UpdateFeed switch - { - SnapNugetFeed snapNugetFeed => new SnapsNugetFeed(snapNugetFeed), - SnapHttpFeed snapHttpFeed => new SnapsHttpFeed(snapHttpFeed), - _ => throw new NotSupportedException($"Unknown update feed type: {snapChannel.UpdateFeed?.GetType()}.") - }; - } - - public SnapsChannel([NotNull] SnapsChannel snapChannel) - { - if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); - Name = snapChannel.Name; - PushFeed = snapChannel.PushFeed; - UpdateFeed = snapChannel.UpdateFeed; - } + public SnapsChannel([NotNull] SnapsChannel snapChannel) + { + if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); + Name = snapChannel.Name; + PushFeed = snapChannel.PushFeed; + UpdateFeed = snapChannel.UpdateFeed; } +} - public sealed class SnapsTarget +public sealed class SnapsTarget +{ + public OSPlatform Os { get; set; } + public string Framework { get; set; } + public string Rid { get; set; } + public string Icon { get; set; } + public List Shortcuts { get; set; } + public List PersistentAssets { get; set; } + public List Installers { get; set; } + + [UsedImplicitly] + public SnapsTarget() { - public OSPlatform Os { get; set; } - public string Framework { get; set; } - public string Rid { get; set; } - public string Icon { get; set; } - public List Shortcuts { get; set; } - public List PersistentAssets { get; set; } - public List Installers { get; set; } - - [UsedImplicitly] - public SnapsTarget() - { - Shortcuts = new List(); - PersistentAssets = new List(); - Installers = new List(); - } + Shortcuts = new List(); + PersistentAssets = new List(); + Installers = new List(); + } - internal SnapsTarget([NotNull] SnapTarget target) - { - if (target == null) throw new ArgumentNullException(nameof(target)); - Os = target.Os; - Framework = target.Framework; - Rid = target.Rid; - Icon = target.Icon; - Shortcuts = target.Shortcuts; - PersistentAssets = target.PersistentAssets; - Installers = target.Installers; - } - - public SnapsTarget([NotNull] SnapsTarget target) - { - if (target == null) throw new ArgumentNullException(nameof(target)); - Os = target.Os; - Framework = target.Framework; - Rid = target.Rid; - Icon = target.Icon; - Shortcuts = target.Shortcuts; - PersistentAssets = target.PersistentAssets; - Installers = target.Installers; - } + internal SnapsTarget([NotNull] SnapTarget target) + { + if (target == null) throw new ArgumentNullException(nameof(target)); + Os = target.Os; + Framework = target.Framework; + Rid = target.Rid; + Icon = target.Icon; + Shortcuts = target.Shortcuts; + PersistentAssets = target.PersistentAssets; + Installers = target.Installers; } - public sealed class SnapsAppNuspec + public SnapsTarget([NotNull] SnapsTarget target) { - public string ReleaseNotes { get; set; } - public string Description { get; set; } - public string RepositoryUrl { get; set; } - public string RepositoryType { get; set; } - public string Authors { get; set; } + if (target == null) throw new ArgumentNullException(nameof(target)); + Os = target.Os; + Framework = target.Framework; + Rid = target.Rid; + Icon = target.Icon; + Shortcuts = target.Shortcuts; + PersistentAssets = target.PersistentAssets; + Installers = target.Installers; + } +} - public SnapsAppNuspec() - { +public sealed class SnapsAppNuspec +{ + public string ReleaseNotes { get; set; } + public string Description { get; set; } + public string RepositoryUrl { get; set; } + public string RepositoryType { get; set; } + public string Authors { get; set; } + + public SnapsAppNuspec() + { - } + } - public SnapsAppNuspec(SnapApp snapApp) - { - ReleaseNotes = snapApp.ReleaseNotes; - Description = snapApp.Description; - RepositoryUrl = snapApp.RepositoryUrl; - RepositoryType = snapApp.RepositoryType; - Authors = snapApp.Authors; - } - - public SnapsAppNuspec(SnapsAppNuspec nuspec) - { - ReleaseNotes = nuspec.ReleaseNotes; - Description = nuspec.Description; - RepositoryUrl = nuspec.RepositoryUrl; - RepositoryType = nuspec.RepositoryType; - Authors = nuspec.Authors; - } + public SnapsAppNuspec(SnapApp snapApp) + { + ReleaseNotes = snapApp.ReleaseNotes; + Description = snapApp.Description; + RepositoryUrl = snapApp.RepositoryUrl; + RepositoryType = snapApp.RepositoryType; + Authors = snapApp.Authors; } - public sealed class SnapsApp + public SnapsAppNuspec(SnapsAppNuspec nuspec) { - public string Id { get; set; } - [YamlMember(Alias = "installDirectory")] - public string InstallDirectoryName { get; set; } - [YamlMember(Alias = "main")] - public string MainExe { get; set; } - [YamlMember(Alias = "supervisorid")] - public string SuperVisorId { get; set; } - public List Channels { get; set; } - public SnapsTarget Target { get; set; } - public SnapsAppNuspec Nuspec { get; set; } - - [UsedImplicitly] - public SnapsApp() - { - Channels = new List(); - Nuspec = new SnapsAppNuspec(); - Target = new SnapsTarget(); - } + ReleaseNotes = nuspec.ReleaseNotes; + Description = nuspec.Description; + RepositoryUrl = nuspec.RepositoryUrl; + RepositoryType = nuspec.RepositoryType; + Authors = nuspec.Authors; + } +} - internal SnapsApp([NotNull] SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - Id = snapApp.Id; - InstallDirectoryName = snapApp.InstallDirectoryName; - MainExe = snapApp.MainExe; - SuperVisorId = snapApp.SuperVisorId; - Channels = snapApp.Channels.Select(x => x.Name).ToList(); - Target = new SnapsTarget(snapApp.Target); - Nuspec = new SnapsAppNuspec(snapApp); - } - - public SnapsApp([NotNull] SnapsApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - Id = snapApp.Id; - InstallDirectoryName = snapApp.InstallDirectoryName; - MainExe = snapApp.MainExe; - SuperVisorId = snapApp.SuperVisorId; - Channels = snapApp.Channels.Select(x => x).ToList(); - Target = new SnapsTarget(snapApp.Target); - Nuspec = new SnapsAppNuspec(snapApp.Nuspec); - } +public sealed class SnapsApp +{ + public string Id { get; set; } + [YamlMember(Alias = "installDirectory")] + public string InstallDirectoryName { get; set; } + [YamlMember(Alias = "main")] + public string MainExe { get; set; } + [YamlMember(Alias = "supervisorid")] + public string SuperVisorId { get; set; } + public List Channels { get; set; } + public SnapsTarget Target { get; set; } + public SnapsAppNuspec Nuspec { get; set; } + + [UsedImplicitly] + public SnapsApp() + { + Channels = new List(); + Nuspec = new SnapsAppNuspec(); + Target = new SnapsTarget(); } - public enum SnapAppsPackStrategy + internal SnapsApp([NotNull] SnapApp snapApp) { - [UsedImplicitly] none, - push + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + Id = snapApp.Id; + InstallDirectoryName = snapApp.InstallDirectoryName; + MainExe = snapApp.MainExe; + SuperVisorId = snapApp.SuperVisorId; + Channels = snapApp.Channels.Select(x => x.Name).ToList(); + Target = new SnapsTarget(snapApp.Target); + Nuspec = new SnapsAppNuspec(snapApp); } - public sealed class SnapAppsGeneric + public SnapsApp([NotNull] SnapsApp snapApp) { - public string Token { get; set; } - public string Artifacts { get; set; } - public string Packages { get; set; } - public string Nuspecs { get; set; } - public string Installers { get; set; } - public SnapAppsPackStrategy PackStrategy { get; set; } = SnapAppsPackStrategy.push; - - [UsedImplicitly] - public SnapAppsGeneric() - { - - } + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + Id = snapApp.Id; + InstallDirectoryName = snapApp.InstallDirectoryName; + MainExe = snapApp.MainExe; + SuperVisorId = snapApp.SuperVisorId; + Channels = snapApp.Channels.Select(x => x).ToList(); + Target = new SnapsTarget(snapApp.Target); + Nuspec = new SnapsAppNuspec(snapApp.Nuspec); + } +} - public SnapAppsGeneric([NotNull] SnapAppsGeneric snapAppsGeneric) - { - if (snapAppsGeneric == null) throw new ArgumentNullException(nameof(snapAppsGeneric)); - Artifacts = snapAppsGeneric.Artifacts; - Packages = snapAppsGeneric.Packages; - Nuspecs = snapAppsGeneric.Nuspecs; - Installers = snapAppsGeneric.Installers; - PackStrategy = snapAppsGeneric.PackStrategy; - } +public enum SnapAppsPackStrategy +{ + [UsedImplicitly] none, + push +} + +public sealed class SnapAppsGeneric +{ + public string Token { get; set; } + public string Artifacts { get; set; } + public string Packages { get; set; } + public string Nuspecs { get; set; } + public string Installers { get; set; } + public SnapAppsPackStrategy PackStrategy { get; set; } = SnapAppsPackStrategy.push; + + [UsedImplicitly] + public SnapAppsGeneric() + { + } - public sealed class SnapApps + public SnapAppsGeneric([NotNull] SnapAppsGeneric snapAppsGeneric) { - public int Schema { get; set; } - public SnapAppsGeneric Generic { get; set; } - public List Channels { get; set; } - public List Apps { get; set; } + if (snapAppsGeneric == null) throw new ArgumentNullException(nameof(snapAppsGeneric)); + Artifacts = snapAppsGeneric.Artifacts; + Packages = snapAppsGeneric.Packages; + Nuspecs = snapAppsGeneric.Nuspecs; + Installers = snapAppsGeneric.Installers; + PackStrategy = snapAppsGeneric.PackStrategy; + } +} - public SnapApps() - { - Channels = new List(); - Apps = new List(); - Generic = new SnapAppsGeneric(); - } +public sealed class SnapApps +{ + public int Schema { get; set; } + public SnapAppsGeneric Generic { get; set; } + public List Channels { get; set; } + public List Apps { get; set; } - internal SnapApps([NotNull] SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - Channels = snapApp.Channels.Select(x => new SnapsChannel(x)).ToList(); - Apps = new List { new(snapApp) }; - Generic = new SnapAppsGeneric(); - } + public SnapApps() + { + Channels = new List(); + Apps = new List(); + Generic = new SnapAppsGeneric(); + } - public SnapApps([NotNull] SnapApps snapApps) - { - if (snapApps == null) throw new ArgumentNullException(nameof(snapApps)); - Channels = snapApps.Channels.Select(x => new SnapsChannel(x)).ToList(); - Apps = snapApps.Apps.Select(x => new SnapsApp(x)).ToList(); - Generic = new SnapAppsGeneric(snapApps.Generic); - Schema = snapApps.Schema; - } - - public string BuildLockKey([NotNull] SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return $"{Generic.Token}-{snapApp.Id}"; - } + internal SnapApps([NotNull] SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + Channels = snapApp.Channels.Select(x => new SnapsChannel(x)).ToList(); + Apps = new List { new(snapApp) }; + Generic = new SnapAppsGeneric(); + } - public string BuildLockKey([NotNull] SnapsApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return $"{Generic.Token}-{snapApp.Id}"; - } + public SnapApps([NotNull] SnapApps snapApps) + { + if (snapApps == null) throw new ArgumentNullException(nameof(snapApps)); + Channels = snapApps.Channels.Select(x => new SnapsChannel(x)).ToList(); + Apps = snapApps.Apps.Select(x => new SnapsApp(x)).ToList(); + Generic = new SnapAppsGeneric(snapApps.Generic); + Schema = snapApps.Schema; + } - public IEnumerable GetRids([NotNull] SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return GetRids(snapApp.Id); - } + public string BuildLockKey([NotNull] SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return $"{Generic.Token}-{snapApp.Id}"; + } - public IEnumerable GetRids([NotNull] SnapsApp snapsApp) - { - if (snapsApp == null) throw new ArgumentNullException(nameof(snapsApp)); - return GetRids(snapsApp.Id); - } + public string BuildLockKey([NotNull] SnapsApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return $"{Generic.Token}-{snapApp.Id}"; + } - List GetRids([NotNull] string snapId) - { - if (snapId == null) throw new ArgumentNullException(nameof(snapId)); - return Apps.Where(x => x.Id == snapId) - .Select(x => x.Target.Rid) - .Distinct() - .ToList(); - } + public IEnumerable GetRids([NotNull] SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return GetRids(snapApp.Id); + } - } -} + public IEnumerable GetRids([NotNull] SnapsApp snapsApp) + { + if (snapsApp == null) throw new ArgumentNullException(nameof(snapsApp)); + return GetRids(snapsApp.Id); + } + + List GetRids([NotNull] string snapId) + { + if (snapId == null) throw new ArgumentNullException(nameof(snapId)); + return Apps.Where(x => x.Id == snapId) + .Select(x => x.Target.Rid) + .Distinct() + .ToList(); + } + +} \ No newline at end of file diff --git a/src/Snap/Core/Models/SnapAppsReleases.cs b/src/Snap/Core/Models/SnapAppsReleases.cs index b297e948..f6c6b065 100644 --- a/src/Snap/Core/Models/SnapAppsReleases.cs +++ b/src/Snap/Core/Models/SnapAppsReleases.cs @@ -9,156 +9,155 @@ using Snap.Core.MessagePack.Formatters; using Snap.Extensions; -namespace Snap.Core.Models +namespace Snap.Core.Models; + +[MessagePackObject] +public sealed class SnapAppsReleases : IEnumerable { - [MessagePackObject] - public sealed class SnapAppsReleases : IEnumerable + [Key(0)] + public List Releases { get; [UsedImplicitly] set; } + [IgnoreDataMember] + public SemanticVersion Version => SemanticVersion.Parse($"{DbVersion}.0.0"); + [Key(1)] + public DateTime LastWriteAccessUtc { get; set; } + [Key(2)] + public int DbVersion { get; set; } + [Key(3)] + public Guid PackId { get; set; } + [Key(4), MessagePackFormatter(typeof(SemanticVersionMessagePackFormatter))] + public SemanticVersion PackVersion { get; set; } + + [UsedImplicitly] + public SnapAppsReleases() { - [Key(0)] - public List Releases { get; [UsedImplicitly] set; } - [IgnoreDataMember] - public SemanticVersion Version => SemanticVersion.Parse($"{DbVersion}.0.0"); - [Key(1)] - public DateTime LastWriteAccessUtc { get; set; } - [Key(2)] - public int DbVersion { get; set; } - [Key(3)] - public Guid PackId { get; set; } - [Key(4), MessagePackFormatter(typeof(SemanticVersionMessagePackFormatter))] - public SemanticVersion PackVersion { get; set; } - - [UsedImplicitly] - public SnapAppsReleases() - { - Releases = new List(); - DbVersion = 0; - } + Releases = new List(); + DbVersion = 0; + } - public void Bump(int? overrideDbVersion = null) - { - var dbVersionCurrent = DbVersion; - DbVersion = overrideDbVersion ?? dbVersionCurrent + 1; - } + public void Bump(int? overrideDbVersion = null) + { + var dbVersionCurrent = DbVersion; + DbVersion = overrideDbVersion ?? dbVersionCurrent + 1; + } - internal ISnapAppReleases GetReleases([NotNull] SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var snapReleases = Releases.Where(x => x.Id == snapApp.Id && x.Target.Rid == snapApp.Target.Rid).Select(x => x); - return new SnapAppReleases(snapApp, snapReleases); - } + internal ISnapAppReleases GetReleases([NotNull] SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + var snapReleases = Releases.Where(x => x.Id == snapApp.Id && x.Target.Rid == snapApp.Target.Rid).Select(x => x); + return new SnapAppReleases(snapApp, snapReleases); + } - internal IEnumerable GetChannels([NotNull] SnapApp snapApp) + internal IEnumerable GetChannels([NotNull] SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + var channelNames = GetReleases(snapApp).SelectMany(x => x.Channels).Distinct(); + foreach (var channelName in channelNames) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var channelNames = GetReleases(snapApp).SelectMany(x => x.Channels).Distinct(); - foreach (var channelName in channelNames) - { - var channel = snapApp.Channels.SingleOrDefault(x => x.Name == channelName); - if(channel == null) continue; - yield return channel; - } + var channel = snapApp.Channels.SingleOrDefault(x => x.Name == channelName); + if(channel == null) continue; + yield return channel; } + } - internal int Gc([NotNull] SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return Releases.RemoveAll(x => x.Id == snapApp.Id && x.Target.Rid == snapApp.Target.Rid); - } + internal int Gc([NotNull] SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return Releases.RemoveAll(x => x.Id == snapApp.Id && x.Target.Rid == snapApp.Target.Rid); + } - internal ISnapAppChannelReleases GetReleases([NotNull] SnapApp snapApp, [NotNull] SnapChannel snapChannel) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); - return GetReleases(snapApp, snapChannel.Name); - } + internal ISnapAppChannelReleases GetReleases([NotNull] SnapApp snapApp, [NotNull] SnapChannel snapChannel) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); + return GetReleases(snapApp, snapChannel.Name); + } - internal ISnapAppChannelReleases GetReleases([NotNull] SnapApp snapApp, [NotNull] string channelName) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (channelName == null) throw new ArgumentNullException(nameof(channelName)); + internal ISnapAppChannelReleases GetReleases([NotNull] SnapApp snapApp, [NotNull] string channelName) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (channelName == null) throw new ArgumentNullException(nameof(channelName)); - var channel = snapApp.Channels.SingleOrDefault(x => x.Name == channelName); - if (channel == null) - { - throw new Exception($"Unknown channel: {channelName}"); - } - - var snapAppReleases = GetReleases(snapApp); - var snapReleasesForChannel = snapAppReleases.Where(x => x.Channels.Contains(channelName)).Select(x => x); - return new SnapAppChannelReleases(snapApp, channel, snapReleasesForChannel); - } - - internal ISnapAppReleases GetReleases([NotNull] SnapApp snapApp, [NotNull] Func filterFunc) + var channel = snapApp.Channels.SingleOrDefault(x => x.Name == channelName); + if (channel == null) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (filterFunc == null) throw new ArgumentNullException(nameof(filterFunc)); - var releases = Releases.Where(x => x.Id == snapApp.Id && filterFunc(x)).ToList(); - return new SnapAppReleases(snapApp, releases); + throw new Exception($"Unknown channel: {channelName}"); } - internal ISnapAppReleases GetMostRecentReleases([NotNull] SnapApp snapApp, [NotNull] Func filterFunc) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (filterFunc == null) throw new ArgumentNullException(nameof(filterFunc)); - var snapAppReleases = Releases.Where(x => x.Id == snapApp.Id).ToList(); - - var mostRecentRelease = snapAppReleases.LastOrDefault(); - if (mostRecentRelease == null) - { - return new SnapAppReleases(snapApp, new List()); - } - - var mostRecentReleaseForRid = snapAppReleases - .Where(x => x.Version == mostRecentRelease.Version && filterFunc(x)).ToList(); + var snapAppReleases = GetReleases(snapApp); + var snapReleasesForChannel = snapAppReleases.Where(x => x.Channels.Contains(channelName)).Select(x => x); + return new SnapAppChannelReleases(snapApp, channel, snapReleasesForChannel); + } + + internal ISnapAppReleases GetReleases([NotNull] SnapApp snapApp, [NotNull] Func filterFunc) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (filterFunc == null) throw new ArgumentNullException(nameof(filterFunc)); + var releases = Releases.Where(x => x.Id == snapApp.Id && filterFunc(x)).ToList(); + return new SnapAppReleases(snapApp, releases); + } - return new SnapAppReleases(snapApp, mostRecentReleaseForRid); - } + internal ISnapAppReleases GetMostRecentReleases([NotNull] SnapApp snapApp, [NotNull] Func filterFunc) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (filterFunc == null) throw new ArgumentNullException(nameof(filterFunc)); + var snapAppReleases = Releases.Where(x => x.Id == snapApp.Id).ToList(); - internal SnapRelease GetMostRecentRelease([NotNull] SnapApp snapApp, [NotNull] SnapChannel channel) + var mostRecentRelease = snapAppReleases.LastOrDefault(); + if (mostRecentRelease == null) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (channel == null) throw new ArgumentNullException(nameof(channel)); - return GetMostRecentRelease(snapApp, channel.Name); + return new SnapAppReleases(snapApp, new List()); } + + var mostRecentReleaseForRid = snapAppReleases + .Where(x => x.Version == mostRecentRelease.Version && filterFunc(x)).ToList(); + + return new SnapAppReleases(snapApp, mostRecentReleaseForRid); + } + + internal SnapRelease GetMostRecentRelease([NotNull] SnapApp snapApp, [NotNull] SnapChannel channel) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (channel == null) throw new ArgumentNullException(nameof(channel)); + return GetMostRecentRelease(snapApp, channel.Name); + } - internal SnapRelease GetMostRecentRelease([NotNull] SnapApp snapApp, string channel) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return GetReleases(snapApp, channel).GetMostRecentRelease(); - } + internal SnapRelease GetMostRecentRelease([NotNull] SnapApp snapApp, string channel) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return GetReleases(snapApp, channel).GetMostRecentRelease(); + } - public void Add([NotNull] SnapRelease snapRelease) + public void Add([NotNull] SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + var existingRelease = Releases.SingleOrDefault(x => string.Equals(x.BuildNugetFilename(), snapRelease.Filename)); + if(existingRelease != null) { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - var existingRelease = Releases.SingleOrDefault(x => string.Equals(x.BuildNugetFilename(), snapRelease.Filename)); - if(existingRelease != null) - { - throw new Exception($"Release already exists: {existingRelease.BuildNugetFilename()}"); - } - Releases.Add(snapRelease); + throw new Exception($"Release already exists: {existingRelease.BuildNugetFilename()}"); } + Releases.Add(snapRelease); + } - public int Demote(ISnapAppReleases releases) + public int Demote(ISnapAppReleases releases) + { + var releasesRemoved = Releases.RemoveAll(snapRelease => + releases.Any(snapDemotedRelease => snapDemotedRelease.Filename == snapRelease.Filename)); + if (releasesRemoved <= 0) { - var releasesRemoved = Releases.RemoveAll(snapRelease => - releases.Any(snapDemotedRelease => snapDemotedRelease.Filename == snapRelease.Filename)); - if (releasesRemoved <= 0) - { - return 0; - } - - Releases = Releases.OrderBy(x => x.Version, new VersionComparer(VersionComparison.Default)).ToList(); - return releasesRemoved; + return 0; } - public IEnumerator GetEnumerator() - { - return Releases.GetEnumerator(); - } + Releases = Releases.OrderBy(x => x.Version, new VersionComparer(VersionComparison.Default)).ToList(); + return releasesRemoved; + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public IEnumerator GetEnumerator() + { + return Releases.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Models/SnapRelease.cs b/src/Snap/Core/Models/SnapRelease.cs index 0ac64246..a83fe94b 100644 --- a/src/Snap/Core/Models/SnapRelease.cs +++ b/src/Snap/Core/Models/SnapRelease.cs @@ -7,108 +7,107 @@ using YamlDotNet.Serialization; using Snap.Core.MessagePack.Formatters; -namespace Snap.Core.Models +namespace Snap.Core.Models; + +[MessagePackObject] +public class SnapRelease { - [MessagePackObject] - public class SnapRelease - { - [Key(0)] - public string Id { get; set; } - [Key(1)] - public string UpstreamId { get; set; } - [Key(2)] - [MessagePackFormatter(typeof(SemanticVersionMessagePackFormatter))] - public SemanticVersion Version { get; set; } - [Key(3)] - public List Channels { get; set; } - [Key(4)] - public SnapTarget Target { get; set; } - [Key(5)] - public bool IsGenesis { get; set; } - [Key(6)] - public bool IsFull { get; set; } - [YamlIgnore] - [Key(7)] - public bool IsDelta => !IsGenesis && !IsFull; - [Key(8)] - public string Filename { get; set; } - [Key(9)] - public long FullFilesize { get; set; } - [Key(10)] - public string FullSha256Checksum { get; set; } - [Key(11)] - public long DeltaFilesize { get; set; } - [Key(12)] - public string DeltaSha256Checksum { get; set; } - [Key(13)] - public List New { get; set; } - [Key(14)] - public List Modified { get; set; } - [Key(15)] - public List Unmodified { get; set; } - [Key(16)] - public List Deleted { get; set; } - [Key(17)] - public List Files { get; set; } - [Key(18)] - public DateTime CreatedDateUtc { get; set; } - [Key(19)] - public string ReleaseNotes { get; set; } - [Key(20)] - public bool Gc { get; set; } + [Key(0)] + public string Id { get; set; } + [Key(1)] + public string UpstreamId { get; set; } + [Key(2)] + [MessagePackFormatter(typeof(SemanticVersionMessagePackFormatter))] + public SemanticVersion Version { get; set; } + [Key(3)] + public List Channels { get; set; } + [Key(4)] + public SnapTarget Target { get; set; } + [Key(5)] + public bool IsGenesis { get; set; } + [Key(6)] + public bool IsFull { get; set; } + [YamlIgnore] + [Key(7)] + public bool IsDelta => !IsGenesis && !IsFull; + [Key(8)] + public string Filename { get; set; } + [Key(9)] + public long FullFilesize { get; set; } + [Key(10)] + public string FullSha256Checksum { get; set; } + [Key(11)] + public long DeltaFilesize { get; set; } + [Key(12)] + public string DeltaSha256Checksum { get; set; } + [Key(13)] + public List New { get; set; } + [Key(14)] + public List Modified { get; set; } + [Key(15)] + public List Unmodified { get; set; } + [Key(16)] + public List Deleted { get; set; } + [Key(17)] + public List Files { get; set; } + [Key(18)] + public DateTime CreatedDateUtc { get; set; } + [Key(19)] + public string ReleaseNotes { get; set; } + [Key(20)] + public bool Gc { get; set; } - [UsedImplicitly] - public SnapRelease() - { - Channels = new List(); - New = new List(); - Modified = new List(); - Unmodified = new List(); - Deleted = new List(); - Files = new List(); - } + [UsedImplicitly] + public SnapRelease() + { + Channels = new List(); + New = new List(); + Modified = new List(); + Unmodified = new List(); + Deleted = new List(); + Files = new List(); + } - public SnapRelease([NotNull] SnapRelease release) : this() - { - if (release == null) throw new ArgumentNullException(nameof(release)); - Id = release.Id; - UpstreamId = release.UpstreamId; - Version = release.Version; - Channels = release.Channels; - Target = new SnapTarget(release.Target); - IsGenesis = release.IsGenesis; - IsFull = release.IsFull; - Filename = release.Filename; - FullFilesize = release.FullFilesize; - FullSha256Checksum = release.FullSha256Checksum; - DeltaFilesize = release.DeltaFilesize; - DeltaSha256Checksum = release.DeltaSha256Checksum; - CreatedDateUtc = release.CreatedDateUtc; - ReleaseNotes = release.ReleaseNotes; - Gc = release.Gc; + public SnapRelease([NotNull] SnapRelease release) : this() + { + if (release == null) throw new ArgumentNullException(nameof(release)); + Id = release.Id; + UpstreamId = release.UpstreamId; + Version = release.Version; + Channels = release.Channels; + Target = new SnapTarget(release.Target); + IsGenesis = release.IsGenesis; + IsFull = release.IsFull; + Filename = release.Filename; + FullFilesize = release.FullFilesize; + FullSha256Checksum = release.FullSha256Checksum; + DeltaFilesize = release.DeltaFilesize; + DeltaSha256Checksum = release.DeltaSha256Checksum; + CreatedDateUtc = release.CreatedDateUtc; + ReleaseNotes = release.ReleaseNotes; + Gc = release.Gc; - Files = release.Files.Select(x => new SnapReleaseChecksum(x)).ToList(); - New = release.New.Select(x => new SnapReleaseChecksum(x)).ToList(); - Modified = release.Modified.Select(x => new SnapReleaseChecksum(x)).ToList(); - Unmodified = release.Unmodified.ToList(); - Deleted = release.Deleted.ToList(); - } - - public void Sort() - { - Files = Files.OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()).ToList(); - New = New.OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()).ToList(); - Modified = Modified.OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()).ToList(); - Unmodified = Unmodified.OrderBy(x => x, new OrdinalIgnoreCaseComparer()).ToList(); - Deleted = Deleted.OrderBy(x => x, new OrdinalIgnoreCaseComparer()).ToList(); - } + Files = release.Files.Select(x => new SnapReleaseChecksum(x)).ToList(); + New = release.New.Select(x => new SnapReleaseChecksum(x)).ToList(); + Modified = release.Modified.Select(x => new SnapReleaseChecksum(x)).ToList(); + Unmodified = release.Unmodified.ToList(); + Deleted = release.Deleted.ToList(); } + + public void Sort() + { + Files = Files.OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()).ToList(); + New = New.OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()).ToList(); + Modified = Modified.OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()).ToList(); + Unmodified = Unmodified.OrderBy(x => x, new OrdinalIgnoreCaseComparer()).ToList(); + Deleted = Deleted.OrderBy(x => x, new OrdinalIgnoreCaseComparer()).ToList(); + } +} - internal class OrdinalIgnoreCaseComparer : IComparer +internal class OrdinalIgnoreCaseComparer : IComparer +{ + public int Compare(string x, string y) { - public int Compare(string x, string y) - { - return string.Compare(x, y, StringComparison.OrdinalIgnoreCase); - } - } -} + return string.Compare(x, y, StringComparison.OrdinalIgnoreCase); + } +} \ No newline at end of file diff --git a/src/Snap/Core/Models/SnapReleaseChecksum.cs b/src/Snap/Core/Models/SnapReleaseChecksum.cs index d20c212c..67e31cac 100644 --- a/src/Snap/Core/Models/SnapReleaseChecksum.cs +++ b/src/Snap/Core/Models/SnapReleaseChecksum.cs @@ -3,45 +3,44 @@ using MessagePack; using YamlDotNet.Serialization; -namespace Snap.Core.Models +namespace Snap.Core.Models; + +[MessagePackObject] +public sealed class SnapReleaseChecksum { - [MessagePackObject] - public sealed class SnapReleaseChecksum + [Key(0)] + public string NuspecTargetPath { get; set; } + [YamlIgnore, IgnoreMember] + public string Filename { - [Key(0)] - public string NuspecTargetPath { get; set; } - [YamlIgnore, IgnoreMember] - public string Filename + get { - get - { - if (NuspecTargetPath == null) return null; - var lastIndexOfSlash = NuspecTargetPath.LastIndexOf("/", StringComparison.Ordinal); - return lastIndexOfSlash != -1 ? NuspecTargetPath[(lastIndexOfSlash + 1)..] : null; - } + if (NuspecTargetPath == null) return null; + var lastIndexOfSlash = NuspecTargetPath.LastIndexOf("/", StringComparison.Ordinal); + return lastIndexOfSlash != -1 ? NuspecTargetPath[(lastIndexOfSlash + 1)..] : null; } - [Key(1)] - public string FullSha256Checksum { get; set; } - [Key(2)] - public long FullFilesize { get; set; } - [Key(3)] - public string DeltaSha256Checksum { get; set; } - [Key(4)] - public long DeltaFilesize { get; set; } + } + [Key(1)] + public string FullSha256Checksum { get; set; } + [Key(2)] + public long FullFilesize { get; set; } + [Key(3)] + public string DeltaSha256Checksum { get; set; } + [Key(4)] + public long DeltaFilesize { get; set; } - [UsedImplicitly] - public SnapReleaseChecksum() - { - } + [UsedImplicitly] + public SnapReleaseChecksum() + { + } - public SnapReleaseChecksum([NotNull] SnapReleaseChecksum checksum) - { - if (checksum == null) throw new ArgumentNullException(nameof(checksum)); - NuspecTargetPath = checksum.NuspecTargetPath; - FullSha256Checksum = checksum.FullSha256Checksum; - FullFilesize = checksum.FullFilesize; - DeltaSha256Checksum = checksum.DeltaSha256Checksum; - DeltaFilesize = checksum.DeltaFilesize; - } + public SnapReleaseChecksum([NotNull] SnapReleaseChecksum checksum) + { + if (checksum == null) throw new ArgumentNullException(nameof(checksum)); + NuspecTargetPath = checksum.NuspecTargetPath; + FullSha256Checksum = checksum.FullSha256Checksum; + FullFilesize = checksum.FullFilesize; + DeltaSha256Checksum = checksum.DeltaSha256Checksum; + DeltaFilesize = checksum.DeltaFilesize; } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Resources/EmbeddedResource.cs b/src/Snap/Core/Resources/EmbeddedResource.cs index 7981e669..e73e090f 100644 --- a/src/Snap/Core/Resources/EmbeddedResource.cs +++ b/src/Snap/Core/Resources/EmbeddedResource.cs @@ -4,109 +4,108 @@ using System.Linq; using System.Reflection; -namespace Snap.Core.Resources +namespace Snap.Core.Resources; + +internal interface IEmbedResources : IDisposable { - internal interface IEmbedResources : IDisposable + IEnumerable Resources { get; } + void AddFromTypeRoot(Type typeRoot, Func filterFn = null); + EmbeddedResource Find(Type typeRoot, string filename); +} + +internal interface IEmbeddedResource : IDisposable +{ + Type TypeRoot { get; } + Assembly Assembly { get; } + MemoryStream Stream { get; } + string Filename { get; set; } +} + +internal sealed class EmbeddedResource : IEmbeddedResource +{ + public Type TypeRoot { get; } + public Assembly Assembly => TypeRoot.Assembly; + public MemoryStream Stream { get; } + public string Filename { get; set; } + + public EmbeddedResource(Type typeRoot, MemoryStream stream, string filename) { - IEnumerable Resources { get; } - void AddFromTypeRoot(Type typeRoot, Func filterFn = null); - EmbeddedResource Find(Type typeRoot, string filename); + if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(filename)); + TypeRoot = typeRoot ?? throw new ArgumentNullException(nameof(typeRoot)); + Stream = stream ?? throw new ArgumentNullException(nameof(stream)); + Filename = filename; } - internal interface IEmbeddedResource : IDisposable + public void Dispose() { - Type TypeRoot { get; } - Assembly Assembly { get; } - MemoryStream Stream { get; } - string Filename { get; set; } + Stream?.Dispose(); } +} - internal sealed class EmbeddedResource : IEmbeddedResource - { - public Type TypeRoot { get; } - public Assembly Assembly => TypeRoot.Assembly; - public MemoryStream Stream { get; } - public string Filename { get; set; } +internal abstract class EmbeddedResources : IEmbedResources +{ + readonly List _resources; - public EmbeddedResource(Type typeRoot, MemoryStream stream, string filename) - { - if (string.IsNullOrWhiteSpace(filename)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(filename)); - TypeRoot = typeRoot ?? throw new ArgumentNullException(nameof(typeRoot)); - Stream = stream ?? throw new ArgumentNullException(nameof(stream)); - Filename = filename; - } + public IEnumerable Resources => _resources.AsReadOnly(); - public void Dispose() - { - Stream?.Dispose(); - } + protected internal EmbeddedResources() + { + _resources = new List(); } - internal abstract class EmbeddedResources : IEmbedResources + public EmbeddedResource Find(Type typeRoot, string filename) { - readonly List _resources; - - public IEnumerable Resources => _resources.AsReadOnly(); + if (typeRoot == null) throw new ArgumentNullException(nameof(typeRoot)); + return _resources.SingleOrDefault(x => x.TypeRoot == typeRoot && string.Equals(filename, x.Filename)); + } - protected internal EmbeddedResources() + public void AddFromTypeRoot(Type typeRoot, Func filterFn = null) + { + var typeRootNamespace = typeRoot?.FullName?.Substring(0, typeRoot.FullName.Length - 1 - typeRoot.Name.Length); + if (string.IsNullOrWhiteSpace(typeRootNamespace)) { - _resources = new List(); + return; } - public EmbeddedResource Find(Type typeRoot, string filename) + foreach (var resource in typeRoot.Assembly.GetManifestResourceNames().Where(resource => { - if (typeRoot == null) throw new ArgumentNullException(nameof(typeRoot)); - return _resources.SingleOrDefault(x => x.TypeRoot == typeRoot && string.Equals(filename, x.Filename)); - } + if (!resource.StartsWith(typeRootNamespace)) + { + return false; + } - public void AddFromTypeRoot(Type typeRoot, Func filterFn = null) + return filterFn == null || filterFn(resource); + })) { - var typeRootNamespace = typeRoot?.FullName?.Substring(0, typeRoot.FullName.Length - 1 - typeRoot.Name.Length); - if (string.IsNullOrWhiteSpace(typeRootNamespace)) + using var manifestResourceStream = typeRoot.Assembly.GetManifestResourceStream(resource); + if (manifestResourceStream == null) { - return; + continue; } - foreach (var resource in typeRoot.Assembly.GetManifestResourceNames().Where(resource => - { - if (!resource.StartsWith(typeRootNamespace)) - { - return false; - } + using var binaryReader = new BinaryReader(manifestResourceStream); + var embededResourceStream = new MemoryStream(); - return filterFn == null || filterFn(resource); - })) + var buffer = new byte[81920]; + int bytesRead; + while ((bytesRead = binaryReader.Read(buffer, 0, buffer.Length)) > 0) { - using var manifestResourceStream = typeRoot.Assembly.GetManifestResourceStream(resource); - if (manifestResourceStream == null) - { - continue; - } - - using var binaryReader = new BinaryReader(manifestResourceStream); - var embededResourceStream = new MemoryStream(); - - var buffer = new byte[81920]; - int bytesRead; - while ((bytesRead = binaryReader.Read(buffer, 0, buffer.Length)) > 0) - { - embededResourceStream.Write(buffer, 0, bytesRead); - } + embededResourceStream.Write(buffer, 0, bytesRead); + } - embededResourceStream.Seek(0, SeekOrigin.Begin); + embededResourceStream.Seek(0, SeekOrigin.Begin); - _resources.Add(new EmbeddedResource(typeRoot, embededResourceStream, resource[(typeRootNamespace.Length + 1)..])); - } + _resources.Add(new EmbeddedResource(typeRoot, embededResourceStream, resource[(typeRootNamespace.Length + 1)..])); } + } - public void Dispose() + public void Dispose() + { + foreach (var embeddedResource in _resources) { - foreach (var embeddedResource in _resources) - { - embeddedResource.Dispose(); - } - - _resources.Clear(); + embeddedResource.Dispose(); } + + _resources.Clear(); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Resources/SnapEmbeddedResources.cs b/src/Snap/Core/Resources/SnapEmbeddedResources.cs index bf901436..bd79dae7 100644 --- a/src/Snap/Core/Resources/SnapEmbeddedResources.cs +++ b/src/Snap/Core/Resources/SnapEmbeddedResources.cs @@ -8,290 +8,289 @@ using Snap.Extensions; using Snap.Resources; -namespace Snap.Core.Resources +namespace Snap.Core.Resources; + +internal interface ISnapEmbeddedResources { - internal interface ISnapEmbeddedResources - { - [UsedImplicitly] - bool IsOptimized { get; } - MemoryStream CoreRunWindowsX86 { get; } - MemoryStream CoreRunWindowsX64 { get; } - MemoryStream CoreRunLinuxX64 { get; } - MemoryStream CoreRunLinuxArm64 { get; } - MemoryStream CoreRunLibWindowsX86 { get; } - MemoryStream CoreRunLibWindowsX64 { get; } - MemoryStream CoreRunLibLinuxX64 { get; } - MemoryStream CoreRunLibLinuxArm64 { get; } - (MemoryStream memoryStream, string filename, OSPlatform osPlatform) GetCoreRunForSnapApp(SnapApp snapApp, - ISnapFilesystem snapFilesystem, ICoreRunLib coreRunLib); - string GetCoreRunExeFilenameForSnapApp(SnapApp snapApp); - string GetCoreRunExeFilename(string assemblyName, OSPlatform osPlatform); - Task ExtractCoreRunLibAsync(ISnapFilesystem filesystem, ISnapCryptoProvider snapCryptoProvider, - string workingDirectory, OSPlatform osPlatform); - } + [UsedImplicitly] + bool IsOptimized { get; } + MemoryStream CoreRunWindowsX86 { get; } + MemoryStream CoreRunWindowsX64 { get; } + MemoryStream CoreRunLinuxX64 { get; } + MemoryStream CoreRunLinuxArm64 { get; } + MemoryStream CoreRunLibWindowsX86 { get; } + MemoryStream CoreRunLibWindowsX64 { get; } + MemoryStream CoreRunLibLinuxX64 { get; } + MemoryStream CoreRunLibLinuxArm64 { get; } + (MemoryStream memoryStream, string filename, OSPlatform osPlatform) GetCoreRunForSnapApp(SnapApp snapApp, + ISnapFilesystem snapFilesystem, ICoreRunLib coreRunLib); + string GetCoreRunExeFilenameForSnapApp(SnapApp snapApp); + string GetCoreRunExeFilename(string assemblyName, OSPlatform osPlatform); + Task ExtractCoreRunLibAsync(ISnapFilesystem filesystem, ISnapCryptoProvider snapCryptoProvider, + string workingDirectory, OSPlatform osPlatform); +} - internal sealed class SnapEmbeddedResources : EmbeddedResources, ISnapEmbeddedResources - { - const string CoreRunWindowsX86Filename = "corerun-win-x86.exe"; - const string CoreRunLibWindowsX86Filename = "libcorerun-win-x86.dll"; +internal sealed class SnapEmbeddedResources : EmbeddedResources, ISnapEmbeddedResources +{ + const string CoreRunWindowsX86Filename = "corerun-win-x86.exe"; + const string CoreRunLibWindowsX86Filename = "libcorerun-win-x86.dll"; - const string CoreRunWindowsX64Filename = "corerun-win-x64.exe"; - const string CoreRunLibWindowsX64Filename = "libcorerun-win-x64.dll"; + const string CoreRunWindowsX64Filename = "corerun-win-x64.exe"; + const string CoreRunLibWindowsX64Filename = "libcorerun-win-x64.dll"; - const string CoreRunLinuxX64Filename = "corerun-linux-x64"; - const string CoreRunLibLinuxX64Filename = "libcorerun-linux-x64.so"; + const string CoreRunLinuxX64Filename = "corerun-linux-x64"; + const string CoreRunLibLinuxX64Filename = "libcorerun-linux-x64.so"; - const string CoreRunLinuxArm64Filename = "corerun-linux-arm64"; - const string CoreRunLibLinuxArm64Filename = "libcorerun-linux-arm64.so"; + const string CoreRunLinuxArm64Filename = "corerun-linux-arm64"; + const string CoreRunLibLinuxArm64Filename = "libcorerun-linux-arm64.so"; - readonly EmbeddedResource _coreRunWindowsX86; - readonly EmbeddedResource _coreRunLibWindowsX86; + readonly EmbeddedResource _coreRunWindowsX86; + readonly EmbeddedResource _coreRunLibWindowsX86; - readonly EmbeddedResource _coreRunWindowsX64; - readonly EmbeddedResource _coreRunLibWindowsX64; + readonly EmbeddedResource _coreRunWindowsX64; + readonly EmbeddedResource _coreRunLibWindowsX64; - readonly EmbeddedResource _coreRunLinuxX64; - readonly EmbeddedResource _coreRunLibLinuxX64; + readonly EmbeddedResource _coreRunLinuxX64; + readonly EmbeddedResource _coreRunLibLinuxX64; - readonly EmbeddedResource _coreRunLinuxArm64; - readonly EmbeddedResource _coreRunLibLinuxArm64; + readonly EmbeddedResource _coreRunLinuxArm64; + readonly EmbeddedResource _coreRunLibLinuxArm64; - [UsedImplicitly] - public bool IsOptimized { get; } + [UsedImplicitly] + public bool IsOptimized { get; } - public MemoryStream CoreRunWindowsX86 + public MemoryStream CoreRunWindowsX86 + { + get { - get + if (_coreRunWindowsX86 == null) { - if (_coreRunWindowsX86 == null) - { - throw new FileNotFoundException($"{CoreRunWindowsX86Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_coreRunWindowsX86.Stream.ToArray()); + throw new FileNotFoundException($"{CoreRunWindowsX86Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_coreRunWindowsX86.Stream.ToArray()); } + } - public MemoryStream CoreRunLibWindowsX86 + public MemoryStream CoreRunLibWindowsX86 + { + get { - get + if (_coreRunLibWindowsX86 == null) { - if (_coreRunLibWindowsX86 == null) - { - throw new FileNotFoundException($"{CoreRunLibWindowsX86Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_coreRunLibWindowsX86.Stream.ToArray()); + throw new FileNotFoundException($"{CoreRunLibWindowsX86Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_coreRunLibWindowsX86.Stream.ToArray()); } + } - public MemoryStream CoreRunWindowsX64 + public MemoryStream CoreRunWindowsX64 + { + get { - get + if (_coreRunWindowsX64 == null) { - if (_coreRunWindowsX64 == null) - { - throw new FileNotFoundException($"{CoreRunWindowsX64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_coreRunWindowsX64.Stream.ToArray()); + throw new FileNotFoundException($"{CoreRunWindowsX64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_coreRunWindowsX64.Stream.ToArray()); } + } - public MemoryStream CoreRunLibWindowsX64 + public MemoryStream CoreRunLibWindowsX64 + { + get { - get + if (_coreRunLibWindowsX64 == null) { - if (_coreRunLibWindowsX64 == null) - { - throw new FileNotFoundException($"{CoreRunLibWindowsX64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_coreRunLibWindowsX64.Stream.ToArray()); + throw new FileNotFoundException($"{CoreRunLibWindowsX64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_coreRunLibWindowsX64.Stream.ToArray()); } + } - public MemoryStream CoreRunLinuxX64 + public MemoryStream CoreRunLinuxX64 + { + get { - get - { - if (_coreRunLinuxX64 == null) - { - throw new FileNotFoundException($"{CoreRunLinuxX64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_coreRunLinuxX64.Stream.ToArray()); + if (_coreRunLinuxX64 == null) + { + throw new FileNotFoundException($"{CoreRunLinuxX64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_coreRunLinuxX64.Stream.ToArray()); } + } - public MemoryStream CoreRunLibLinuxX64 + public MemoryStream CoreRunLibLinuxX64 + { + get { - get + if (_coreRunLibLinuxX64 == null) { - if (_coreRunLibLinuxX64 == null) - { - throw new FileNotFoundException($"{CoreRunLibLinuxX64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_coreRunLibLinuxX64.Stream.ToArray()); + throw new FileNotFoundException($"{CoreRunLibLinuxX64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_coreRunLibLinuxX64.Stream.ToArray()); } + } - public MemoryStream CoreRunLinuxArm64 + public MemoryStream CoreRunLinuxArm64 + { + get { - get - { - - if (_coreRunLinuxArm64 == null) - { - throw new FileNotFoundException($"{CoreRunLinuxArm64Filename} was not found in current assembly resources manifest"); - } - return new MemoryStream(_coreRunLinuxArm64.Stream.ToArray()); + if (_coreRunLinuxArm64 == null) + { + throw new FileNotFoundException($"{CoreRunLinuxArm64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_coreRunLinuxArm64.Stream.ToArray()); } + } - public MemoryStream CoreRunLibLinuxArm64 + public MemoryStream CoreRunLibLinuxArm64 + { + get { - get + if (_coreRunLibLinuxArm64 == null) { - if (_coreRunLibLinuxArm64 == null) - { - throw new FileNotFoundException($"{CoreRunLibLinuxArm64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_coreRunLibLinuxArm64.Stream.ToArray()); + throw new FileNotFoundException($"{CoreRunLibLinuxArm64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_coreRunLibLinuxArm64.Stream.ToArray()); } + } - internal SnapEmbeddedResources() - { - AddFromTypeRoot(typeof(SnapEmbeddedResourcesTypeRoot)); + internal SnapEmbeddedResources() + { + AddFromTypeRoot(typeof(SnapEmbeddedResourcesTypeRoot)); - _coreRunWindowsX86 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunWindowsX86Filename}"); - _coreRunLibWindowsX86 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLibWindowsX86Filename}"); + _coreRunWindowsX86 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunWindowsX86Filename}"); + _coreRunLibWindowsX86 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLibWindowsX86Filename}"); - _coreRunWindowsX64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunWindowsX64Filename}"); - _coreRunLibWindowsX64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLibWindowsX64Filename}"); + _coreRunWindowsX64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunWindowsX64Filename}"); + _coreRunLibWindowsX64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLibWindowsX64Filename}"); - _coreRunLinuxX64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLinuxX64Filename}"); - _coreRunLibLinuxX64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLibLinuxX64Filename}"); + _coreRunLinuxX64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLinuxX64Filename}"); + _coreRunLibLinuxX64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLibLinuxX64Filename}"); - _coreRunLinuxArm64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLinuxArm64Filename}"); - _coreRunLibLinuxArm64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLibLinuxArm64Filename}"); - } + _coreRunLinuxArm64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLinuxArm64Filename}"); + _coreRunLibLinuxArm64 = Resources.SingleOrDefault(x => x.Filename == $"corerun.{CoreRunLibLinuxArm64Filename}"); + } - public (MemoryStream memoryStream, string filename, OSPlatform osPlatform) GetCoreRunForSnapApp([NotNull] SnapApp snapApp, - [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ICoreRunLib coreRunLib) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); - if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); + public (MemoryStream memoryStream, string filename, OSPlatform osPlatform) GetCoreRunForSnapApp([NotNull] SnapApp snapApp, + [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ICoreRunLib coreRunLib) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); + if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); - MemoryStream coreRunStream; - OSPlatform osPlatform; + MemoryStream coreRunStream; + OSPlatform osPlatform; - if (snapApp.Target.Os == OSPlatform.Windows) - { - coreRunStream = snapApp.Target.Rid == "win-x86" ? CoreRunWindowsX86 : CoreRunWindowsX64; - osPlatform = OSPlatform.Windows; - } else if (snapApp.Target.Os == OSPlatform.Linux) - { - coreRunStream = snapApp.Target.Rid == "linux-x64" ? CoreRunLinuxX64 : CoreRunLinuxArm64; - osPlatform = OSPlatform.Linux; - } - else - { - throw new PlatformNotSupportedException(); - } + if (snapApp.Target.Os == OSPlatform.Windows) + { + coreRunStream = snapApp.Target.Rid == "win-x86" ? CoreRunWindowsX86 : CoreRunWindowsX64; + osPlatform = OSPlatform.Windows; + } else if (snapApp.Target.Os == OSPlatform.Linux) + { + coreRunStream = snapApp.Target.Rid == "linux-x64" ? CoreRunLinuxX64 : CoreRunLinuxArm64; + osPlatform = OSPlatform.Linux; + } + else + { + throw new PlatformNotSupportedException(); + } - var coreRunFilename = GetCoreRunExeFilenameForSnapApp(snapApp); + var coreRunFilename = GetCoreRunExeFilenameForSnapApp(snapApp); - return (coreRunStream, coreRunFilename, osPlatform); - } + return (coreRunStream, coreRunFilename, osPlatform); + } + + public string GetCoreRunExeFilenameForSnapApp(SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return GetCoreRunExeFilename(snapApp.MainExe ?? snapApp.Id, snapApp.Target.Os); + } - public string GetCoreRunExeFilenameForSnapApp(SnapApp snapApp) + public string GetCoreRunExeFilename(string assemblyName, OSPlatform osPlatform) + { + if (osPlatform == OSPlatform.Windows) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return GetCoreRunExeFilename(snapApp.MainExe ?? snapApp.Id, snapApp.Target.Os); + return $"{assemblyName}.exe"; } - public string GetCoreRunExeFilename(string assemblyName, OSPlatform osPlatform) + if (osPlatform == OSPlatform.Linux) { - if (osPlatform == OSPlatform.Windows) - { - return $"{assemblyName}.exe"; - } + return $"{assemblyName}"; + } - if (osPlatform == OSPlatform.Linux) - { - return $"{assemblyName}"; - } + throw new PlatformNotSupportedException(); + } - throw new PlatformNotSupportedException(); - } + public async Task ExtractCoreRunLibAsync([NotNull] ISnapFilesystem filesystem, [NotNull] ISnapCryptoProvider snapCryptoProvider, + [NotNull] string workingDirectory, OSPlatform osPlatform) + { + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (snapCryptoProvider == null) throw new ArgumentNullException(nameof(snapCryptoProvider)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + +#if SNAP_BOOTSTRAP + return; +#endif - public async Task ExtractCoreRunLibAsync([NotNull] ISnapFilesystem filesystem, [NotNull] ISnapCryptoProvider snapCryptoProvider, - [NotNull] string workingDirectory, OSPlatform osPlatform) + bool ShouldOverwrite(Stream lhsStream, string filename) { - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (snapCryptoProvider == null) throw new ArgumentNullException(nameof(snapCryptoProvider)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + if (lhsStream == null) throw new ArgumentNullException(nameof(lhsStream)); + if (filename == null) throw new ArgumentNullException(nameof(filename)); + var lhsSha256 = snapCryptoProvider.Sha256(lhsStream); + using var rhsStream = filesystem.FileRead(filename); + var rhsSha256 = snapCryptoProvider.Sha256(rhsStream); + return !string.Equals(lhsSha256, rhsSha256); + } - #if SNAP_BOOTSTRAP - return; - #endif + var rid = osPlatform.BuildRid(); - bool ShouldOverwrite(Stream lhsStream, string filename) + if (osPlatform == OSPlatform.Windows) + { + var coreRunLib = rid == "win-x86" ? CoreRunLibWindowsX86 : CoreRunLibWindowsX64; + var filename = filesystem.PathCombine(workingDirectory, $"libcorerun-{rid}.dll"); + if (filesystem.FileExists(filename) + && !ShouldOverwrite(coreRunLib, filename)) { - if (lhsStream == null) throw new ArgumentNullException(nameof(lhsStream)); - if (filename == null) throw new ArgumentNullException(nameof(filename)); - var lhsSha256 = snapCryptoProvider.Sha256(lhsStream); - using var rhsStream = filesystem.FileRead(filename); - var rhsSha256 = snapCryptoProvider.Sha256(rhsStream); - return !string.Equals(lhsSha256, rhsSha256); + await coreRunLib.DisposeAsync(); + return; } - var rid = osPlatform.BuildRid(); + await using var dstStream = filesystem.FileWrite(filename); + await using var coreRunLibWindows = coreRunLib; + await coreRunLibWindows.CopyToAsync(dstStream); - if (osPlatform == OSPlatform.Windows) - { - var coreRunLib = rid == "win-x86" ? CoreRunLibWindowsX86 : CoreRunLibWindowsX64; - var filename = filesystem.PathCombine(workingDirectory, $"libcorerun-{rid}.dll"); - if (filesystem.FileExists(filename) - && !ShouldOverwrite(coreRunLib, filename)) - { - await coreRunLib.DisposeAsync(); - return; - } - - await using var dstStream = filesystem.FileWrite(filename); - await using var coreRunLibWindows = coreRunLib; - await coreRunLibWindows.CopyToAsync(dstStream); + return; + } + if (osPlatform == OSPlatform.Linux) + { + var filename = filesystem.PathCombine(workingDirectory, $"libcorerun-{rid}.so"); + var coreRunLib = rid == "linux-x64" ? CoreRunLibLinuxX64 : CoreRunLibLinuxArm64; + if (filesystem.FileExists(filename) + && !ShouldOverwrite(coreRunLib, filename)) + { + await coreRunLib.DisposeAsync(); return; } - if (osPlatform == OSPlatform.Linux) - { - var filename = filesystem.PathCombine(workingDirectory, $"libcorerun-{rid}.so"); - var coreRunLib = rid == "linux-x64" ? CoreRunLibLinuxX64 : CoreRunLibLinuxArm64; - if (filesystem.FileExists(filename) - && !ShouldOverwrite(coreRunLib, filename)) - { - await coreRunLib.DisposeAsync(); - return; - } - - await using var dstStream = filesystem.FileWrite(filename); - await using var coreRunLibLinux = coreRunLib; - await coreRunLibLinux.CopyToAsync(dstStream); + await using var dstStream = filesystem.FileWrite(filename); + await using var coreRunLibLinux = coreRunLib; + await coreRunLibLinux.CopyToAsync(dstStream); - return; - } - - throw new PlatformNotSupportedException(); + return; } + + throw new PlatformNotSupportedException(); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/SnapAppReader.cs b/src/Snap/Core/SnapAppReader.cs index 99988421..77ccdd2c 100644 --- a/src/Snap/Core/SnapAppReader.cs +++ b/src/Snap/Core/SnapAppReader.cs @@ -13,82 +13,81 @@ using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeTypeResolvers; -namespace Snap.Core +namespace Snap.Core; + +internal interface ISnapAppReader { - internal interface ISnapAppReader - { - SnapApps BuildSnapAppsFromYamlString(string yamlString); - SnapApp BuildSnapAppFromStream(MemoryStream stream); - SnapApp BuildSnapAppFromYamlString(string yamlString); - ValueTask BuildSnapAppsReleasesFromStreamAsync(MemoryStream stream); - } + SnapApps BuildSnapAppsFromYamlString(string yamlString); + SnapApp BuildSnapAppFromStream(MemoryStream stream); + SnapApp BuildSnapAppFromYamlString(string yamlString); + ValueTask BuildSnapAppsReleasesFromStreamAsync(MemoryStream stream); +} - internal sealed class SnapAppReader : ISnapAppReader +internal sealed class SnapAppReader : ISnapAppReader +{ + internal static readonly Dictionary AbstractClassTypeMappingsSnapApp = new() { - internal static readonly Dictionary AbstractClassTypeMappingsSnapApp = new() - { - { "nuget", typeof(SnapNugetFeed) }, - { "http", typeof(SnapHttpFeed)} - }; + { "nuget", typeof(SnapNugetFeed) }, + { "http", typeof(SnapHttpFeed)} + }; - internal static readonly Dictionary AbstractClassTypeMappingsSnapApps = new() - { - { "nuget", typeof(SnapsNugetFeed) }, - { "http", typeof(SnapsHttpFeed)} - }; + internal static readonly Dictionary AbstractClassTypeMappingsSnapApps = new() + { + { "nuget", typeof(SnapsNugetFeed) }, + { "http", typeof(SnapsHttpFeed)} + }; - static readonly IDeserializer DeserializerSnapApp = Build(new DeserializerBuilder() - .WithNodeTypeResolver(new AbstractClassTypeResolver(AbstractClassTypeMappingsSnapApp), - selector => selector.After() - ) - ); + static readonly IDeserializer DeserializerSnapApp = Build(new DeserializerBuilder() + .WithNodeTypeResolver(new AbstractClassTypeResolver(AbstractClassTypeMappingsSnapApp), + selector => selector.After() + ) + ); - static readonly IDeserializer DeserializerSnapApps = Build(new DeserializerBuilder() - .WithNodeTypeResolver(new AbstractClassTypeResolver(AbstractClassTypeMappingsSnapApps), - selector => selector.After() - ) - ); + static readonly IDeserializer DeserializerSnapApps = Build(new DeserializerBuilder() + .WithNodeTypeResolver(new AbstractClassTypeResolver(AbstractClassTypeMappingsSnapApps), + selector => selector.After() + ) + ); - static IDeserializer Build(DeserializerBuilder builder) - { - return builder - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .IgnoreUnmatchedProperties() - .WithTypeConverter(new SemanticVersionYamlTypeConverter()) - .WithTypeConverter(new UriYamlTypeConverter()) - .WithTypeConverter(new OsPlatformYamlTypeConverter()) - .WithTypeConverter(new DateTimeConverter(DateTimeKind.Utc)) - .Build(); - } + static IDeserializer Build(DeserializerBuilder builder) + { + return builder + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .WithTypeConverter(new SemanticVersionYamlTypeConverter()) + .WithTypeConverter(new UriYamlTypeConverter()) + .WithTypeConverter(new OsPlatformYamlTypeConverter()) + .WithTypeConverter(new DateTimeConverter(DateTimeKind.Utc)) + .Build(); + } - public SnapApps BuildSnapAppsFromStream([NotNull] MemoryStream stream) - { - if (stream == null) throw new ArgumentNullException(nameof(stream)); - return BuildSnapAppsFromYamlString(Encoding.UTF8.GetString(stream.ToArray())); - } + public SnapApps BuildSnapAppsFromStream([NotNull] MemoryStream stream) + { + if (stream == null) throw new ArgumentNullException(nameof(stream)); + return BuildSnapAppsFromYamlString(Encoding.UTF8.GetString(stream.ToArray())); + } - public SnapApp BuildSnapAppFromStream([NotNull] MemoryStream stream) - { - if (stream == null) throw new ArgumentNullException(nameof(stream)); - return BuildSnapAppFromYamlString(Encoding.UTF8.GetString(stream.ToArray())); - } + public SnapApp BuildSnapAppFromStream([NotNull] MemoryStream stream) + { + if (stream == null) throw new ArgumentNullException(nameof(stream)); + return BuildSnapAppFromYamlString(Encoding.UTF8.GetString(stream.ToArray())); + } - public SnapApp BuildSnapAppFromYamlString(string yamlString) - { - if (string.IsNullOrWhiteSpace(yamlString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(yamlString)); - return DeserializerSnapApp.Deserialize(yamlString); - } + public SnapApp BuildSnapAppFromYamlString(string yamlString) + { + if (string.IsNullOrWhiteSpace(yamlString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(yamlString)); + return DeserializerSnapApp.Deserialize(yamlString); + } - public ValueTask BuildSnapAppsReleasesFromStreamAsync([NotNull] MemoryStream stream) - { - if (stream == null) throw new ArgumentNullException(nameof(stream)); - return MessagePackSerializer.DeserializeAsync(stream, MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray)); - } + public ValueTask BuildSnapAppsReleasesFromStreamAsync([NotNull] MemoryStream stream) + { + if (stream == null) throw new ArgumentNullException(nameof(stream)); + return MessagePackSerializer.DeserializeAsync(stream, MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray)); + } - public SnapApps BuildSnapAppsFromYamlString([NotNull] string yamlString) - { - if (string.IsNullOrWhiteSpace(yamlString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(yamlString)); - return DeserializerSnapApps.Deserialize(yamlString); - } + public SnapApps BuildSnapAppsFromYamlString([NotNull] string yamlString) + { + if (string.IsNullOrWhiteSpace(yamlString)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(yamlString)); + return DeserializerSnapApps.Deserialize(yamlString); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/SnapAppWriter.cs b/src/Snap/Core/SnapAppWriter.cs index 42f31b5f..1e0901b4 100644 --- a/src/Snap/Core/SnapAppWriter.cs +++ b/src/Snap/Core/SnapAppWriter.cs @@ -18,140 +18,139 @@ using YamlDotNet.Serialization.NamingConventions; using EmbeddedResource = Mono.Cecil.EmbeddedResource; -namespace Snap.Core +namespace Snap.Core; + +internal interface ISnapAppWriter { - internal interface ISnapAppWriter + AssemblyDefinition BuildSnapAppAssembly(SnapApp snapsApp); + AssemblyDefinition OptimizeSnapDllForPackageArchive(AssemblyDefinition assemblyDefinition, OSPlatform osPlatform); + string ToSnapAppYamlString(SnapApp snapApp); + string ToSnapAppsYamlString(SnapApps snapApps); + byte[] ToSnapAppsReleases(SnapAppsReleases snapAppsApps); +} + +internal sealed class SnapAppWriter : ISnapAppWriter +{ + static readonly ISerializer YamlSerializerSnapApp = Build(new SerializerBuilder() + .WithEventEmitter(eventEmitter => new AbstractClassTagEventEmitter(eventEmitter, + SnapAppReader.AbstractClassTypeMappingsSnapApp) + ) + ); + + static readonly ISerializer YamlSerializerSnapApps = Build(new SerializerBuilder() + .WithEventEmitter(eventEmitter => new AbstractClassTagEventEmitter(eventEmitter, + SnapAppReader.AbstractClassTypeMappingsSnapApps) + ) + ); + + static ISerializer Build(SerializerBuilder serializerBuilder) { - AssemblyDefinition BuildSnapAppAssembly(SnapApp snapsApp); - AssemblyDefinition OptimizeSnapDllForPackageArchive(AssemblyDefinition assemblyDefinition, OSPlatform osPlatform); - string ToSnapAppYamlString(SnapApp snapApp); - string ToSnapAppsYamlString(SnapApps snapApps); - byte[] ToSnapAppsReleases(SnapAppsReleases snapAppsApps); + return serializerBuilder + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .WithTypeConverter(new SemanticVersionYamlTypeConverter()) + .WithTypeConverter(new OsPlatformYamlTypeConverter()) + .WithTypeConverter(new UriYamlTypeConverter()) + .WithTypeConverter(new DateTimeConverter(DateTimeKind.Utc)) + .DisableAliases() + .Build(); } - internal sealed class SnapAppWriter : ISnapAppWriter - { - static readonly ISerializer YamlSerializerSnapApp = Build(new SerializerBuilder() - .WithEventEmitter(eventEmitter => new AbstractClassTagEventEmitter(eventEmitter, - SnapAppReader.AbstractClassTypeMappingsSnapApp) - ) - ); - - static readonly ISerializer YamlSerializerSnapApps = Build(new SerializerBuilder() - .WithEventEmitter(eventEmitter => new AbstractClassTagEventEmitter(eventEmitter, - SnapAppReader.AbstractClassTypeMappingsSnapApps) - ) - ); - - static ISerializer Build(SerializerBuilder serializerBuilder) - { - return serializerBuilder - .WithNamingConvention(CamelCaseNamingConvention.Instance) - .WithTypeConverter(new SemanticVersionYamlTypeConverter()) - .WithTypeConverter(new OsPlatformYamlTypeConverter()) - .WithTypeConverter(new UriYamlTypeConverter()) - .WithTypeConverter(new DateTimeConverter(DateTimeKind.Utc)) - .DisableAliases() - .Build(); - } + public AssemblyDefinition BuildSnapAppAssembly([NotNull] SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + + snapApp = new SnapApp(snapApp); - public AssemblyDefinition BuildSnapAppAssembly([NotNull] SnapApp snapApp) + foreach (var channel in snapApp.Channels) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (channel.PushFeed == null) + { + throw new Exception($"{nameof(channel.PushFeed)} cannot be null. Channel: {channel.Name}. Application id: {snapApp.Id}"); + } - snapApp = new SnapApp(snapApp); + if (channel.UpdateFeed == null) + { + throw new Exception($"{nameof(channel.UpdateFeed)} cannot be null. Channel: {channel.Name}. Application id: {snapApp.Id}"); + } + + channel.PushFeed.ApiKey = null; + channel.PushFeed.Username = null; + channel.PushFeed.Password = null; + + if (channel.UpdateFeed.Source == null) + { + throw new Exception( + $"Update feed {nameof(channel.UpdateFeed.Source)} cannot be null. Channel: {channel.Name}. Application id: {snapApp.Id}"); + } - foreach (var channel in snapApp.Channels) + // Prevent publishing nuget.org credentials. + if (channel.UpdateFeed is SnapNugetFeed updateFeed + && updateFeed.Source.Host.IndexOf("nuget.org", StringComparison.OrdinalIgnoreCase) != -1) { - if (channel.PushFeed == null) - { - throw new Exception($"{nameof(channel.PushFeed)} cannot be null. Channel: {channel.Name}. Application id: {snapApp.Id}"); - } - - if (channel.UpdateFeed == null) - { - throw new Exception($"{nameof(channel.UpdateFeed)} cannot be null. Channel: {channel.Name}. Application id: {snapApp.Id}"); - } - - channel.PushFeed.ApiKey = null; - channel.PushFeed.Username = null; - channel.PushFeed.Password = null; - - if (channel.UpdateFeed.Source == null) - { - throw new Exception( - $"Update feed {nameof(channel.UpdateFeed.Source)} cannot be null. Channel: {channel.Name}. Application id: {snapApp.Id}"); - } - - // Prevent publishing nuget.org credentials. - if (channel.UpdateFeed is SnapNugetFeed updateFeed - && updateFeed.Source.Host.IndexOf("nuget.org", StringComparison.OrdinalIgnoreCase) != -1) - { - updateFeed.ApiKey = null; - updateFeed.Username = null; - updateFeed.Password = null; - } + updateFeed.ApiKey = null; + updateFeed.Username = null; + updateFeed.Password = null; } + } - var snapAppYamlStr = ToSnapAppYamlString(snapApp); - var currentVersion = snapApp.Version; + var snapAppYamlStr = ToSnapAppYamlString(snapApp); + var currentVersion = snapApp.Version; - var assembly = AssemblyDefinition.CreateAssembly( - new AssemblyNameDefinition(SnapConstants.SnapAppLibraryName, new Version(currentVersion.Major, - currentVersion.Minor, currentVersion.Patch)), SnapConstants.SnapAppLibraryName, ModuleKind.Dll); + var assembly = AssemblyDefinition.CreateAssembly( + new AssemblyNameDefinition(SnapConstants.SnapAppLibraryName, new Version(currentVersion.Major, + currentVersion.Minor, currentVersion.Patch)), SnapConstants.SnapAppLibraryName, ModuleKind.Dll); - var assemblyReflector = new CecilAssemblyReflector(assembly); + var assemblyReflector = new CecilAssemblyReflector(assembly); - var snapAppReleaseDetailsAttributeMethodDefinition = assemblyReflector.MainModule.ImportReference( - typeof(SnapAppReleaseDetailsAttribute).GetConstructor(Type.EmptyTypes)); + var snapAppReleaseDetailsAttributeMethodDefinition = assemblyReflector.MainModule.ImportReference( + typeof(SnapAppReleaseDetailsAttribute).GetConstructor(Type.EmptyTypes)); - assemblyReflector.AddCustomAttribute(new CustomAttribute(snapAppReleaseDetailsAttributeMethodDefinition)); - assemblyReflector.AddResource(new EmbeddedResource(SnapConstants.SnapAppLibraryName, ManifestResourceAttributes.Public, Encoding.UTF8.GetBytes(snapAppYamlStr))); + assemblyReflector.AddCustomAttribute(new CustomAttribute(snapAppReleaseDetailsAttributeMethodDefinition)); + assemblyReflector.AddResource(new EmbeddedResource(SnapConstants.SnapAppLibraryName, ManifestResourceAttributes.Public, Encoding.UTF8.GetBytes(snapAppYamlStr))); - return assembly; - } + return assembly; + } - public AssemblyDefinition OptimizeSnapDllForPackageArchive([NotNull] AssemblyDefinition assemblyDefinition, OSPlatform osPlatform) - { - if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); + public AssemblyDefinition OptimizeSnapDllForPackageArchive([NotNull] AssemblyDefinition assemblyDefinition, OSPlatform osPlatform) + { + if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); - if (!osPlatform.IsSupportedOsVersion()) - { - throw new PlatformNotSupportedException(); - } + if (!osPlatform.IsSupportedOsVersion()) + { + throw new PlatformNotSupportedException(); + } - var cecilReflector = new CecilAssemblyReflector(assemblyDefinition); - var cecilResourceReflector = cecilReflector.GetResourceReflector(); + var cecilReflector = new CecilAssemblyReflector(assemblyDefinition); + var cecilResourceReflector = cecilReflector.GetResourceReflector(); - cecilResourceReflector.RemoveAllOrThrow(typeof(SnapEmbeddedResourcesTypeRoot).Namespace); + cecilResourceReflector.RemoveAllOrThrow(typeof(SnapEmbeddedResourcesTypeRoot).Namespace); - cecilReflector.RewriteOrThrow(x => x.IsOptimized, (typedDefinition, getterName, setterName, propertyDefinition) => - { - var getIlProcessor = propertyDefinition.GetMethod.Body.GetILProcessor(); - getIlProcessor.Body.Instructions.Clear(); - getIlProcessor.Emit(OpCodes.Ldc_I4_1); - getIlProcessor.Emit(OpCodes.Ret); - }); + cecilReflector.RewriteOrThrow(x => x.IsOptimized, (typedDefinition, getterName, setterName, propertyDefinition) => + { + var getIlProcessor = propertyDefinition.GetMethod.Body.GetILProcessor(); + getIlProcessor.Body.Instructions.Clear(); + getIlProcessor.Emit(OpCodes.Ldc_I4_1); + getIlProcessor.Emit(OpCodes.Ret); + }); - return assemblyDefinition; - } + return assemblyDefinition; + } - public string ToSnapAppYamlString([NotNull] SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return YamlSerializerSnapApp.Serialize(snapApp); - } + public string ToSnapAppYamlString([NotNull] SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return YamlSerializerSnapApp.Serialize(snapApp); + } - public string ToSnapAppsYamlString([NotNull] SnapApps snapApps) - { - if (snapApps == null) throw new ArgumentNullException(nameof(snapApps)); - return YamlSerializerSnapApps.Serialize(snapApps); - } + public string ToSnapAppsYamlString([NotNull] SnapApps snapApps) + { + if (snapApps == null) throw new ArgumentNullException(nameof(snapApps)); + return YamlSerializerSnapApps.Serialize(snapApps); + } - public byte[] ToSnapAppsReleases([NotNull] SnapAppsReleases snapAppsApps) - { - if (snapAppsApps == null) throw new ArgumentNullException(nameof(snapAppsApps)); - return MessagePackSerializer.Serialize(snapAppsApps, MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray)); - } + public byte[] ToSnapAppsReleases([NotNull] SnapAppsReleases snapAppsApps) + { + if (snapAppsApps == null) throw new ArgumentNullException(nameof(snapAppsApps)); + return MessagePackSerializer.Serialize(snapAppsApps, MessagePackSerializerOptions.Standard.WithCompression(MessagePackCompression.Lz4BlockArray)); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/SnapBinaryPatcher.cs b/src/Snap/Core/SnapBinaryPatcher.cs index 65c80ea4..77af3966 100644 --- a/src/Snap/Core/SnapBinaryPatcher.cs +++ b/src/Snap/Core/SnapBinaryPatcher.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.IO; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using SharpCompress.Compressors.BZip2; @@ -9,957 +10,955 @@ // Adapted from https://github.com/LogosBible/bsdiff.net/blob/master/src/bsdiff/BinaryPatchUtility.cs // Adapter from https://raw.githubusercontent.com/Squirrel/Squirrel.Windows/afe47c9c064bf9860404c61d6ef36d8ecb250b04/src/Squirrel/BinaryPatchUtility.cs -namespace Snap.Core +namespace Snap.Core; + +/* +The original bsdiff.c source code (http://www.daemonology.net/bsdiff/) is +distributed under the following license: + +Copyright 2003-2005 Colin Percival +All rights reserved + +Redistribution and use in source and binary forms, with or without +modification, are permitted providing that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ +internal interface ISnapBinaryPatcher { - /* - The original bsdiff.c source code (http://www.daemonology.net/bsdiff/) is - distributed under the following license: - - Copyright 2003-2005 Colin Percival - All rights reserved - - Redistribution and use in source and binary forms, with or without - modification, are permitted providing that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - */ - internal interface ISnapBinaryPatcher - { - /// - /// Creates a binary patch (in bsdiff format) that can be used - /// (by ) to transform into . - /// - /// The original binary data. - /// The new binary data. - /// A to which the patch will be written. - /// - Task CreateAsync(ReadOnlyMemory oldData, ReadOnlyMemory newData, Stream output, CancellationToken cancellationToken); - - /// - /// Applies a binary patch (in bsdiff format) to the data in - /// and writes the results of patching to . - /// - /// A containing the input data. - /// A func that can open a positioned at the start of the patch data. - /// This stream must support reading and seeking, and must allow multiple streams on - /// the patch to be opened concurrently. - /// A to which the patched data is written. - /// - Task ApplyAsync(Stream input, Func> openPatchStream, Stream output, CancellationToken cancellationToken); - } + /// + /// Creates a binary patch (in bsdiff format) that can be used + /// (by ) to transform into . + /// + /// The original binary data. + /// The new binary data. + /// A to which the patch will be written. + /// + Task CreateAsync(ReadOnlyMemory oldData, ReadOnlyMemory newData, Stream output, CancellationToken cancellationToken); + + /// + /// Applies a binary patch (in bsdiff format) to the data in + /// and writes the results of patching to . + /// + /// A containing the input data. + /// A func that can open a positioned at the start of the patch data. + /// This stream must support reading and seeking, and must allow multiple streams on + /// the patch to be opened concurrently. + /// A to which the patched data is written. + /// + Task ApplyAsync(Stream input, Func> openPatchStream, Stream output, CancellationToken cancellationToken); +} - internal sealed class SnapBinaryPatcher : ISnapBinaryPatcher +internal sealed class SnapBinaryPatcher : ISnapBinaryPatcher +{ + /// + /// Creates a binary patch (in bsdiff format) that can be used + /// (by ) to transform into . + /// + /// The original binary data. + /// The new binary data. + /// A to which the patch will be written. + /// + public async Task CreateAsync(ReadOnlyMemory oldData, ReadOnlyMemory newData, Stream output, CancellationToken cancellationToken) { - /// - /// Creates a binary patch (in bsdiff format) that can be used - /// (by ) to transform into . - /// - /// The original binary data. - /// The new binary data. - /// A to which the patch will be written. - /// - public async Task CreateAsync(ReadOnlyMemory oldData, ReadOnlyMemory newData, Stream output, CancellationToken cancellationToken) + // check arguments + if (oldData.IsEmpty) throw new ArgumentNullException(nameof(oldData)); + if (newData.IsEmpty) throw new ArgumentNullException(nameof(newData)); + if (output == null) throw new ArgumentNullException(nameof(output)); + if (!output.CanSeek) throw new ArgumentException("Output stream must be seekable.", nameof(output)); + if (!output.CanWrite) throw new ArgumentException("Output stream must be writable.", nameof(output)); + + var header = ArrayPool.Shared.Rent(CHeaderSize); + var db = ArrayPool.Shared.Rent(newData.Length); + var eb = ArrayPool.Shared.Rent(newData.Length); + + try { - // check arguments - if (oldData.IsEmpty) throw new ArgumentNullException(nameof(oldData)); - if (newData.IsEmpty) throw new ArgumentNullException(nameof(newData)); - if (output == null) throw new ArgumentNullException(nameof(output)); - if (!output.CanSeek) throw new ArgumentException("Output stream must be seekable.", nameof(output)); - if (!output.CanWrite) throw new ArgumentException("Output stream must be writable.", nameof(output)); - - var header = ArrayPool.Shared.Rent(CHeaderSize); - var db = ArrayPool.Shared.Rent(newData.Length); - var eb = ArrayPool.Shared.Rent(newData.Length); + await DiffAsync(); + } + finally + { + ArrayPool.Shared.Return(header); + ArrayPool.Shared.Return(db); + ArrayPool.Shared.Return(eb); + } - try - { - await DiffAsync(); - } - finally + async Task DiffAsync() + { + /* Header is + 0 8 "BSDIFF40" + 8 8 length of bzip2ed ctrl block + 16 8 length of bzip2ed diff block + 24 8 length of new file */ + /* File is + 0 32 Header + 32 ?? Bzip2ed ctrl block + ?? ?? Bzip2ed diff block + ?? ?? Bzip2ed extra block */ + + WriteInt64(CFileSignature, header, 0); // "BSDIFF40" + WriteInt64(0, header, 8); + WriteInt64(0, header, 16); + WriteInt64(newData.Length, header, 24); + + var startPosition = output.Position; + await output.WriteAsync(header.AsMemory(0, header.Length), cancellationToken); + + var I = SuffixSort(oldData); + + var dblen = 0; + var eblen = 0; + + await using (var wrappingStream = new WrappingStream(output, Ownership.None)) { - ArrayPool.Shared.Return(header); - ArrayPool.Shared.Return(db); - ArrayPool.Shared.Return(eb); - } + await using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true); - async Task DiffAsync() - { - /* Header is - 0 8 "BSDIFF40" - 8 8 length of bzip2ed ctrl block - 16 8 length of bzip2ed diff block - 24 8 length of new file */ - /* File is - 0 32 Header - 32 ?? Bzip2ed ctrl block - ?? ?? Bzip2ed diff block - ?? ?? Bzip2ed extra block */ - - WriteInt64(CFileSignature, header, 0); // "BSDIFF40" - WriteInt64(0, header, 8); - WriteInt64(0, header, 16); - WriteInt64(newData.Length, header, 24); - - var startPosition = output.Position; - await output.WriteAsync(header.AsMemory(0, header.Length), cancellationToken); - - var I = SuffixSort(oldData); - - var dblen = 0; - var eblen = 0; - - await using (var wrappingStream = new WrappingStream(output, Ownership.None)) - { - await using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true); + // compute the differences, writing ctrl as we go - // compute the differences, writing ctrl as we go + var scan = 0; + var pos = 0; + var len = 0; + var lastscan = 0; + var lastpos = 0; + var lastoffset = 0; - var scan = 0; - var pos = 0; - var len = 0; - var lastscan = 0; - var lastpos = 0; - var lastoffset = 0; + while (scan < newData.Length) + { + var oldscore = 0; - while (scan < newData.Length) + for (var scsc = scan += len; scan < newData.Length; scan++) { - var oldscore = 0; + len = Search(I, oldData, newData, scan, 0, oldData.Length, out pos); - for (var scsc = scan += len; scan < newData.Length; scan++) + for (; scsc < scan + len; scsc++) { - len = Search(I, oldData, newData, scan, 0, oldData.Length, out pos); - - for (; scsc < scan + len; scsc++) - { - if (scsc + lastoffset < oldData.Length && oldData.Span[scsc + lastoffset] == newData.Span[scsc]) - oldscore++; - } + if (scsc + lastoffset < oldData.Length && oldData.Span[scsc + lastoffset] == newData.Span[scsc]) + oldscore++; + } - if (len == oldscore && len != 0 || len > oldscore + 8) - break; + if (len == oldscore && len != 0 || len > oldscore + 8) + break; - if (scan + lastoffset < oldData.Length && oldData.Span[scan + lastoffset] == newData.Span[scan]) - oldscore--; - } + if (scan + lastoffset < oldData.Length && oldData.Span[scan + lastoffset] == newData.Span[scan]) + oldscore--; + } - if (len != oldscore || scan == newData.Length) + if (len != oldscore || scan == newData.Length) + { + var s = 0; + var sf = 0; + var lenf = 0; + for (var i = 0; lastscan + i < scan && lastpos + i < oldData.Length;) { - var s = 0; - var sf = 0; - var lenf = 0; - for (var i = 0; lastscan + i < scan && lastpos + i < oldData.Length;) + if (oldData.Span[lastpos + i] == newData.Span[lastscan + i]) + s++; + i++; + if (s * 2 - i > sf * 2 - lenf) { - if (oldData.Span[lastpos + i] == newData.Span[lastscan + i]) - s++; - i++; - if (s * 2 - i > sf * 2 - lenf) - { - sf = s; - lenf = i; - } + sf = s; + lenf = i; } + } - var lenb = 0; - if (scan < newData.Length) + var lenb = 0; + if (scan < newData.Length) + { + s = 0; + var sb = 0; + for (var i = 1; scan >= lastscan + i && pos >= i; i++) { - s = 0; - var sb = 0; - for (var i = 1; scan >= lastscan + i && pos >= i; i++) + if (oldData.Span[pos - i] == newData.Span[scan - i]) + s++; + if (s * 2 - i > sb * 2 - lenb) { - if (oldData.Span[pos - i] == newData.Span[scan - i]) - s++; - if (s * 2 - i > sb * 2 - lenb) - { - sb = s; - lenb = i; - } + sb = s; + lenb = i; } } + } - if (lastscan + lenf > scan - lenb) + if (lastscan + lenf > scan - lenb) + { + var overlap = lastscan + lenf - (scan - lenb); + s = 0; + var ss = 0; + var lens = 0; + for (var i = 0; i < overlap; i++) { - var overlap = lastscan + lenf - (scan - lenb); - s = 0; - var ss = 0; - var lens = 0; - for (var i = 0; i < overlap; i++) + if (newData.Span[lastscan + lenf - overlap + i] == oldData.Span[lastpos + lenf - overlap + i]) + s++; + if (newData.Span[scan - lenb + i] == oldData.Span[pos - lenb + i]) + s--; + if (s > ss) { - if (newData.Span[lastscan + lenf - overlap + i] == oldData.Span[lastpos + lenf - overlap + i]) - s++; - if (newData.Span[scan - lenb + i] == oldData.Span[pos - lenb + i]) - s--; - if (s > ss) - { - ss = s; - lens = i + 1; - } + ss = s; + lens = i + 1; } - - lenf += lens - overlap; - lenb -= lens; } - for (var i = 0; i < lenf; i++) - { - db[dblen + i] = (byte)(newData.Span[lastscan + i] - oldData.Span[lastpos + i]); - } - for (var i = 0; i < scan - lenb - (lastscan + lenf); i++) - { - eb[eblen + i] = newData.Span[lastscan + lenf + i]; - } + lenf += lens - overlap; + lenb -= lens; + } - dblen += lenf; - eblen += scan - lenb - (lastscan + lenf); + for (var i = 0; i < lenf; i++) + { + db[dblen + i] = (byte)(newData.Span[lastscan + i] - oldData.Span[lastpos + i]); + } + for (var i = 0; i < scan - lenb - (lastscan + lenf); i++) + { + eb[eblen + i] = newData.Span[lastscan + lenf + i]; + } - var buf = ArrayPool.Shared.Rent(8); - try - { - WriteInt64(lenf, buf, 0); - await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken); + dblen += lenf; + eblen += scan - lenb - (lastscan + lenf); - WriteInt64(scan - lenb - (lastscan + lenf), buf, 0); - await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken); + var buf = ArrayPool.Shared.Rent(8); + try + { + WriteInt64(lenf, buf, 0); + await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken); - WriteInt64(pos - lenb - (lastpos + lenf), buf, 0); - await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken); - } - finally - { - ArrayPool.Shared.Return(buf); - } + WriteInt64(scan - lenb - (lastscan + lenf), buf, 0); + await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken); - lastscan = scan - lenb; - lastpos = pos - lenb; - lastoffset = pos - scan; + WriteInt64(pos - lenb - (lastpos + lenf), buf, 0); + await bz2Stream.WriteAsync(buf.AsMemory(0, 8), cancellationToken); + } + finally + { + ArrayPool.Shared.Return(buf); } + + lastscan = scan - lenb; + lastpos = pos - lenb; + lastoffset = pos - scan; } } + } - // compute size of compressed ctrl data - var controlEndPosition = output.Position; - WriteInt64(controlEndPosition - startPosition - CHeaderSize, header, 8); - - // write compressed diff data - await using (var wrappingStream = new WrappingStream(output, Ownership.None)) - { - await using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true); - await bz2Stream.WriteAsync(db.AsMemory(0, dblen), cancellationToken); - } + // compute size of compressed ctrl data + var controlEndPosition = output.Position; + WriteInt64(controlEndPosition - startPosition - CHeaderSize, header, 8); - // compute size of compressed diff data - var diffEndPosition = output.Position; - WriteInt64(diffEndPosition - controlEndPosition, header, 16); + // write compressed diff data + await using (var wrappingStream = new WrappingStream(output, Ownership.None)) + { + await using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true); + await bz2Stream.WriteAsync(db.AsMemory(0, dblen), cancellationToken); + } - // write compressed extra data, if any - if (eblen > 0) - { - await using var wrappingStream = new WrappingStream(output, Ownership.None); - await using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true); - await bz2Stream.WriteAsync(eb.AsMemory(0, eblen), cancellationToken); - } + // compute size of compressed diff data + var diffEndPosition = output.Position; + WriteInt64(diffEndPosition - controlEndPosition, header, 16); - // seek to the beginning, write the header, then seek back to end - var endPosition = output.Position; - output.Position = startPosition; - await output.WriteAsync(header.AsMemory(0, header.Length), cancellationToken); - output.Position = endPosition; + // write compressed extra data, if any + if (eblen > 0) + { + await using var wrappingStream = new WrappingStream(output, Ownership.None); + await using var bz2Stream = new BZip2Stream(wrappingStream, CompressionMode.Compress, true); + await bz2Stream.WriteAsync(eb.AsMemory(0, eblen), cancellationToken); } + + // seek to the beginning, write the header, then seek back to end + var endPosition = output.Position; + output.Position = startPosition; + await output.WriteAsync(header.AsMemory(0, header.Length), cancellationToken); + output.Position = endPosition; } + } - /// - /// Applies a binary patch (in bsdiff format) to the data in - /// and writes the results of patching to . - /// - /// A containing the input data. - /// A func that can open a positioned at the start of the patch data. - /// This stream must support reading and seeking, and must allow multiple streams on - /// the patch to be opened concurrently. - /// A to which the patched data is written. - /// - public async Task ApplyAsync(Stream input, Func> openPatchStream, Stream output, CancellationToken cancellationToken) + /// + /// Applies a binary patch (in bsdiff format) to the data in + /// and writes the results of patching to . + /// + /// A containing the input data. + /// A func that can open a positioned at the start of the patch data. + /// This stream must support reading and seeking, and must allow multiple streams on + /// the patch to be opened concurrently. + /// A to which the patched data is written. + /// + public async Task ApplyAsync(Stream input, Func> openPatchStream, Stream output, CancellationToken cancellationToken) + { + if (input == null) throw new ArgumentNullException(nameof(input)); + if (openPatchStream == null) throw new ArgumentNullException(nameof(openPatchStream)); + if (output == null) throw new ArgumentNullException(nameof(output)); + + /* + File format: + 0 8 "BSDIFF40" + 8 8 X + 16 8 Y + 24 8 sizeof(newfile) + 32 X bzip2(control block) + 32+X Y bzip2(diff block) + 32+X+Y ??? bzip2(extra block) + with control block a set of triples (x,y,z) meaning "add x bytes + from oldfile to x bytes from the diff block; copy y bytes from the + extra block; seek forwards in oldfile by z bytes". + */ + // read header + long controlLength, diffLength, newSize; + await using (var patchStream = await openPatchStream()) { - if (input == null) throw new ArgumentNullException(nameof(input)); - if (openPatchStream == null) throw new ArgumentNullException(nameof(openPatchStream)); - if (output == null) throw new ArgumentNullException(nameof(output)); - - /* - File format: - 0 8 "BSDIFF40" - 8 8 X - 16 8 Y - 24 8 sizeof(newfile) - 32 X bzip2(control block) - 32+X Y bzip2(diff block) - 32+X+Y ??? bzip2(extra block) - with control block a set of triples (x,y,z) meaning "add x bytes - from oldfile to x bytes from the diff block; copy y bytes from the - extra block; seek forwards in oldfile by z bytes". - */ - // read header - long controlLength, diffLength, newSize; - await using (var patchStream = await openPatchStream()) + if (!patchStream.CanRead) + { + throw new ArgumentException("Patch stream must be readable.", nameof(openPatchStream)); + } + if (!patchStream.CanSeek) + { + throw new ArgumentException("Patch stream must be seekable.", nameof(openPatchStream)); + } + + var header = ArrayPool.Shared.Rent(CHeaderSize); + await patchStream.ReadAsync(header, cancellationToken); + + try { - if (!patchStream.CanRead) + // check for appropriate magic + var signature = ReadInt64(header, 0); + if (signature != CFileSignature) { - throw new ArgumentException("Patch stream must be readable.", nameof(openPatchStream)); + throw new InvalidOperationException("Corrupt patch."); } - if (!patchStream.CanSeek) + + // read lengths from header + controlLength = ReadInt64(header, 8); + diffLength = ReadInt64(header, 16); + newSize = ReadInt64(header, 24); + if (controlLength < 0 || diffLength < 0 || newSize < 0) { - throw new ArgumentException("Patch stream must be seekable.", nameof(openPatchStream)); + throw new InvalidOperationException("Corrupt patch."); } + } + finally + { + ArrayPool.Shared.Return(header); + } + } - var header = ArrayPool.Shared.Rent(CHeaderSize); - await patchStream.ReadAsync(header, cancellationToken); + // preallocate buffers for reading and writing + const int cBufferSize = 1048576; + var newData = ArrayPool.Shared.Rent(cBufferSize); + var oldData = ArrayPool.Shared.Rent(cBufferSize); + var control = ArrayPool.Shared.Rent(3); + var buffer = ArrayPool.Shared.Rent(8); - try + try + { + // prepare to read three parts of the patch in parallel + await using var compressedControlStream = await openPatchStream(); + await using var compressedDiffStream = await openPatchStream(); + await using var compressedExtraStream = await openPatchStream(); + // seek to the start of each part + compressedControlStream.Seek(CHeaderSize, SeekOrigin.Current); + compressedDiffStream.Seek(CHeaderSize + controlLength, SeekOrigin.Current); + compressedExtraStream.Seek(CHeaderSize + controlLength + diffLength, SeekOrigin.Current); + + // the stream might end here if there is no extra data + var hasExtraData = compressedExtraStream.Position < compressedExtraStream.Length; + + // decompress each part (to read it) + await using var controlStream = new BZip2Stream(compressedControlStream, CompressionMode.Decompress, true); + await using var diffStream = new BZip2Stream(compressedDiffStream, CompressionMode.Decompress, true); + await using var extraStream = hasExtraData ? new BZip2Stream(compressedExtraStream, CompressionMode.Decompress, true) : null; + + var oldPosition = 0; + var newPosition = 0; + while (newPosition < newSize) + { + // read control data + for (var i = 0; i < 3; i++) { - // check for appropriate magic - var signature = ReadInt64(header, 0); - if (signature != CFileSignature) - { - throw new InvalidOperationException("Corrupt patch."); - } - - // read lengths from header - controlLength = ReadInt64(header, 8); - diffLength = ReadInt64(header, 16); - newSize = ReadInt64(header, 24); - if (controlLength < 0 || diffLength < 0 || newSize < 0) - { - throw new InvalidOperationException("Corrupt patch."); - } + await controlStream.ReadAsync(buffer.AsMemory(0, 8), cancellationToken); + control[i] = ReadInt64(buffer, 0); } - finally + + // sanity-check + if (newPosition + control[0] > newSize) { - ArrayPool.Shared.Return(header); + throw new InvalidOperationException("Corrupt patch."); } - } - // preallocate buffers for reading and writing - const int cBufferSize = 1048576; - var newData = ArrayPool.Shared.Rent(cBufferSize); - var oldData = ArrayPool.Shared.Rent(cBufferSize); - var control = ArrayPool.Shared.Rent(3); - var buffer = ArrayPool.Shared.Rent(8); + // seek old file to the position that the new data is diffed against + input.Position = oldPosition; - try - { - // prepare to read three parts of the patch in parallel - await using var compressedControlStream = await openPatchStream(); - await using var compressedDiffStream = await openPatchStream(); - await using var compressedExtraStream = await openPatchStream(); - // seek to the start of each part - compressedControlStream.Seek(CHeaderSize, SeekOrigin.Current); - compressedDiffStream.Seek(CHeaderSize + controlLength, SeekOrigin.Current); - compressedExtraStream.Seek(CHeaderSize + controlLength + diffLength, SeekOrigin.Current); - - // the stream might end here if there is no extra data - var hasExtraData = compressedExtraStream.Position < compressedExtraStream.Length; - - // decompress each part (to read it) - await using var controlStream = new BZip2Stream(compressedControlStream, CompressionMode.Decompress, true); - await using var diffStream = new BZip2Stream(compressedDiffStream, CompressionMode.Decompress, true); - await using var extraStream = hasExtraData ? new BZip2Stream(compressedExtraStream, CompressionMode.Decompress, true) : null; - - var oldPosition = 0; - var newPosition = 0; - while (newPosition < newSize) + var bytesToCopy = (int)control[0]; + while (bytesToCopy > 0) { - // read control data - for (var i = 0; i < 3; i++) - { - await controlStream.ReadAsync(buffer.AsMemory(0, 8), cancellationToken); - control[i] = ReadInt64(buffer, 0); - } + var actualBytesToCopy = Math.Min(bytesToCopy, cBufferSize); - // sanity-check - if (newPosition + control[0] > newSize) - { - throw new InvalidOperationException("Corrupt patch."); - } + // read diff string + await diffStream.ReadAsync(newData.AsMemory(0, actualBytesToCopy), cancellationToken); - // seek old file to the position that the new data is diffed against - input.Position = oldPosition; + // add old data to diff string + var availableInputBytes = Math.Min(actualBytesToCopy, (int)(input.Length - input.Position)); + await input.ReadAsync(oldData.AsMemory(0, availableInputBytes), cancellationToken); - var bytesToCopy = (int)control[0]; - while (bytesToCopy > 0) + for (var index = 0; index < availableInputBytes; index++) { - var actualBytesToCopy = Math.Min(bytesToCopy, cBufferSize); - - // read diff string - await diffStream.ReadAsync(newData.AsMemory(0, actualBytesToCopy), cancellationToken); + newData[index] += oldData[index]; + } - // add old data to diff string - var availableInputBytes = Math.Min(actualBytesToCopy, (int)(input.Length - input.Position)); - await input.ReadAsync(oldData.AsMemory(0, availableInputBytes), cancellationToken); + await output.WriteAsync(newData.AsMemory(0, actualBytesToCopy), cancellationToken); - for (var index = 0; index < availableInputBytes; index++) - { - newData[index] += oldData[index]; - } + // adjust counters + newPosition += actualBytesToCopy; + oldPosition += actualBytesToCopy; + bytesToCopy -= actualBytesToCopy; + } - await output.WriteAsync(newData.AsMemory(0, actualBytesToCopy), cancellationToken); + // sanity-check + if (newPosition + control[1] > newSize) + { + throw new InvalidOperationException("Corrupt patch."); + } - // adjust counters - newPosition += actualBytesToCopy; - oldPosition += actualBytesToCopy; - bytesToCopy -= actualBytesToCopy; - } + // read extra string + bytesToCopy = (int)control[1]; + while (bytesToCopy > 0) + { + var actualBytesToCopy = Math.Min(bytesToCopy, cBufferSize); - // sanity-check - if (newPosition + control[1] > newSize) + if (hasExtraData) { - throw new InvalidOperationException("Corrupt patch."); + await extraStream.ReadAsync(newData.AsMemory(0, actualBytesToCopy), cancellationToken); } - // read extra string - bytesToCopy = (int)control[1]; - while (bytesToCopy > 0) - { - var actualBytesToCopy = Math.Min(bytesToCopy, cBufferSize); - - if (hasExtraData) - { - await extraStream.ReadAsync(newData.AsMemory(0, actualBytesToCopy), cancellationToken); - } - - await output.WriteAsync(newData.AsMemory(0, actualBytesToCopy), cancellationToken); + await output.WriteAsync(newData.AsMemory(0, actualBytesToCopy), cancellationToken); - newPosition += actualBytesToCopy; - bytesToCopy -= actualBytesToCopy; - } - - // adjust position - oldPosition = (int)(oldPosition + control[2]); + newPosition += actualBytesToCopy; + bytesToCopy -= actualBytesToCopy; } - } - finally - { - ArrayPool.Shared.Return(newData); - ArrayPool.Shared.Return(oldData); - ArrayPool.Shared.Return(control); - ArrayPool.Shared.Return(buffer); + + // adjust position + oldPosition = (int)(oldPosition + control[2]); } } + finally + { + ArrayPool.Shared.Return(newData); + ArrayPool.Shared.Return(oldData); + ArrayPool.Shared.Return(control); + ArrayPool.Shared.Return(buffer); + } + } - static int CompareBytes(ReadOnlySpan left, int leftOffset, ReadOnlySpan right, int rightOffset) + static int CompareBytes(ReadOnlySpan left, int leftOffset, ReadOnlySpan right, int rightOffset) + { + for (var index = 0; index < left.Length - leftOffset && index < right.Length - rightOffset; index++) { - for (var index = 0; index < left.Length - leftOffset && index < right.Length - rightOffset; index++) + var diff = left[index + leftOffset] - right[index + rightOffset]; + if (diff != 0) { - var diff = left[index + leftOffset] - right[index + rightOffset]; - if (diff != 0) - { - return diff; - } + return diff; } - return 0; } + return 0; + } - static int MatchLength(ReadOnlySpan oldData, int oldOffset, ReadOnlySpan newData, int newOffset) + static int MatchLength(ReadOnlySpan oldData, int oldOffset, ReadOnlySpan newData, int newOffset) + { + int i; + for (i = 0; i < oldData.Length - oldOffset && i < newData.Length - newOffset; i++) { - int i; - for (i = 0; i < oldData.Length - oldOffset && i < newData.Length - newOffset; i++) + if (oldData[i + oldOffset] != newData[i + newOffset]) { - if (oldData[i + oldOffset] != newData[i + newOffset]) - { - break; - } + break; } - return i; } + return i; + } - static int Search(ReadOnlyMemory I, ReadOnlyMemory oldData, ReadOnlyMemory newData, int newOffset, int start, int end, out int pos) + static int Search(ReadOnlyMemory I, ReadOnlyMemory oldData, ReadOnlyMemory newData, int newOffset, int start, int end, out int pos) + { + while (true) { - while (true) + if (end - start < 2) { - if (end - start < 2) - { - var startLength = MatchLength(oldData.Span, I.Span[start], newData.Span, newOffset); - var endLength = MatchLength(oldData.Span, I.Span[end], newData.Span, newOffset); - - if (startLength > endLength) - { - pos = I.Span[start]; - return startLength; - } - - pos = I.Span[end]; - return endLength; - } + var startLength = MatchLength(oldData.Span, I.Span[start], newData.Span, newOffset); + var endLength = MatchLength(oldData.Span, I.Span[end], newData.Span, newOffset); - var midPoint = start + (end - start) / 2; - if (CompareBytes(oldData.Span, I.Span[midPoint], newData.Span, newOffset) < 0) + if (startLength > endLength) { - start = midPoint; - continue; + pos = I.Span[start]; + return startLength; } - end = midPoint; + pos = I.Span[end]; + return endLength; + } + + var midPoint = start + (end - start) / 2; + if (CompareBytes(oldData.Span, I.Span[midPoint], newData.Span, newOffset) < 0) + { + start = midPoint; + continue; } + + end = midPoint; } + } - static void Split(Span I, Span v, int start, int len, int h) + static void Split(Span I, Span v, int start, int len, int h) + { + while (true) { - while (true) + if (len < 16) { - if (len < 16) + int j; + for (var k = start; k < start + len; k += j) { - int j; - for (var k = start; k < start + len; k += j) + j = 1; + var x = v[I[k] + h]; + for (var i = 1; k + i < start + len; i++) { - j = 1; - var x = v[I[k] + h]; - for (var i = 1; k + i < start + len; i++) + if (v[I[k + i] + h] < x) { - if (v[I[k + i] + h] < x) - { - x = v[I[k + i] + h]; - j = 0; - } - - if (v[I[k + i] + h] == x) - { - Swap(ref I[k + j], ref I[k + i]); - j++; - } + x = v[I[k + i] + h]; + j = 0; } - for (var i = 0; i < j; i++) - { - v[I[k + i]] = k + j - 1; - } - if (j == 1) + if (v[I[k + i] + h] == x) { - I[k] = -1; + Swap(ref I[k + j], ref I[k + i]); + j++; } } - } - else - { - var x = v[I[start + len / 2] + h]; - var jj = 0; - var kk = 0; - for (var i2 = start; i2 < start + len; i2++) + + for (var i = 0; i < j; i++) + { + v[I[k + i]] = k + j - 1; + } + if (j == 1) { - if (v[I[i2] + h] < x) jj++; - if (v[I[i2] + h] == x) kk++; + I[k] = -1; } + } + } + else + { + var x = v[I[start + len / 2] + h]; + var jj = 0; + var kk = 0; + for (var i2 = start; i2 < start + len; i2++) + { + if (v[I[i2] + h] < x) jj++; + if (v[I[i2] + h] == x) kk++; + } - jj += start; - kk += jj; + jj += start; + kk += jj; - var i = start; - var j = 0; - var k = 0; - while (i < jj) + var i = start; + var j = 0; + var k = 0; + while (i < jj) + { + if (v[I[i] + h] < x) { - if (v[I[i] + h] < x) - { - i++; - } - else if (v[I[i] + h] == x) - { - Swap(ref I[i], ref I[jj + j]); - j++; - } - else - { - Swap(ref I[i], ref I[kk + k]); - k++; - } + i++; } - - while (jj + j < kk) + else if (v[I[i] + h] == x) { - if (v[I[jj + j] + h] == x) - { - j++; - } - else - { - Swap(ref I[jj + j], ref I[kk + k]); - k++; - } + Swap(ref I[i], ref I[jj + j]); + j++; } - - if (jj > start) Split(I, v, start, jj - start, h); - - for (i = 0; i < kk - jj; i++) + else { - v[I[jj + i]] = kk - 1; + Swap(ref I[i], ref I[kk + k]); + k++; } + } - if (jj == kk - 1) + while (jj + j < kk) + { + if (v[I[jj + j] + h] == x) { - I[jj] = -1; + j++; } - - if (start + len > kk) + else { - var start1 = start; - start = kk; - len = start1 + len - kk; - continue; + Swap(ref I[jj + j], ref I[kk + k]); + k++; } } - break; + if (jj > start) Split(I, v, start, jj - start, h); + + for (i = 0; i < kk - jj; i++) + { + v[I[jj + i]] = kk - 1; + } + + if (jj == kk - 1) + { + I[jj] = -1; + } + + if (start + len > kk) + { + var start1 = start; + start = kk; + len = start1 + len - kk; + continue; + } } + + break; } + } + + static Memory SuffixSort(ReadOnlyMemory oldData) + { + Span buckets = stackalloc int[256]; + Span I = new int[oldData.Length + 1]; + Span v = new int[oldData.Length + 1]; - static Memory SuffixSort(ReadOnlyMemory oldData) + foreach (var oldByte in oldData.Span) { - Span buckets = stackalloc int[256]; - Span I = new int[oldData.Length + 1]; - Span v = new int[oldData.Length + 1]; + buckets[oldByte]++; + } - foreach (var oldByte in oldData.Span) - { - buckets[oldByte]++; - } + for (var i = 1; i < 256; i++) + { + buckets[i] += buckets[i - 1]; + } - for (var i = 1; i < 256; i++) - { - buckets[i] += buckets[i - 1]; - } + for (var i = 255; i > 0; i--) + { + buckets[i] = buckets[i - 1]; + } - for (var i = 255; i > 0; i--) - { - buckets[i] = buckets[i - 1]; - } + buckets[0] = 0; - buckets[0] = 0; + for (var i = 0; i < oldData.Length; i++) + { + I[++buckets[oldData.Span[i]]] = i; + } - for (var i = 0; i < oldData.Length; i++) - { - I[++buckets[oldData.Span[i]]] = i; - } + for (var i = 0; i < oldData.Length; i++) + { + v[i] = buckets[oldData.Span[i]]; + } - for (var i = 0; i < oldData.Length; i++) + for (var i = 1; i < 256; i++) + { + if (buckets[i] == buckets[i - 1] + 1) { - v[i] = buckets[oldData.Span[i]]; + I[buckets[i]] = -1; } + } + + I[0] = -1; - for (var i = 1; i < 256; i++) + for (var h = 1; I[0] != -(oldData.Length + 1); h += h) + { + var len = 0; + var i = 0; + while (i < oldData.Length + 1) { - if (buckets[i] == buckets[i - 1] + 1) + if (I[i] < 0) { - I[buckets[i]] = -1; + len -= I[i]; + i -= I[i]; } - } - - I[0] = -1; - - for (var h = 1; I[0] != -(oldData.Length + 1); h += h) - { - var len = 0; - var i = 0; - while (i < oldData.Length + 1) + else { - if (I[i] < 0) - { - len -= I[i]; - i -= I[i]; - } - else + if (len != 0) { - if (len != 0) - { - I[i - len] = -len; - } - len = v[I[i]] + 1 - i; - Split(I, v, i, len, h); - i += len; - len = 0; + I[i - len] = -len; } - } - - if (len != 0) - { - I[i - len] = -len; + len = v[I[i]] + 1 - i; + Split(I, v, i, len, h); + i += len; + len = 0; } } - for (var i = 0; i < oldData.Length + 1; i++) + if (len != 0) { - I[v[i]] = i; + I[i - len] = -len; } - - return I.ToArray(); } - static void Swap(ref int first, ref int second) + for (var i = 0; i < oldData.Length + 1; i++) { - var temp = first; - first = second; - second = temp; + I[v[i]] = i; } - static long ReadInt64(ReadOnlySpan buffer, int offset) - { - long value = buffer[offset + 7] & 0x7F; + return I.ToArray(); + } - for (var index = 6; index >= 0; index--) - { - value *= 256; - value += buffer[offset + index]; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void Swap(ref int first, ref int second) + { + (first, second) = (second, first); + } - if ((buffer[offset + 7] & 0x80) != 0) - value = -value; + static long ReadInt64(ReadOnlySpan buffer, int offset) + { + long value = buffer[offset + 7] & 0x7F; - return value; + for (var index = 6; index >= 0; index--) + { + value *= 256; + value += buffer[offset + index]; } - static void WriteInt64(long value, Span buffer, int offset) - { - var valueToWrite = value < 0 ? -value : value; + if ((buffer[offset + 7] & 0x80) != 0) + value = -value; - for (var byteIndex = 0; byteIndex < 8; byteIndex++) - { - buffer[offset + byteIndex] = (byte)(valueToWrite % 256); - valueToWrite -= buffer[offset + byteIndex]; - valueToWrite /= 256; - } + return value; + } - if (value < 0) - { - buffer[offset + 7] |= 0x80; - } + static void WriteInt64(long value, Span buffer, int offset) + { + var valueToWrite = value < 0 ? -value : value; + + for (var byteIndex = 0; byteIndex < 8; byteIndex++) + { + buffer[offset + byteIndex] = (byte)(valueToWrite % 256); + valueToWrite -= buffer[offset + byteIndex]; + valueToWrite /= 256; } - const long CFileSignature = 0x3034464649445342L; - const int CHeaderSize = 32; + if (value < 0) + { + buffer[offset + 7] |= 0x80; + } } + const long CFileSignature = 0x3034464649445342L; + const int CHeaderSize = 32; +} + +/// +/// A that wraps another stream. One major feature of is that it does not dispose the +/// underlying stream when it is disposed if Ownership.None is used; this is useful when using classes such as and +/// that take ownership of the stream passed to their constructors. +/// +/// See WrappingStream Implementation. +internal class WrappingStream : Stream +{ /// - /// A that wraps another stream. One major feature of is that it does not dispose the - /// underlying stream when it is disposed if Ownership.None is used; this is useful when using classes such as and - /// that take ownership of the stream passed to their constructors. + /// Initializes a new instance of the class. /// - /// See WrappingStream Implementation. - internal class WrappingStream : Stream + /// The wrapped stream. + /// Use Owns if the wrapped stream should be disposed when this stream is disposed. + public WrappingStream(Stream streamBase, Ownership ownership) { - /// - /// Initializes a new instance of the class. - /// - /// The wrapped stream. - /// Use Owns if the wrapped stream should be disposed when this stream is disposed. - public WrappingStream(Stream streamBase, Ownership ownership) - { - WrappedStream = streamBase ?? throw new ArgumentNullException(nameof(streamBase)); - _ownership = ownership; - } + WrappedStream = streamBase ?? throw new ArgumentNullException(nameof(streamBase)); + _ownership = ownership; + } - /// - /// Gets a value indicating whether the current stream supports reading. - /// - /// true if the stream supports reading; otherwise, false. - public override bool CanRead => WrappedStream?.CanRead ?? false; - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - /// true if the stream supports seeking; otherwise, false. - public override bool CanSeek => WrappedStream?.CanSeek ?? false; - - /// - /// Gets a value indicating whether the current stream supports writing. - /// - /// true if the stream supports writing; otherwise, false. - public override bool CanWrite => WrappedStream?.CanWrite ?? false; - - /// - /// Gets the length in bytes of the stream. - /// - public override long Length - { - get { ThrowIfDisposed(); return WrappedStream.Length; } - } + /// + /// Gets a value indicating whether the current stream supports reading. + /// + /// true if the stream supports reading; otherwise, false. + public override bool CanRead => WrappedStream?.CanRead ?? false; - /// - /// Gets or sets the position within the current stream. - /// - public override long Position - { - get { ThrowIfDisposed(); return WrappedStream.Position; } - set { ThrowIfDisposed(); WrappedStream.Position = value; } - } + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + /// true if the stream supports seeking; otherwise, false. + public override bool CanSeek => WrappedStream?.CanSeek ?? false; - /// - /// Begins an asynchronous read operation. - /// - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - ThrowIfDisposed(); - return WrappedStream.BeginRead(buffer, offset, count, callback, state); - } + /// + /// Gets a value indicating whether the current stream supports writing. + /// + /// true if the stream supports writing; otherwise, false. + public override bool CanWrite => WrappedStream?.CanWrite ?? false; - /// - /// Begins an asynchronous write operation. - /// - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) - { - ThrowIfDisposed(); - return WrappedStream.BeginWrite(buffer, offset, count, callback, state); - } + /// + /// Gets the length in bytes of the stream. + /// + public override long Length + { + get { ThrowIfDisposed(); return WrappedStream.Length; } + } - /// - /// Waits for the pending asynchronous read to complete. - /// - public override int EndRead(IAsyncResult asyncResult) - { - ThrowIfDisposed(); - return WrappedStream.EndRead(asyncResult); - } + /// + /// Gets or sets the position within the current stream. + /// + public override long Position + { + get { ThrowIfDisposed(); return WrappedStream.Position; } + set { ThrowIfDisposed(); WrappedStream.Position = value; } + } - /// - /// Ends an asynchronous write operation. - /// - public override void EndWrite(IAsyncResult asyncResult) - { - ThrowIfDisposed(); - WrappedStream.EndWrite(asyncResult); - } + /// + /// Begins an asynchronous read operation. + /// + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + ThrowIfDisposed(); + return WrappedStream.BeginRead(buffer, offset, count, callback, state); + } - /// - /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. - /// - public override void Flush() - { - ThrowIfDisposed(); - WrappedStream.Flush(); - } + /// + /// Begins an asynchronous write operation. + /// + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + ThrowIfDisposed(); + return WrappedStream.BeginWrite(buffer, offset, count, callback, state); + } - /// - /// Reads a sequence of bytes from the current stream and advances the position - /// within the stream by the number of bytes read. - /// - public override int Read(byte[] buffer, int offset, int count) - { - ThrowIfDisposed(); - return WrappedStream.Read(buffer, offset, count); - } + /// + /// Waits for the pending asynchronous read to complete. + /// + public override int EndRead(IAsyncResult asyncResult) + { + ThrowIfDisposed(); + return WrappedStream.EndRead(asyncResult); + } - /// - /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. - /// - public override int ReadByte() - { - ThrowIfDisposed(); - return WrappedStream.ReadByte(); - } + /// + /// Ends an asynchronous write operation. + /// + public override void EndWrite(IAsyncResult asyncResult) + { + ThrowIfDisposed(); + WrappedStream.EndWrite(asyncResult); + } - /// - /// Sets the position within the current stream. - /// - /// A byte offset relative to the parameter. - /// A value of type indicating the reference point used to obtain the new position. - /// The new position within the current stream. - public override long Seek(long offset, SeekOrigin origin) - { - ThrowIfDisposed(); - return WrappedStream.Seek(offset, origin); - } + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + public override void Flush() + { + ThrowIfDisposed(); + WrappedStream.Flush(); + } - /// - /// Sets the length of the current stream. - /// - /// The desired length of the current stream in bytes. - public override void SetLength(long value) - { - ThrowIfDisposed(); - WrappedStream.SetLength(value); - } + /// + /// Reads a sequence of bytes from the current stream and advances the position + /// within the stream by the number of bytes read. + /// + public override int Read(byte[] buffer, int offset, int count) + { + ThrowIfDisposed(); + return WrappedStream.Read(buffer, offset, count); + } - /// - /// Writes a sequence of bytes to the current stream and advances the current position - /// within this stream by the number of bytes written. - /// - public override void Write(byte[] buffer, int offset, int count) - { - ThrowIfDisposed(); - WrappedStream.Write(buffer, offset, count); - } + /// + /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. + /// + public override int ReadByte() + { + ThrowIfDisposed(); + return WrappedStream.ReadByte(); + } - /// - /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. - /// - public override void WriteByte(byte value) - { - ThrowIfDisposed(); - WrappedStream.WriteByte(value); - } + /// + /// Sets the position within the current stream. + /// + /// A byte offset relative to the parameter. + /// A value of type indicating the reference point used to obtain the new position. + /// The new position within the current stream. + public override long Seek(long offset, SeekOrigin origin) + { + ThrowIfDisposed(); + return WrappedStream.Seek(offset, origin); + } + + /// + /// Sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + public override void SetLength(long value) + { + ThrowIfDisposed(); + WrappedStream.SetLength(value); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position + /// within this stream by the number of bytes written. + /// + public override void Write(byte[] buffer, int offset, int count) + { + ThrowIfDisposed(); + WrappedStream.Write(buffer, offset, count); + } + + /// + /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. + /// + public override void WriteByte(byte value) + { + ThrowIfDisposed(); + WrappedStream.WriteByte(value); + } + + /// + /// Gets the wrapped stream. + /// + /// The wrapped stream. + protected Stream WrappedStream { get; set; } - /// - /// Gets the wrapped stream. - /// - /// The wrapped stream. - protected Stream WrappedStream { get; set; } - - /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + try { - try - { - // doesn't close the base stream, but just prevents access to it through this WrappingStream - if (disposing) - { - if (WrappedStream != null && _ownership == Ownership.Owns) - WrappedStream.Dispose(); - WrappedStream = null; - } - } - finally + // doesn't close the base stream, but just prevents access to it through this WrappingStream + if (disposing) { - base.Dispose(disposing); + if (WrappedStream != null && _ownership == Ownership.Owns) + WrappedStream.Dispose(); + WrappedStream = null; } } - - void ThrowIfDisposed() + finally { - // throws an ObjectDisposedException if this object has been disposed - if (WrappedStream == null) - { - throw new ObjectDisposedException(GetType().Name); - } + base.Dispose(disposing); } - - readonly Ownership _ownership; } - /// - /// Indicates whether an object takes ownership of an item. - /// - internal enum Ownership + void ThrowIfDisposed() { - /// - /// The object does not own this item. - /// - None, - - /// - /// The object owns this item, and is responsible for releasing it. - /// - Owns + // throws an ObjectDisposedException if this object has been disposed + if (WrappedStream == null) + { + throw new ObjectDisposedException(GetType().Name); + } } + + readonly Ownership _ownership; } + +/// +/// Indicates whether an object takes ownership of an item. +/// +internal enum Ownership +{ + /// + /// The object does not own this item. + /// + None, + + /// + /// The object owns this item, and is responsible for releasing it. + /// + Owns +} \ No newline at end of file diff --git a/src/Snap/Core/SnapConstants.cs b/src/Snap/Core/SnapConstants.cs index d67ee707..495579ea 100644 --- a/src/Snap/Core/SnapConstants.cs +++ b/src/Snap/Core/SnapConstants.cs @@ -3,32 +3,31 @@ using System.Runtime.InteropServices; using NuGet.Frameworks; -namespace Snap.Core +namespace Snap.Core; + +internal static class SnapConstants { - internal static class SnapConstants - { - public static readonly string SnapAppLibraryName = "Snap.App"; - public static readonly string SnapDllFilename = "Snap.dll"; - public static string SnapAppDllFilename => $"{SnapAppLibraryName}.dll"; - public static string SetupNupkgFilename = "Setup.nupkg"; - public const string Sha256EmptyFileChecksum = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + public static readonly string SnapAppLibraryName = "Snap.App"; + public static readonly string SnapDllFilename = "Snap.dll"; + public static string SnapAppDllFilename => $"{SnapAppLibraryName}.dll"; + public static string SetupNupkgFilename = "Setup.nupkg"; + public const string Sha256EmptyFileChecksum = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - public static readonly string SnapUniqueTargetPathFolderName = BuildSnapNuspecUniqueFolderName(); - public static readonly string NuspecTargetFrameworkMoniker = NuGetFramework.AnyFramework.Framework; - public static readonly string NuspecRootTargetPath = $"lib/{NuspecTargetFrameworkMoniker}"; - public static readonly string NuspecAssetsTargetPath = $"{NuspecRootTargetPath}/{SnapUniqueTargetPathFolderName}"; - public const string ReleasesFilename = "Snap.Releases"; + public static readonly string SnapUniqueTargetPathFolderName = BuildSnapNuspecUniqueFolderName(); + public static readonly string NuspecTargetFrameworkMoniker = NuGetFramework.AnyFramework.Framework; + public static readonly string NuspecRootTargetPath = $"lib/{NuspecTargetFrameworkMoniker}"; + public static readonly string NuspecAssetsTargetPath = $"{NuspecRootTargetPath}/{SnapUniqueTargetPathFolderName}"; + public const string ReleasesFilename = "Snap.Releases"; - static string BuildSnapNuspecUniqueFolderName() + static string BuildSnapNuspecUniqueFolderName() + { + var guidStr = typeof(SnapConstants).Assembly.GetCustomAttribute()?.Value; + Guid.TryParse(guidStr, out var assemblyGuid); + if (assemblyGuid == Guid.Empty) { - var guidStr = typeof(SnapConstants).Assembly.GetCustomAttribute()?.Value; - Guid.TryParse(guidStr, out var assemblyGuid); - if (assemblyGuid == Guid.Empty) - { - throw new Exception("Fatal error! Assembly guid is empty"); - } - - return assemblyGuid.ToString("N"); + throw new Exception("Fatal error! Assembly guid is empty"); } + + return assemblyGuid.ToString("N"); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/SnapCryptoProvider.cs b/src/Snap/Core/SnapCryptoProvider.cs index 29e05fd3..fe57bf98 100644 --- a/src/Snap/Core/SnapCryptoProvider.cs +++ b/src/Snap/Core/SnapCryptoProvider.cs @@ -12,153 +12,152 @@ using Snap.Core.Models; using Snap.Extensions; -namespace Snap.Core +namespace Snap.Core; + +internal interface ISnapCryptoProvider { - internal interface ISnapCryptoProvider - { - string Sha256(byte[] content); - string Sha256(Stream content); - string Sha256(StringBuilder content, Encoding encoding); - string Sha256(AssemblyDefinition assemblyDefinition); - string Sha256(SnapRelease snapRelease, IPackageCoreReader packageCoreReader, ISnapPack snapPack); - string Sha256(SnapRelease snapRelease, PackageBuilder packageBuilder); - } + string Sha256(byte[] content); + string Sha256(Stream content); + string Sha256(StringBuilder content, Encoding encoding); + string Sha256(AssemblyDefinition assemblyDefinition); + string Sha256(SnapRelease snapRelease, IPackageCoreReader packageCoreReader, ISnapPack snapPack); + string Sha256(SnapRelease snapRelease, PackageBuilder packageBuilder); +} - internal sealed class SnapCryptoProvider : ISnapCryptoProvider +internal sealed class SnapCryptoProvider : ISnapCryptoProvider +{ + public string Sha256(byte[] content) { - public string Sha256(byte[] content) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - - var sha256 = SHA256.Create(); - var hash = sha256.ComputeHash(content); - - return HashToString(hash); - } - - public string Sha256(Stream content) - { - if (content == null) throw new ArgumentNullException(nameof(content)); - - if (!content.CanSeek) - { - throw new Exception("Stream must be seekable"); - } - - content.Seek(0, SeekOrigin.Begin); + if (content == null) throw new ArgumentNullException(nameof(content)); - var sha256 = SHA256.Create(); - var hash = sha256.ComputeHash(content); + var sha256 = SHA256.Create(); + var hash = sha256.ComputeHash(content); - content.Seek(0, SeekOrigin.Begin); + return HashToString(hash); + } - return HashToString(hash); - } + public string Sha256(Stream content) + { + if (content == null) throw new ArgumentNullException(nameof(content)); - public string Sha256([NotNull] StringBuilder content, [NotNull] Encoding encoding) + if (!content.CanSeek) { - if (content == null) throw new ArgumentNullException(nameof(content)); - if (encoding == null) throw new ArgumentNullException(nameof(encoding)); - return Sha256(encoding.GetBytes(content.ToString())); + throw new Exception("Stream must be seekable"); } - public string Sha256([NotNull] AssemblyDefinition assemblyDefinition) - { - if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); - using var outputStream = new MemoryStream(); - assemblyDefinition.Write(outputStream); - outputStream.Seek(0, SeekOrigin.Begin); - return Sha256(outputStream); - } + content.Seek(0, SeekOrigin.Begin); - public string Sha256(SnapRelease snapRelease, [NotNull] IPackageCoreReader packageCoreReader, [NotNull] ISnapPack snapPack) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - if (packageCoreReader == null) throw new ArgumentNullException(nameof(packageCoreReader)); - if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); + var sha256 = SHA256.Create(); + var hash = sha256.ComputeHash(content); - var packageArchiveFiles = packageCoreReader.GetFiles(); - - var checksumFiles = GetChecksumFilesForSnapRelease(snapRelease); + content.Seek(0, SeekOrigin.Begin); - var inputStreams = checksumFiles - .Select(checksum => (checksum, targetPath: packageArchiveFiles.SingleOrDefault(targetPath => checksum.NuspecTargetPath == targetPath))) - .Select(x => - { - var (checksum, packageArchiveReaderTargetPath) = x; - if (packageArchiveReaderTargetPath == null) - { - throw new FileNotFoundException($"Unable to find file in nupkg: {snapRelease.Filename}.", checksum.NuspecTargetPath); - } - return (checksum, packageCoreReader.GetStream(packageArchiveReaderTargetPath)); - }); - - return Sha256(inputStreams); - } + return HashToString(hash); + } - public string Sha256([NotNull] SnapRelease snapRelease, [NotNull] PackageBuilder packageBuilder) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - if (packageBuilder == null) throw new ArgumentNullException(nameof(packageBuilder)); + public string Sha256([NotNull] StringBuilder content, [NotNull] Encoding encoding) + { + if (content == null) throw new ArgumentNullException(nameof(content)); + if (encoding == null) throw new ArgumentNullException(nameof(encoding)); + return Sha256(encoding.GetBytes(content.ToString())); + } - var checksumFiles = GetChecksumFilesForSnapRelease(snapRelease); + public string Sha256([NotNull] AssemblyDefinition assemblyDefinition) + { + if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); + using var outputStream = new MemoryStream(); + assemblyDefinition.Write(outputStream); + outputStream.Seek(0, SeekOrigin.Begin); + return Sha256(outputStream); + } + + public string Sha256(SnapRelease snapRelease, [NotNull] IPackageCoreReader packageCoreReader, [NotNull] ISnapPack snapPack) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + if (packageCoreReader == null) throw new ArgumentNullException(nameof(packageCoreReader)); + if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); - var enumerable = checksumFiles - .Select(checksum => (checksum, packageFile: packageBuilder.GetPackageFile(checksum.NuspecTargetPath, StringComparison.OrdinalIgnoreCase))); + var packageArchiveFiles = packageCoreReader.GetFiles(); + + var checksumFiles = GetChecksumFilesForSnapRelease(snapRelease); - return Sha256(enumerable.Select(x => + var inputStreams = checksumFiles + .Select(checksum => (checksum, targetPath: packageArchiveFiles.SingleOrDefault(targetPath => checksum.NuspecTargetPath == targetPath))) + .Select(x => { - var (checksum, packageFile) = x; - if (packageFile == null) + var (checksum, packageArchiveReaderTargetPath) = x; + if (packageArchiveReaderTargetPath == null) { throw new FileNotFoundException($"Unable to find file in nupkg: {snapRelease.Filename}.", checksum.NuspecTargetPath); } + return (checksum, packageCoreReader.GetStream(packageArchiveReaderTargetPath)); + }); - return (checksum, packageFile.GetStream()); - })); - } + return Sha256(inputStreams); + } + + public string Sha256([NotNull] SnapRelease snapRelease, [NotNull] PackageBuilder packageBuilder) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + if (packageBuilder == null) throw new ArgumentNullException(nameof(packageBuilder)); + + var checksumFiles = GetChecksumFilesForSnapRelease(snapRelease); - string Sha256(IEnumerable<(SnapReleaseChecksum targetPath, Stream srcStream)> inputStreams) + var enumerable = checksumFiles + .Select(checksum => (checksum, packageFile: packageBuilder.GetPackageFile(checksum.NuspecTargetPath, StringComparison.OrdinalIgnoreCase))); + + return Sha256(enumerable.Select(x => { - var sb = new StringBuilder(); - foreach (var (_, srcStream) in inputStreams) + var (checksum, packageFile) = x; + if (packageFile == null) { - if (srcStream.CanSeek) - { - srcStream.Seek(0, SeekOrigin.Begin); - var sha256 = Sha256(srcStream); - sb.Append(sha256); - srcStream.Seek(0, SeekOrigin.Begin); - continue; - } - - using var intermediateStream = new MemoryStream(); - { - srcStream.CopyTo(intermediateStream); - var sha256 = Sha256(intermediateStream); - sb.Append(sha256); - } + throw new FileNotFoundException($"Unable to find file in nupkg: {snapRelease.Filename}.", checksum.NuspecTargetPath); } - return Sha256(sb, Encoding.UTF8); - } + return (checksum, packageFile.GetStream()); + })); + } - static string HashToString([NotNull] byte[] hash) + string Sha256(IEnumerable<(SnapReleaseChecksum targetPath, Stream srcStream)> inputStreams) + { + var sb = new StringBuilder(); + foreach (var (_, srcStream) in inputStreams) { - if (hash == null) throw new ArgumentNullException(nameof(hash)); - var builder = new StringBuilder(); - foreach (var t in hash) + if (srcStream.CanSeek) { - builder.Append(t.ToString("x2", CultureInfo.InvariantCulture)); + srcStream.Seek(0, SeekOrigin.Begin); + var sha256 = Sha256(srcStream); + sb.Append(sha256); + srcStream.Seek(0, SeekOrigin.Begin); + continue; } - return builder.ToString(); + using var intermediateStream = new MemoryStream(); + { + srcStream.CopyTo(intermediateStream); + var sha256 = Sha256(intermediateStream); + sb.Append(sha256); + } } - static IEnumerable GetChecksumFilesForSnapRelease(SnapRelease snapRelease) + return Sha256(sb, Encoding.UTF8); + } + + static string HashToString([NotNull] byte[] hash) + { + if (hash == null) throw new ArgumentNullException(nameof(hash)); + var builder = new StringBuilder(); + foreach (var t in hash) { - var files = snapRelease.IsDelta ? snapRelease.New.Concat(snapRelease.Modified).ToList() : snapRelease.Files; - return files.OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()); + builder.Append(t.ToString("x2", CultureInfo.InvariantCulture)); } + + return builder.ToString(); } -} + + static IEnumerable GetChecksumFilesForSnapRelease(SnapRelease snapRelease) + { + var files = snapRelease.IsDelta ? snapRelease.New.Concat(snapRelease.Modified).ToList() : snapRelease.Files; + return files.OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()); + } +} \ No newline at end of file diff --git a/src/Snap/Core/SnapExtractor.cs b/src/Snap/Core/SnapExtractor.cs index 2229328f..4d89456a 100644 --- a/src/Snap/Core/SnapExtractor.cs +++ b/src/Snap/Core/SnapExtractor.cs @@ -12,109 +12,108 @@ using Snap.Core.Resources; using Snap.Extensions; -namespace Snap.Core +namespace Snap.Core; + +internal interface ISnapExtractor { - internal interface ISnapExtractor - { - Task> ExtractAsync(string nupkgAbsolutePath, string destinationDirectoryAbsolutePath, SnapRelease snapRelease, CancellationToken cancellationToken = default); - Task> ExtractAsync(string destinationDirectoryAbsolutePath, SnapRelease snapRelease, IAsyncPackageCoreReader asyncPackageCoreReader, CancellationToken cancellationToken = default); - Task GetSnapAppsReleasesAsync(IAsyncPackageCoreReader asyncPackageCoreReader, [NotNull] ISnapAppReader snapAppReader, CancellationToken cancellationToken = default); - } + Task> ExtractAsync(string nupkgAbsolutePath, string destinationDirectoryAbsolutePath, SnapRelease snapRelease, CancellationToken cancellationToken = default); + Task> ExtractAsync(string destinationDirectoryAbsolutePath, SnapRelease snapRelease, IAsyncPackageCoreReader asyncPackageCoreReader, CancellationToken cancellationToken = default); + Task GetSnapAppsReleasesAsync(IAsyncPackageCoreReader asyncPackageCoreReader, [NotNull] ISnapAppReader snapAppReader, CancellationToken cancellationToken = default); +} - internal sealed class SnapExtractor : ISnapExtractor - { - readonly ISnapFilesystem _snapFilesystem; - readonly ISnapPack _snapPack; - readonly ISnapEmbeddedResources _snapEmbeddedResources; +internal sealed class SnapExtractor : ISnapExtractor +{ + readonly ISnapFilesystem _snapFilesystem; + readonly ISnapPack _snapPack; + readonly ISnapEmbeddedResources _snapEmbeddedResources; - public SnapExtractor(ISnapFilesystem snapFilesystem, [NotNull] ISnapPack snapPack, [NotNull] ISnapEmbeddedResources snapEmbeddedResources) - { - _snapFilesystem = snapFilesystem ?? throw new ArgumentNullException(nameof(snapFilesystem)); - _snapPack = snapPack ?? throw new ArgumentNullException(nameof(snapPack)); - _snapEmbeddedResources = snapEmbeddedResources ?? throw new ArgumentNullException(nameof(snapEmbeddedResources)); - } + public SnapExtractor(ISnapFilesystem snapFilesystem, [NotNull] ISnapPack snapPack, [NotNull] ISnapEmbeddedResources snapEmbeddedResources) + { + _snapFilesystem = snapFilesystem ?? throw new ArgumentNullException(nameof(snapFilesystem)); + _snapPack = snapPack ?? throw new ArgumentNullException(nameof(snapPack)); + _snapEmbeddedResources = snapEmbeddedResources ?? throw new ArgumentNullException(nameof(snapEmbeddedResources)); + } - public async Task> ExtractAsync(string nupkgAbsolutePath, string destinationDirectoryAbsolutePath, SnapRelease snapRelease, CancellationToken cancellationToken = default) - { - if (nupkgAbsolutePath == null) throw new ArgumentNullException(nameof(nupkgAbsolutePath)); - if (destinationDirectoryAbsolutePath == null) throw new ArgumentNullException(nameof(destinationDirectoryAbsolutePath)); + public async Task> ExtractAsync(string nupkgAbsolutePath, string destinationDirectoryAbsolutePath, SnapRelease snapRelease, CancellationToken cancellationToken = default) + { + if (nupkgAbsolutePath == null) throw new ArgumentNullException(nameof(nupkgAbsolutePath)); + if (destinationDirectoryAbsolutePath == null) throw new ArgumentNullException(nameof(destinationDirectoryAbsolutePath)); - using var packageArchiveReader = new PackageArchiveReader(nupkgAbsolutePath); - return await ExtractAsync(destinationDirectoryAbsolutePath, snapRelease, packageArchiveReader, cancellationToken); - } + using var packageArchiveReader = new PackageArchiveReader(nupkgAbsolutePath); + return await ExtractAsync(destinationDirectoryAbsolutePath, snapRelease, packageArchiveReader, cancellationToken); + } - public async Task> ExtractAsync(string destinationDirectoryAbsolutePath, [NotNull] SnapRelease snapRelease, - IAsyncPackageCoreReader asyncPackageCoreReader, CancellationToken cancellationToken = default) - { - if (destinationDirectoryAbsolutePath == null) throw new ArgumentNullException(nameof(destinationDirectoryAbsolutePath)); - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - if (asyncPackageCoreReader == null) throw new ArgumentNullException(nameof(asyncPackageCoreReader)); + public async Task> ExtractAsync(string destinationDirectoryAbsolutePath, [NotNull] SnapRelease snapRelease, + IAsyncPackageCoreReader asyncPackageCoreReader, CancellationToken cancellationToken = default) + { + if (destinationDirectoryAbsolutePath == null) throw new ArgumentNullException(nameof(destinationDirectoryAbsolutePath)); + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + if (asyncPackageCoreReader == null) throw new ArgumentNullException(nameof(asyncPackageCoreReader)); - var snapApp = await _snapPack.GetSnapAppAsync(asyncPackageCoreReader, cancellationToken); - var coreRunExeFilename = _snapEmbeddedResources.GetCoreRunExeFilenameForSnapApp(snapApp); - var extractedFiles = new List(); + var snapApp = await _snapPack.GetSnapAppAsync(asyncPackageCoreReader, cancellationToken); + var coreRunExeFilename = _snapEmbeddedResources.GetCoreRunExeFilenameForSnapApp(snapApp); + var extractedFiles = new List(); - _snapFilesystem.DirectoryCreateIfNotExists(destinationDirectoryAbsolutePath); + _snapFilesystem.DirectoryCreateIfNotExists(destinationDirectoryAbsolutePath); - var files = !snapRelease.IsFull ? - snapRelease - .New - .Concat(snapRelease.Modified) - .OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()) - .ToList() : - snapRelease.Files; + var files = !snapRelease.IsFull ? + snapRelease + .New + .Concat(snapRelease.Modified) + .OrderBy(x => x.NuspecTargetPath, new OrdinalIgnoreCaseComparer()) + .ToList() : + snapRelease.Files; - foreach (var checksum in files) - { - var isSnapRootTargetItem = checksum.NuspecTargetPath.StartsWith(SnapConstants.NuspecAssetsTargetPath); + foreach (var checksum in files) + { + var isSnapRootTargetItem = checksum.NuspecTargetPath.StartsWith(SnapConstants.NuspecAssetsTargetPath); - string dstFilename; - if (isSnapRootTargetItem) - { - dstFilename = _snapFilesystem.PathCombine(destinationDirectoryAbsolutePath, checksum.Filename); + string dstFilename; + if (isSnapRootTargetItem) + { + dstFilename = _snapFilesystem.PathCombine(destinationDirectoryAbsolutePath, checksum.Filename); - if (checksum.Filename == coreRunExeFilename) - { - dstFilename = _snapFilesystem.PathCombine( - _snapFilesystem.DirectoryGetParent(destinationDirectoryAbsolutePath), checksum.Filename); - } - } - else + if (checksum.Filename == coreRunExeFilename) { - var targetPath = checksum.NuspecTargetPath[(SnapConstants.NuspecRootTargetPath.Length + 1)..]; - dstFilename = _snapFilesystem.PathCombine(destinationDirectoryAbsolutePath, - _snapFilesystem.PathEnsureThisOsDirectoryPathSeperator(targetPath)); + dstFilename = _snapFilesystem.PathCombine( + _snapFilesystem.DirectoryGetParent(destinationDirectoryAbsolutePath), checksum.Filename); } + } + else + { + var targetPath = checksum.NuspecTargetPath[(SnapConstants.NuspecRootTargetPath.Length + 1)..]; + dstFilename = _snapFilesystem.PathCombine(destinationDirectoryAbsolutePath, + _snapFilesystem.PathEnsureThisOsDirectoryPathSeperator(targetPath)); + } - var thisDestinationDir = _snapFilesystem.PathGetDirectoryName(dstFilename); - _snapFilesystem.DirectoryCreateIfNotExists(thisDestinationDir); + var thisDestinationDir = _snapFilesystem.PathGetDirectoryName(dstFilename); + _snapFilesystem.DirectoryCreateIfNotExists(thisDestinationDir); - var srcStream = await asyncPackageCoreReader.GetStreamAsync(checksum.NuspecTargetPath, cancellationToken); + var srcStream = await asyncPackageCoreReader.GetStreamAsync(checksum.NuspecTargetPath, cancellationToken); - await _snapFilesystem.FileWriteAsync(srcStream, dstFilename, cancellationToken); + await _snapFilesystem.FileWriteAsync(srcStream, dstFilename, cancellationToken); - extractedFiles.Add(dstFilename); - } - - return extractedFiles; + extractedFiles.Add(dstFilename); } - public async Task GetSnapAppsReleasesAsync([NotNull] IAsyncPackageCoreReader asyncPackageCoreReader, ISnapAppReader snapAppReader, CancellationToken cancellationToken = default) - { - if (asyncPackageCoreReader == null) throw new ArgumentNullException(nameof(asyncPackageCoreReader)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - - var snapReleasesFilename = _snapFilesystem.PathCombine(SnapConstants.NuspecRootTargetPath, SnapConstants.ReleasesFilename); - await using var snapReleasesCompressedStream = - await asyncPackageCoreReader - .GetStreamAsync(snapReleasesFilename, cancellationToken) - .ReadToEndAsync(cancellationToken: cancellationToken); - await using var snapReleasesUncompressedStream = new MemoryStream(); - using var reader = ReaderFactory.Open(snapReleasesCompressedStream); - reader.MoveToNextEntry(); - reader.WriteEntryTo(snapReleasesUncompressedStream); - snapReleasesUncompressedStream.Seek(0, SeekOrigin.Begin); - return await snapAppReader.BuildSnapAppsReleasesFromStreamAsync(snapReleasesUncompressedStream); - } + return extractedFiles; } -} + + public async Task GetSnapAppsReleasesAsync([NotNull] IAsyncPackageCoreReader asyncPackageCoreReader, ISnapAppReader snapAppReader, CancellationToken cancellationToken = default) + { + if (asyncPackageCoreReader == null) throw new ArgumentNullException(nameof(asyncPackageCoreReader)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + + var snapReleasesFilename = _snapFilesystem.PathCombine(SnapConstants.NuspecRootTargetPath, SnapConstants.ReleasesFilename); + await using var snapReleasesCompressedStream = + await asyncPackageCoreReader + .GetStreamAsync(snapReleasesFilename, cancellationToken) + .ReadToEndAsync(cancellationToken: cancellationToken); + await using var snapReleasesUncompressedStream = new MemoryStream(); + using var reader = ReaderFactory.Open(snapReleasesCompressedStream); + reader.MoveToNextEntry(); + reader.WriteEntryTo(snapReleasesUncompressedStream); + snapReleasesUncompressedStream.Seek(0, SeekOrigin.Begin); + return await snapAppReader.BuildSnapAppsReleasesFromStreamAsync(snapReleasesUncompressedStream); + } +} \ No newline at end of file diff --git a/src/Snap/Core/SnapFilesystem.cs b/src/Snap/Core/SnapFilesystem.cs index fb254f8d..3d16dcae 100644 --- a/src/Snap/Core/SnapFilesystem.cs +++ b/src/Snap/Core/SnapFilesystem.cs @@ -12,600 +12,599 @@ using Snap.Extensions; using Snap.Logging; -namespace Snap.Core -{ - internal interface ISnapFilesystem - { - char FixedNewlineChar { get; } - string DirectorySeparator { get; } - char DirectorySeparatorChar { get; } - void DirectoryCreate(string directory); - bool DirectoryCreateIfNotExists(string directory); - bool DirectoryExists(string directory); - void DirectoryDelete(string directory, bool recursive = false); - Task DirectoryDeleteAsync(string directory, List excludePaths = null); - string DirectoryWorkingDirectory(); - string DirectoryGetParent(string path); - void DirectoryExistsThrowIfNotExists(string directory); - void SetCurrentDirectory(string path); - DisposableDirectory WithDisposableTempDirectory(string workingDirectory); - DisposableDirectory WithDisposableTempDirectory(); - IEnumerable EnumerateDirectories(string path); - IEnumerable EnumerateFiles(string path); - IEnumerable DirectoryGetAllFilesRecursively(string rootPath); - IEnumerable DirectoryGetAllFiles(string rootPath); - void FileWrite(Stream srcStream, string destFilename, bool overwrite = true); - Task FileCopyAsync(string sourcePath, string destinationPath, CancellationToken cancellationToken, bool overwrite = true); - Task FileWriteAsync(Stream srcStream, string dstFilename, CancellationToken cancellationToken, bool overwrite = true); - Task FileWriteAsync(byte[] bytes, string dstFilename, CancellationToken cancellationToken, bool overwrite = true); - Task FileWriteUtf8StringAsync([NotNull] string utf8Text, [NotNull] string dstFilename, CancellationToken cancellationToken, bool overwrite = true); - Task FileReadAsync(string filename, CancellationToken cancellationToken); - Task FileReadAllTextAsync(string fileName); - string FileReadAllText(string filename); - byte[] FileReadAllBytes(string filename); - Task FileReadAllBytesAsync([NotNull] string filename, CancellationToken cancellationToken); - void FileDelete(string fileName); - bool FileDeleteIfExists(string fileName, bool throwIfException = true); - bool FileDeleteWithRetries(string path, bool ignoreIfFails = false); - FileStream FileRead(string fileName, int bufferSize = 8196, bool useAsync = true); - FileStream FileReadWrite(string fileName, bool overwrite = true); - FileStream FileWrite(string fileName, bool overwrite = true); - Task FileReadAssemblyDefinitionAsync(string filename, CancellationToken cancellationToken); - bool FileExists(string fileName); - void FileExistsThrowIfNotExists(string fileName); - FileInfo FileStat(string fileName); - string PathGetFileNameWithoutExtension(string filename); - string PathCombine(string path1, string path2); - string PathCombine(string path1, string path2, string path3); - string PathCombine(string path1, string path2, string path3, string path4); - string PathGetDirectoryName(string path); - string PathGetFullPath(string path); - string PathGetExtension(string path); - string PathNormalize([NotNull] string path); - string PathEnsureThisOsDirectoryPathSeperator([NotNull] string path); - string PathGetFileName(string filename); - string PathChangeExtension(string path, string extension); - string PathGetTempPath(); - void FileMove(string sourceFilename, string destinationFilename); - bool TryFileMove(string srcFilenameAbsolutePath, string dstFilenameAbsolutePath, Action beforeMoveAction = null, int retries = 3); - } - - internal sealed class SnapFilesystem : ISnapFilesystem - { - static readonly ILog Logger = LogProvider.For(); - - public char FixedNewlineChar => '\n'; - public string DirectorySeparator { get; } - public char DirectorySeparatorChar => Path.DirectorySeparatorChar; - - public SnapFilesystem() - { - DirectorySeparator = char.ToString(DirectorySeparatorChar); - } - - public string PathNormalize(string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); +namespace Snap.Core; - if (!path.EndsWith(DirectorySeparatorChar.ToString())) - { - path += DirectorySeparatorChar.ToString(); - } - - return PathGetFullPath(path); - } +internal interface ISnapFilesystem +{ + char FixedNewlineChar { get; } + string DirectorySeparator { get; } + char DirectorySeparatorChar { get; } + void DirectoryCreate(string directory); + bool DirectoryCreateIfNotExists(string directory); + bool DirectoryExists(string directory); + void DirectoryDelete(string directory, bool recursive = false); + Task DirectoryDeleteAsync(string directory, List excludePaths = null); + string DirectoryWorkingDirectory(); + string DirectoryGetParent(string path); + void DirectoryExistsThrowIfNotExists(string directory); + void SetCurrentDirectory(string path); + DisposableDirectory WithDisposableTempDirectory(string workingDirectory); + DisposableDirectory WithDisposableTempDirectory(); + IEnumerable EnumerateDirectories(string path); + IEnumerable EnumerateFiles(string path); + IEnumerable DirectoryGetAllFilesRecursively(string rootPath); + IEnumerable DirectoryGetAllFiles(string rootPath); + void FileWrite(Stream srcStream, string destFilename, bool overwrite = true); + Task FileCopyAsync(string sourcePath, string destinationPath, CancellationToken cancellationToken, bool overwrite = true); + Task FileWriteAsync(Stream srcStream, string dstFilename, CancellationToken cancellationToken, bool overwrite = true); + Task FileWriteAsync(byte[] bytes, string dstFilename, CancellationToken cancellationToken, bool overwrite = true); + Task FileWriteUtf8StringAsync([NotNull] string utf8Text, [NotNull] string dstFilename, CancellationToken cancellationToken, bool overwrite = true); + Task FileReadAsync(string filename, CancellationToken cancellationToken); + Task FileReadAllTextAsync(string fileName); + string FileReadAllText(string filename); + byte[] FileReadAllBytes(string filename); + Task FileReadAllBytesAsync([NotNull] string filename, CancellationToken cancellationToken); + void FileDelete(string fileName); + bool FileDeleteIfExists(string fileName, bool throwIfException = true); + bool FileDeleteWithRetries(string path, bool ignoreIfFails = false); + FileStream FileRead(string fileName, int bufferSize = 8196, bool useAsync = true); + FileStream FileReadWrite(string fileName, bool overwrite = true); + FileStream FileWrite(string fileName, bool overwrite = true); + Task FileReadAssemblyDefinitionAsync(string filename, CancellationToken cancellationToken); + bool FileExists(string fileName); + void FileExistsThrowIfNotExists(string fileName); + FileInfo FileStat(string fileName); + string PathGetFileNameWithoutExtension(string filename); + string PathCombine(string path1, string path2); + string PathCombine(string path1, string path2, string path3); + string PathCombine(string path1, string path2, string path3, string path4); + string PathGetDirectoryName(string path); + string PathGetFullPath(string path); + string PathGetExtension(string path); + string PathNormalize([NotNull] string path); + string PathEnsureThisOsDirectoryPathSeperator([NotNull] string path); + string PathGetFileName(string filename); + string PathChangeExtension(string path, string extension); + string PathGetTempPath(); + void FileMove(string sourceFilename, string destinationFilename); + bool TryFileMove(string srcFilenameAbsolutePath, string dstFilenameAbsolutePath, Action beforeMoveAction = null, int retries = 3); +} - public string PathEnsureThisOsDirectoryPathSeperator(string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); +internal sealed class SnapFilesystem : ISnapFilesystem +{ + static readonly ILog Logger = LogProvider.For(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return path.TrailingSlashesSafe(); - } + public char FixedNewlineChar => '\n'; + public string DirectorySeparator { get; } + public char DirectorySeparatorChar => Path.DirectorySeparatorChar; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - return path.ForwardSlashesSafe(); - } + public SnapFilesystem() + { + DirectorySeparator = char.ToString(DirectorySeparatorChar); + } - throw new PlatformNotSupportedException(); - } + public string PathNormalize(string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); - - public async Task FileWriteUtf8StringAsync(string utf8Text, string dstFilename, CancellationToken cancellationToken, bool overwrite = true) + if (!path.EndsWith(DirectorySeparatorChar.ToString())) { - if (utf8Text == null) throw new ArgumentNullException(nameof(utf8Text)); - if (dstFilename == null) throw new ArgumentNullException(nameof(dstFilename)); + path += DirectorySeparatorChar.ToString(); + } - var outputBytes = Encoding.UTF8.GetBytes(utf8Text); + return PathGetFullPath(path); + } - await using var outputStream = FileWrite(dstFilename, overwrite); - await outputStream.WriteAsync(outputBytes.AsMemory(0, outputBytes.Length), cancellationToken); - } + public string PathEnsureThisOsDirectoryPathSeperator(string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); - public FileStream FileRead([NotNull] string fileName, int bufferSize = 8196, bool useAsync = true) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - return new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, useAsync); + return path.TrailingSlashesSafe(); } - public FileStream FileReadWrite([NotNull] string fileName, bool overwrite = true) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - var fileStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); - if (overwrite) - { - fileStream.SetLength(0); - } - return fileStream; + return path.ForwardSlashesSafe(); } - public FileStream FileWrite([NotNull] string fileName, bool overwrite = true) - { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - var fileStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); - if (overwrite) - { - fileStream.SetLength(0); - } - return fileStream; - } + throw new PlatformNotSupportedException(); + } - public async Task FileReadAssemblyDefinitionAsync([NotNull] string filename, CancellationToken cancellationToken) - { - if (filename == null) throw new ArgumentNullException(nameof(filename)); + + public async Task FileWriteUtf8StringAsync(string utf8Text, string dstFilename, CancellationToken cancellationToken, bool overwrite = true) + { + if (utf8Text == null) throw new ArgumentNullException(nameof(utf8Text)); + if (dstFilename == null) throw new ArgumentNullException(nameof(dstFilename)); - if (!FileExists(filename)) - { - throw new FileNotFoundException(filename); - } + var outputBytes = Encoding.UTF8.GetBytes(utf8Text); - var srcStream = await FileReadAsync(filename, cancellationToken); - return AssemblyDefinition.ReadAssembly(srcStream); - } + await using var outputStream = FileWrite(dstFilename, overwrite); + await outputStream.WriteAsync(outputBytes.AsMemory(0, outputBytes.Length), cancellationToken); + } - public bool FileExists([NotNull] string fileName) - { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - return File.Exists(fileName); - } + public FileStream FileRead([NotNull] string fileName, int bufferSize = 8196, bool useAsync = true) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + return new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize, useAsync); + } - public void FileExistsThrowIfNotExists([NotNull] string fileName) + public FileStream FileReadWrite([NotNull] string fileName, bool overwrite = true) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + var fileStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + if (overwrite) { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - if (!FileExists(fileName)) - { - throw new FileNotFoundException(fileName); - } + fileStream.SetLength(0); } + return fileStream; + } - public FileInfo FileStat([NotNull] string fileName) + public FileStream FileWrite([NotNull] string fileName, bool overwrite = true) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + var fileStream = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); + if (overwrite) { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - return new FileInfo(fileName); + fileStream.SetLength(0); } + return fileStream; + } - public async Task FileReadAllTextAsync([NotNull] string fileName) - { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - await using var stream = FileRead(fileName); - using var streamReader = new StreamReader(stream); - return await streamReader.ReadToEndAsync(); - } + public async Task FileReadAssemblyDefinitionAsync([NotNull] string filename, CancellationToken cancellationToken) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); - public string FileReadAllText([NotNull] string filename) + if (!FileExists(filename)) { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - using var stream = FileRead(filename); - using var streamReader = new StreamReader(stream); - return streamReader.ReadToEnd(); + throw new FileNotFoundException(filename); } - public byte[] FileReadAllBytes([NotNull] string filename) - { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - return File.ReadAllBytes(filename); - } + var srcStream = await FileReadAsync(filename, cancellationToken); + return AssemblyDefinition.ReadAssembly(srcStream); + } - public async Task FileReadAllBytesAsync(string filename, CancellationToken cancellationToken) - { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - await using var srcStream = FileRead(filename); - await using var destinationStream = new MemoryStream((int) srcStream.Length); - await srcStream.CopyToAsync(destinationStream, cancellationToken); - destinationStream.Seek(0, SeekOrigin.Begin); - return destinationStream.ToArray(); - } + public bool FileExists([NotNull] string fileName) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + return File.Exists(fileName); + } - public void FileDelete([NotNull] string fileName) + public void FileExistsThrowIfNotExists([NotNull] string fileName) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + if (!FileExists(fileName)) { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - File.Delete(fileName); + throw new FileNotFoundException(fileName); } + } - public bool FileDeleteIfExists([NotNull] string fileName, bool throwIfException = true) - { - if (fileName == null) throw new ArgumentNullException(nameof(fileName)); - if (!FileExists(fileName)) - { - return false; - } + public FileInfo FileStat([NotNull] string fileName) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + return new FileInfo(fileName); + } - try - { - FileDelete(fileName); - return true; - } - catch (Exception) - { - if (throwIfException) throw; - } - - return false; - } + public async Task FileReadAllTextAsync([NotNull] string fileName) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + await using var stream = FileRead(fileName); + using var streamReader = new StreamReader(stream); + return await streamReader.ReadToEndAsync(); + } - public bool FileDeleteWithRetries([NotNull] string path, bool ignoreIfFails = false) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - try - { - var success = false; - SnapUtility.Retry(() => - { - FileDelete(path); - success = true; - }); - return success; - } - catch (Exception ex) - { - if (ignoreIfFails) return false; + public string FileReadAllText([NotNull] string filename) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + using var stream = FileRead(filename); + using var streamReader = new StreamReader(stream); + return streamReader.ReadToEnd(); + } - Logger.ErrorException("Really couldn't delete file: " + path, ex); - throw; - } - } + public byte[] FileReadAllBytes([NotNull] string filename) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + return File.ReadAllBytes(filename); + } + + public async Task FileReadAllBytesAsync(string filename, CancellationToken cancellationToken) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + await using var srcStream = FileRead(filename); + await using var destinationStream = new MemoryStream((int) srcStream.Length); + await srcStream.CopyToAsync(destinationStream, cancellationToken); + destinationStream.Seek(0, SeekOrigin.Begin); + return destinationStream.ToArray(); + } - public void DirectoryCreate(string directory) + public void FileDelete([NotNull] string fileName) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + File.Delete(fileName); + } + + public bool FileDeleteIfExists([NotNull] string fileName, bool throwIfException = true) + { + if (fileName == null) throw new ArgumentNullException(nameof(fileName)); + if (!FileExists(fileName)) { - if (directory == null) throw new ArgumentNullException(nameof(directory)); - Directory.CreateDirectory(directory); + return false; } - public bool DirectoryCreateIfNotExists([NotNull] string directory) + try { - if (directory == null) throw new ArgumentNullException(nameof(directory)); - if (DirectoryExists(directory)) return false; - DirectoryCreate(directory); + FileDelete(fileName); return true; } - - public bool DirectoryExists(string directory) + catch (Exception) { - if (directory == null) throw new ArgumentNullException(nameof(directory)); - return Directory.Exists(directory); + if (throwIfException) throw; } + + return false; + } - public void DirectoryExistsThrowIfNotExists(string directory) + public bool FileDeleteWithRetries([NotNull] string path, bool ignoreIfFails = false) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + try { - if (!DirectoryExists(directory)) + var success = false; + SnapUtility.Retry(() => { - throw new DirectoryNotFoundException(directory); - } + FileDelete(path); + success = true; + }); + return success; } - - public void SetCurrentDirectory([NotNull] string path) + catch (Exception ex) { - if (path == null) throw new ArgumentNullException(nameof(path)); - Directory.SetCurrentDirectory(path); - } + if (ignoreIfFails) return false; - public DisposableDirectory WithDisposableTempDirectory([NotNull] string workingDirectory) - { - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - return new DisposableDirectory(workingDirectory, this); + Logger.ErrorException("Really couldn't delete file: " + path, ex); + throw; } + } - public DisposableDirectory WithDisposableTempDirectory() - { - return WithDisposableTempDirectory(Path.GetTempPath()); - } + public void DirectoryCreate(string directory) + { + if (directory == null) throw new ArgumentNullException(nameof(directory)); + Directory.CreateDirectory(directory); + } - public IEnumerable EnumerateDirectories([NotNull] string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - return Directory.EnumerateDirectories(path); - } + public bool DirectoryCreateIfNotExists([NotNull] string directory) + { + if (directory == null) throw new ArgumentNullException(nameof(directory)); + if (DirectoryExists(directory)) return false; + DirectoryCreate(directory); + return true; + } - public IEnumerable EnumerateFiles([NotNull] string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - return new DirectoryInfo(path).EnumerateFiles(); - } + public bool DirectoryExists(string directory) + { + if (directory == null) throw new ArgumentNullException(nameof(directory)); + return Directory.Exists(directory); + } - public IEnumerable DirectoryGetAllFilesRecursively(string rootPath) + public void DirectoryExistsThrowIfNotExists(string directory) + { + if (!DirectoryExists(directory)) { - if (rootPath == null) throw new ArgumentNullException(nameof(rootPath)); - return Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories); + throw new DirectoryNotFoundException(directory); } + } - public IEnumerable DirectoryGetAllFiles(string rootPath) - { - if (rootPath == null) throw new ArgumentNullException(nameof(rootPath)); - return Directory.EnumerateFiles(rootPath, "*", SearchOption.TopDirectoryOnly); - } + public void SetCurrentDirectory([NotNull] string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + Directory.SetCurrentDirectory(path); + } - public void FileWrite([NotNull] Stream srcStream, [NotNull] string destFilename, bool overwrite = true) - { - if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); - if (destFilename == null) throw new ArgumentNullException(nameof(destFilename)); + public DisposableDirectory WithDisposableTempDirectory([NotNull] string workingDirectory) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + return new DisposableDirectory(workingDirectory, this); + } - using var dstStream = FileWrite(destFilename, overwrite); - srcStream.CopyTo(dstStream); - } + public DisposableDirectory WithDisposableTempDirectory() + { + return WithDisposableTempDirectory(Path.GetTempPath()); + } - public async Task FileCopyAsync(string sourcePath, string destinationPath, CancellationToken cancellationToken, bool overwrite = true) - { - if (sourcePath == null) throw new ArgumentNullException(nameof(sourcePath)); - if (destinationPath == null) throw new ArgumentNullException(nameof(destinationPath)); + public IEnumerable EnumerateDirectories([NotNull] string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + return Directory.EnumerateDirectories(path); + } - await using Stream source = FileRead(sourcePath); - await using Stream destination = FileWrite(destinationPath, overwrite); - await source.CopyToAsync(destination, cancellationToken); - } + public IEnumerable EnumerateFiles([NotNull] string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + return new DirectoryInfo(path).EnumerateFiles(); + } - public async Task FileWriteAsync([NotNull] Stream srcStream, [NotNull] string dstFilename, CancellationToken cancellationToken, bool overwrite = true) - { - if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); - if (dstFilename == null) throw new ArgumentNullException(nameof(dstFilename)); - await using var dstStream = FileWrite(dstFilename, overwrite); - await srcStream.CopyToAsync(dstStream, cancellationToken); - } + public IEnumerable DirectoryGetAllFilesRecursively(string rootPath) + { + if (rootPath == null) throw new ArgumentNullException(nameof(rootPath)); + return Directory.EnumerateFiles(rootPath, "*", SearchOption.AllDirectories); + } + + public IEnumerable DirectoryGetAllFiles(string rootPath) + { + if (rootPath == null) throw new ArgumentNullException(nameof(rootPath)); + return Directory.EnumerateFiles(rootPath, "*", SearchOption.TopDirectoryOnly); + } + + public void FileWrite([NotNull] Stream srcStream, [NotNull] string destFilename, bool overwrite = true) + { + if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); + if (destFilename == null) throw new ArgumentNullException(nameof(destFilename)); + + using var dstStream = FileWrite(destFilename, overwrite); + srcStream.CopyTo(dstStream); + } + + public async Task FileCopyAsync(string sourcePath, string destinationPath, CancellationToken cancellationToken, bool overwrite = true) + { + if (sourcePath == null) throw new ArgumentNullException(nameof(sourcePath)); + if (destinationPath == null) throw new ArgumentNullException(nameof(destinationPath)); + + await using Stream source = FileRead(sourcePath); + await using Stream destination = FileWrite(destinationPath, overwrite); + await source.CopyToAsync(destination, cancellationToken); + } + + public async Task FileWriteAsync([NotNull] Stream srcStream, [NotNull] string dstFilename, CancellationToken cancellationToken, bool overwrite = true) + { + if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); + if (dstFilename == null) throw new ArgumentNullException(nameof(dstFilename)); + await using var dstStream = FileWrite(dstFilename, overwrite); + await srcStream.CopyToAsync(dstStream, cancellationToken); + } - public async Task FileWriteAsync([NotNull] byte[] bytes, [NotNull] string dstFilename, CancellationToken cancellationToken, bool overwrite = true) + public async Task FileWriteAsync([NotNull] byte[] bytes, [NotNull] string dstFilename, CancellationToken cancellationToken, bool overwrite = true) + { + if (bytes == null) throw new ArgumentNullException(nameof(bytes)); + if (dstFilename == null) throw new ArgumentNullException(nameof(dstFilename)); + await using var dstStream = FileWrite(dstFilename, overwrite); + await dstStream.WriteAsync(bytes.AsMemory(0, bytes.Length), cancellationToken); + } + + public async Task FileReadAsync(string filename, CancellationToken cancellationToken) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + + if (!FileExists(filename)) { - if (bytes == null) throw new ArgumentNullException(nameof(bytes)); - if (dstFilename == null) throw new ArgumentNullException(nameof(dstFilename)); - await using var dstStream = FileWrite(dstFilename, overwrite); - await dstStream.WriteAsync(bytes.AsMemory(0, bytes.Length), cancellationToken); + throw new FileNotFoundException(filename); } - public async Task FileReadAsync(string filename, CancellationToken cancellationToken) + var dstStream = new MemoryStream(); + + await using (var srcStream = FileRead(filename)) { - if (filename == null) throw new ArgumentNullException(nameof(filename)); + await srcStream.CopyToAsync(dstStream, cancellationToken); + } - if (!FileExists(filename)) - { - throw new FileNotFoundException(filename); - } + dstStream.Seek(0, SeekOrigin.Begin); - var dstStream = new MemoryStream(); + return dstStream; + } - await using (var srcStream = FileRead(filename)) - { - await srcStream.CopyToAsync(dstStream, cancellationToken); - } + public void DirectoryDelete(string directory, bool recursive = false) + { + if (directory == null) throw new ArgumentNullException(nameof(directory)); + Directory.Delete(directory, recursive); + } - dstStream.Seek(0, SeekOrigin.Begin); + public async Task DirectoryDeleteAsync([NotNull] string directory, List excludePaths = null) + { + if (directory == null) throw new ArgumentNullException(nameof(directory)); + + Logger.Debug($"Starting to delete folder: {directory}"); - return dstStream; + if (!DirectoryExists(directory)) + { + Logger.Error($"Directory does not exist: {directory}"); + return; } - public void DirectoryDelete(string directory, bool recursive = false) + excludePaths = excludePaths?.Where(x => x != null).Select(x => PathCombine(directory, x)).ToList() ?? new List(); + + // From http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true/329502#329502 + var files = Array.Empty(); + try { - if (directory == null) throw new ArgumentNullException(nameof(directory)); - Directory.Delete(directory, recursive); + files = Directory.GetFiles(directory); } - - public async Task DirectoryDeleteAsync([NotNull] string directory, List excludePaths = null) + catch (Exception ex) { - if (directory == null) throw new ArgumentNullException(nameof(directory)); - - Logger.Debug($"Starting to delete folder: {directory}"); - - if (!DirectoryExists(directory)) - { - Logger.Error($"Directory does not exist: {directory}"); - return; - } + Logger.Warn($"The files inside {directory} could not be read", ex); + } - excludePaths = excludePaths?.Where(x => x != null).Select(x => PathCombine(directory, x)).ToList() ?? new List(); - - // From http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true/329502#329502 - var files = Array.Empty(); - try - { - files = Directory.GetFiles(directory); - } - catch (Exception ex) - { - Logger.Warn($"The files inside {directory} could not be read", ex); - } + var subdirectories = Array.Empty(); + try + { + subdirectories = Directory.GetDirectories(directory); + } + catch (Exception ex) + { + Logger.ErrorException($"The directories inside {directory} could not be read", ex); + } - var subdirectories = Array.Empty(); - try - { - subdirectories = Directory.GetDirectories(directory); - } - catch (Exception ex) + var fileDeleteTasks = files.ForEachAsync(file => + { + foreach (var excludePath in excludePaths) { - Logger.ErrorException($"The directories inside {directory} could not be read", ex); + if (string.Equals(excludePath, file, StringComparison.OrdinalIgnoreCase)) + { + return Task.CompletedTask; + } } - var fileDeleteTasks = files.ForEachAsync(file => + return Task.Run(() => { - foreach (var excludePath in excludePaths) + try { - if (string.Equals(excludePath, file, StringComparison.OrdinalIgnoreCase)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - return Task.CompletedTask; + File.SetAttributes(directory, FileAttributes.Normal); } - } - return Task.Run(() => + File.Delete(file); + } + catch (Exception e) { - try - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - File.SetAttributes(directory, FileAttributes.Normal); - } - - File.Delete(file); - } - catch (Exception e) - { - Logger.ErrorException($"Unable to delete file: {file}", e); - } - }); + Logger.ErrorException($"Unable to delete file: {file}", e); + } }); + }); - // We have to delete all files before we delete the directory. - await Task.WhenAll(fileDeleteTasks); + // We have to delete all files before we delete the directory. + await Task.WhenAll(fileDeleteTasks); - var subdirectoriesDeleteTasks = - subdirectories.ForEachAsync(dir => + var subdirectoriesDeleteTasks = + subdirectories.ForEachAsync(dir => + { + foreach (var excludePath in excludePaths) { - foreach (var excludePath in excludePaths) + if (string.Equals(excludePath, dir, StringComparison.OrdinalIgnoreCase)) { - if (string.Equals(excludePath, dir, StringComparison.OrdinalIgnoreCase)) - { - return Task.CompletedTask; - } + return Task.CompletedTask; } - return DirectoryDeleteAsync(dir); - }); + } + return DirectoryDeleteAsync(dir); + }); - await Task.WhenAll(subdirectoriesDeleteTasks); + await Task.WhenAll(subdirectoriesDeleteTasks); - Logger.Debug($"Deleting directory: {directory}"); - try + Logger.Debug($"Deleting directory: {directory}"); + try + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - File.SetAttributes(directory, FileAttributes.Normal); - } + File.SetAttributes(directory, FileAttributes.Normal); + } - Directory.Delete(directory, false); + Directory.Delete(directory, false); - Logger.Debug($"Successfully deleted directory: {directory}"); - } - catch (Exception e) - { - Logger.ErrorException($"Unable to delete directory: {directory}", e); - } - } - - public string DirectoryWorkingDirectory() - { - return Directory.GetCurrentDirectory(); - } - - public string PathGetFileName([NotNull] string filename) - { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - return Path.GetFileName(filename); + Logger.Debug($"Successfully deleted directory: {directory}"); } - - public string PathChangeExtension([NotNull] string path, [NotNull] string extension) + catch (Exception e) { - if (path == null) throw new ArgumentNullException(nameof(path)); - if (extension == null) throw new ArgumentNullException(nameof(extension)); - return Path.ChangeExtension(path, extension); - } + Logger.ErrorException($"Unable to delete directory: {directory}", e); + } + } - public string PathGetTempPath() - { - return Path.GetTempPath(); - } + public string DirectoryWorkingDirectory() + { + return Directory.GetCurrentDirectory(); + } - public void FileMove([NotNull] string sourceFilename, [NotNull] string destinationFilename) - { - if (sourceFilename == null) throw new ArgumentNullException(nameof(sourceFilename)); - if (destinationFilename == null) throw new ArgumentNullException(nameof(destinationFilename)); - File.Move(sourceFilename, destinationFilename); - } + public string PathGetFileName([NotNull] string filename) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + return Path.GetFileName(filename); + } - public bool TryFileMove(string srcFilenameAbsolutePath, string dstFilenameAbsolutePath, Action beforeMoveAction = null, int retries = 3) - { - if (srcFilenameAbsolutePath == null) throw new ArgumentNullException(nameof(srcFilenameAbsolutePath)); - if (dstFilenameAbsolutePath == null) throw new ArgumentNullException(nameof(dstFilenameAbsolutePath)); - if (retries < 0) throw new ArgumentOutOfRangeException(nameof(retries)); + public string PathChangeExtension([NotNull] string path, [NotNull] string extension) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + if (extension == null) throw new ArgumentNullException(nameof(extension)); + return Path.ChangeExtension(path, extension); + } - if (!FileExists(srcFilenameAbsolutePath)) - { - return false; - } + public string PathGetTempPath() + { + return Path.GetTempPath(); + } - var success = false; - SnapUtility.Retry(() => - { - beforeMoveAction?.Invoke(); - FileDeleteIfExists(dstFilenameAbsolutePath, false); - FileMove(srcFilenameAbsolutePath, dstFilenameAbsolutePath); - success = true; - }, retries, 500, false); + public void FileMove([NotNull] string sourceFilename, [NotNull] string destinationFilename) + { + if (sourceFilename == null) throw new ArgumentNullException(nameof(sourceFilename)); + if (destinationFilename == null) throw new ArgumentNullException(nameof(destinationFilename)); + File.Move(sourceFilename, destinationFilename); + } - return success; - } + public bool TryFileMove(string srcFilenameAbsolutePath, string dstFilenameAbsolutePath, Action beforeMoveAction = null, int retries = 3) + { + if (srcFilenameAbsolutePath == null) throw new ArgumentNullException(nameof(srcFilenameAbsolutePath)); + if (dstFilenameAbsolutePath == null) throw new ArgumentNullException(nameof(dstFilenameAbsolutePath)); + if (retries < 0) throw new ArgumentOutOfRangeException(nameof(retries)); - public string PathGetFileNameWithoutExtension([NotNull] string filename) + if (!FileExists(srcFilenameAbsolutePath)) { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - return Path.GetFileNameWithoutExtension(filename); + return false; } - public string PathCombine([NotNull] string path1, [NotNull] string path2) + var success = false; + SnapUtility.Retry(() => { - if (path1 == null) throw new ArgumentNullException(nameof(path1)); - if (path2 == null) throw new ArgumentNullException(nameof(path2)); - return PathEnsureThisOsDirectoryPathSeperator(Path.Combine(path1, path2)); - } + beforeMoveAction?.Invoke(); + FileDeleteIfExists(dstFilenameAbsolutePath, false); + FileMove(srcFilenameAbsolutePath, dstFilenameAbsolutePath); + success = true; + }, retries, 500, false); - public string PathCombine([NotNull] string path1, [NotNull] string path2, [NotNull] string path3) - { - if (path1 == null) throw new ArgumentNullException(nameof(path1)); - if (path2 == null) throw new ArgumentNullException(nameof(path2)); - if (path3 == null) throw new ArgumentNullException(nameof(path3)); - return PathEnsureThisOsDirectoryPathSeperator(Path.Combine(path1, path2, path3)); - } + return success; + } - public string PathCombine([NotNull] string path1, [NotNull] string path2, [NotNull] string path3, [NotNull] string path4) - { - if (path1 == null) throw new ArgumentNullException(nameof(path1)); - if (path2 == null) throw new ArgumentNullException(nameof(path2)); - if (path3 == null) throw new ArgumentNullException(nameof(path3)); - if (path4 == null) throw new ArgumentNullException(nameof(path4)); - return PathEnsureThisOsDirectoryPathSeperator(Path.Combine(path1, path2, path3, path4)); - } + public string PathGetFileNameWithoutExtension([NotNull] string filename) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + return Path.GetFileNameWithoutExtension(filename); + } - public string PathGetDirectoryName([NotNull] string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - return Path.GetDirectoryName(path); - } + public string PathCombine([NotNull] string path1, [NotNull] string path2) + { + if (path1 == null) throw new ArgumentNullException(nameof(path1)); + if (path2 == null) throw new ArgumentNullException(nameof(path2)); + return PathEnsureThisOsDirectoryPathSeperator(Path.Combine(path1, path2)); + } - public string PathGetFullPath([NotNull] string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - return Path.GetFullPath(path); - } + public string PathCombine([NotNull] string path1, [NotNull] string path2, [NotNull] string path3) + { + if (path1 == null) throw new ArgumentNullException(nameof(path1)); + if (path2 == null) throw new ArgumentNullException(nameof(path2)); + if (path3 == null) throw new ArgumentNullException(nameof(path3)); + return PathEnsureThisOsDirectoryPathSeperator(Path.Combine(path1, path2, path3)); + } - public string PathGetExtension([NotNull] string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); - return Path.GetExtension(path); - } + public string PathCombine([NotNull] string path1, [NotNull] string path2, [NotNull] string path3, [NotNull] string path4) + { + if (path1 == null) throw new ArgumentNullException(nameof(path1)); + if (path2 == null) throw new ArgumentNullException(nameof(path2)); + if (path3 == null) throw new ArgumentNullException(nameof(path3)); + if (path4 == null) throw new ArgumentNullException(nameof(path4)); + return PathEnsureThisOsDirectoryPathSeperator(Path.Combine(path1, path2, path3, path4)); + } - public string DirectoryGetParent([NotNull] string path) - { - if (path == null) throw new ArgumentNullException(nameof(path)); + public string PathGetDirectoryName([NotNull] string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + return Path.GetDirectoryName(path); + } - var parentDirectory = Directory.GetParent(path)?.FullName; + public string PathGetFullPath([NotNull] string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + return Path.GetFullPath(path); + } - return parentDirectory; - } + public string PathGetExtension([NotNull] string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + return Path.GetExtension(path); } -} + + public string DirectoryGetParent([NotNull] string path) + { + if (path == null) throw new ArgumentNullException(nameof(path)); + + var parentDirectory = Directory.GetParent(path)?.FullName; + + return parentDirectory; + } +} \ No newline at end of file diff --git a/src/Snap/Core/SnapHttpClient.cs b/src/Snap/Core/SnapHttpClient.cs index 1b0fc81f..c302d5b6 100644 --- a/src/Snap/Core/SnapHttpClient.cs +++ b/src/Snap/Core/SnapHttpClient.cs @@ -4,33 +4,32 @@ using System.Net.Http; using System.Threading.Tasks; -namespace Snap.Core +namespace Snap.Core; + +public interface ISnapHttpClient { - public interface ISnapHttpClient + Task GetStreamAsync(Uri requestUri, IDictionary headers = null); +} + +public sealed class SnapHttpClient : ISnapHttpClient +{ + readonly HttpClient _httpClient; + + public SnapHttpClient(HttpClient httpClient) { - Task GetStreamAsync(Uri requestUri, IDictionary headers = null); + _httpClient = httpClient; } - public sealed class SnapHttpClient : ISnapHttpClient + public async Task GetStreamAsync(Uri requestUri, IDictionary headers = null) { - readonly HttpClient _httpClient; - - public SnapHttpClient(HttpClient httpClient) + var httpResponseMessage = await _httpClient.GetAsync(requestUri); + if (headers != null) { - _httpClient = httpClient; - } - - public async Task GetStreamAsync(Uri requestUri, IDictionary headers = null) - { - var httpResponseMessage = await _httpClient.GetAsync(requestUri); - if (headers != null) + foreach (var pair in headers) { - foreach (var pair in headers) - { - httpResponseMessage.Headers.Add(pair.Key, pair.Value); - } + httpResponseMessage.Headers.Add(pair.Key, pair.Value); } - return await httpResponseMessage.Content.ReadAsStreamAsync(); } + return await httpResponseMessage.Content.ReadAsStreamAsync(); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/SnapInstaller.cs b/src/Snap/Core/SnapInstaller.cs index a7ca5641..478e4e9a 100644 --- a/src/Snap/Core/SnapInstaller.cs +++ b/src/Snap/Core/SnapInstaller.cs @@ -14,408 +14,407 @@ using Snap.Extensions; using Snap.Logging; -namespace Snap.Core +namespace Snap.Core; + +[Flags] +public enum SnapShortcutLocation { - [Flags] - public enum SnapShortcutLocation - { - StartMenu = 1, - Desktop = 1 << 1, - Startup = 1 << 2 - } + StartMenu = 1, + Desktop = 1 << 1, + Startup = 1 << 2 +} - [Flags] - public enum SnapInstallerType - { - None, - Web = 1, - Offline = 1 << 1 - } +[Flags] +public enum SnapInstallerType +{ + None, + Web = 1, + Offline = 1 << 1 +} + +internal interface ISnapInstaller +{ + Task InstallAsync(string nupkgAbsoluteFilename, [NotNull] string baseDirectory, + [NotNull] SnapRelease snapRelease, [NotNull] SnapChannel snapChannel, + ISnapProgressSource snapProgressSource = null, ILog logger = null, bool copyNupkgToPackagesDirectory = true, + CancellationToken cancellationToken = default); + Task UpdateAsync([NotNull] string baseDirectory, [NotNull] SnapRelease snapRelease, [NotNull] SnapChannel snapChannel, + ISnapProgressSource snapProgressSource = null, ILog logger = null, CancellationToken cancellationToken = default); + string GetApplicationDirectory(string baseDirectory, SemanticVersion version); + string GetApplicationDirectory(string baseDirectory, SnapRelease release); +} - internal interface ISnapInstaller +internal sealed class SnapInstaller : ISnapInstaller +{ + readonly ISnapExtractor _snapExtractor; + readonly ISnapPack _snapPack; + readonly ISnapOs _snapOs; + readonly ISnapEmbeddedResources _snapEmbeddedResources; + readonly ISnapAppWriter _snapAppWriter; + + public SnapInstaller(ISnapExtractor snapExtractor, [NotNull] ISnapPack snapPack, + [NotNull] ISnapOs snapOs, [NotNull] ISnapEmbeddedResources snapEmbeddedResources, [NotNull] ISnapAppWriter snapAppWriter) { - Task InstallAsync(string nupkgAbsoluteFilename, [NotNull] string baseDirectory, - [NotNull] SnapRelease snapRelease, [NotNull] SnapChannel snapChannel, - ISnapProgressSource snapProgressSource = null, ILog logger = null, bool copyNupkgToPackagesDirectory = true, - CancellationToken cancellationToken = default); - Task UpdateAsync([NotNull] string baseDirectory, [NotNull] SnapRelease snapRelease, [NotNull] SnapChannel snapChannel, - ISnapProgressSource snapProgressSource = null, ILog logger = null, CancellationToken cancellationToken = default); - string GetApplicationDirectory(string baseDirectory, SemanticVersion version); - string GetApplicationDirectory(string baseDirectory, SnapRelease release); + _snapExtractor = snapExtractor ?? throw new ArgumentNullException(nameof(snapExtractor)); + _snapPack = snapPack ?? throw new ArgumentNullException(nameof(snapPack)); + _snapOs = snapOs ?? throw new ArgumentNullException(nameof(snapOs)); + _snapEmbeddedResources = snapEmbeddedResources ?? throw new ArgumentNullException(nameof(snapEmbeddedResources)); + _snapAppWriter = snapAppWriter ?? throw new ArgumentNullException(nameof(snapAppWriter)); } - internal sealed class SnapInstaller : ISnapInstaller + public async Task UpdateAsync(string baseDirectory, SnapRelease snapRelease, SnapChannel snapChannel, + ISnapProgressSource snapProgressSource = null, ILog logger = null, CancellationToken cancellationToken = default) { - readonly ISnapExtractor _snapExtractor; - readonly ISnapPack _snapPack; - readonly ISnapOs _snapOs; - readonly ISnapEmbeddedResources _snapEmbeddedResources; - readonly ISnapAppWriter _snapAppWriter; - - public SnapInstaller(ISnapExtractor snapExtractor, [NotNull] ISnapPack snapPack, - [NotNull] ISnapOs snapOs, [NotNull] ISnapEmbeddedResources snapEmbeddedResources, [NotNull] ISnapAppWriter snapAppWriter) + if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); + + if (!_snapOs.Filesystem.DirectoryExists(baseDirectory)) { - _snapExtractor = snapExtractor ?? throw new ArgumentNullException(nameof(snapExtractor)); - _snapPack = snapPack ?? throw new ArgumentNullException(nameof(snapPack)); - _snapOs = snapOs ?? throw new ArgumentNullException(nameof(snapOs)); - _snapEmbeddedResources = snapEmbeddedResources ?? throw new ArgumentNullException(nameof(snapEmbeddedResources)); - _snapAppWriter = snapAppWriter ?? throw new ArgumentNullException(nameof(snapAppWriter)); + logger?.Error($"Base directory does not exist: {baseDirectory}"); + return null; } - public async Task UpdateAsync(string baseDirectory, SnapRelease snapRelease, SnapChannel snapChannel, - ISnapProgressSource snapProgressSource = null, ILog logger = null, CancellationToken cancellationToken = default) + var nupkgAbsoluteFilename = _snapOs.Filesystem.PathCombine(baseDirectory, "packages", snapRelease.Filename); + if (!_snapOs.Filesystem.FileExists(nupkgAbsoluteFilename)) { - if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); - - if (!_snapOs.Filesystem.DirectoryExists(baseDirectory)) - { - logger?.Error($"Base directory does not exist: {baseDirectory}"); - return null; - } - - var nupkgAbsoluteFilename = _snapOs.Filesystem.PathCombine(baseDirectory, "packages", snapRelease.Filename); - if (!_snapOs.Filesystem.FileExists(nupkgAbsoluteFilename)) - { - logger?.Error($"Unable to apply full update because the nupkg does not exist: {nupkgAbsoluteFilename}"); - return null; - } + logger?.Error($"Unable to apply full update because the nupkg does not exist: {nupkgAbsoluteFilename}"); + return null; + } - snapProgressSource?.Raise(0); - logger?.Debug("Attempting to get snap app details from nupkg"); + snapProgressSource?.Raise(0); + logger?.Debug("Attempting to get snap app details from nupkg"); - var nupkgFileStream = _snapOs.Filesystem.FileRead(nupkgAbsoluteFilename); - using var packageArchiveReader = new PackageArchiveReader(nupkgFileStream); - var snapApp = await _snapPack.GetSnapAppAsync(packageArchiveReader, cancellationToken); - if (!snapApp.IsFull) - { - logger?.Error($"You can only update from a full nupkg. Snap id: {snapApp.Id}. Filename: {nupkgAbsoluteFilename}"); - return null; - } + var nupkgFileStream = _snapOs.Filesystem.FileRead(nupkgAbsoluteFilename); + using var packageArchiveReader = new PackageArchiveReader(nupkgFileStream); + var snapApp = await _snapPack.GetSnapAppAsync(packageArchiveReader, cancellationToken); + if (!snapApp.IsFull) + { + logger?.Error($"You can only update from a full nupkg. Snap id: {snapApp.Id}. Filename: {nupkgAbsoluteFilename}"); + return null; + } - snapApp.SetCurrentChannel(snapChannel.Name); + snapApp.SetCurrentChannel(snapChannel.Name); - logger?.Info($"Updating snap id: {snapApp.Id}. Version: {snapApp.Version}. "); + logger?.Info($"Updating snap id: {snapApp.Id}. Version: {snapApp.Version}. "); - var appDirectory = GetApplicationDirectory(baseDirectory, snapApp.Version); + var appDirectory = GetApplicationDirectory(baseDirectory, snapApp.Version); - snapProgressSource?.Raise(10); - if (_snapOs.Filesystem.DirectoryExists(appDirectory)) - { - _snapOs.KillAllProcessesInsideDirectory(appDirectory); - logger?.Info($"Deleting existing app directory: {appDirectory}"); - await _snapOs.Filesystem.DirectoryDeleteAsync(appDirectory); - } + snapProgressSource?.Raise(10); + if (_snapOs.Filesystem.DirectoryExists(appDirectory)) + { + _snapOs.KillAllProcessesInsideDirectory(appDirectory); + logger?.Info($"Deleting existing app directory: {appDirectory}"); + await _snapOs.Filesystem.DirectoryDeleteAsync(appDirectory); + } - logger?.Info($"Creating app directory: {appDirectory}"); - _snapOs.Filesystem.DirectoryCreate(appDirectory); + logger?.Info($"Creating app directory: {appDirectory}"); + _snapOs.Filesystem.DirectoryCreate(appDirectory); - var packagesDirectory = GetPackagesDirectory(baseDirectory); - if (!_snapOs.Filesystem.DirectoryExists(packagesDirectory)) - { - logger?.Error($"Packages directory does not exist: {packagesDirectory}"); - return null; - } + var packagesDirectory = GetPackagesDirectory(baseDirectory); + if (!_snapOs.Filesystem.DirectoryExists(packagesDirectory)) + { + logger?.Error($"Packages directory does not exist: {packagesDirectory}"); + return null; + } - snapProgressSource?.Raise(30); + snapProgressSource?.Raise(30); - logger?.Info($"Extracting nupkg to app directory: {appDirectory}"); - var extractedFiles = await _snapExtractor.ExtractAsync(appDirectory, snapRelease, packageArchiveReader, cancellationToken); - if (!extractedFiles.Any()) - { - logger?.Error($"Unknown error when attempting to extract nupkg: {nupkgAbsoluteFilename}"); - return null; - } + logger?.Info($"Extracting nupkg to app directory: {appDirectory}"); + var extractedFiles = await _snapExtractor.ExtractAsync(appDirectory, snapRelease, packageArchiveReader, cancellationToken); + if (!extractedFiles.Any()) + { + logger?.Error($"Unknown error when attempting to extract nupkg: {nupkgAbsoluteFilename}"); + return null; + } - snapProgressSource?.Raise(90); + snapProgressSource?.Raise(90); - logger?.Info("Performing post install tasks"); - var nuspecReader = await packageArchiveReader.GetNuspecReaderAsync(cancellationToken); + logger?.Info("Performing post install tasks"); + var nuspecReader = await packageArchiveReader.GetNuspecReaderAsync(cancellationToken); - await InvokePostInstall(snapApp, nuspecReader, - baseDirectory, appDirectory, snapApp.Version, false, logger, cancellationToken); - logger?.Info("Post install tasks completed"); + await InvokePostInstall(snapApp, nuspecReader, + baseDirectory, appDirectory, snapApp.Version, false, logger, cancellationToken); + logger?.Info("Post install tasks completed"); - snapProgressSource?.Raise(100); + snapProgressSource?.Raise(100); - return snapApp; - } + return snapApp; + } - public async Task InstallAsync(string nupkgAbsoluteFilename, string baseDirectory, - SnapRelease snapRelease, SnapChannel snapChannel, - ISnapProgressSource snapProgressSource = null, ILog logger = null, bool copyNupkgToPackagesDirectory = true, - CancellationToken cancellationToken = default) - { - if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); + public async Task InstallAsync(string nupkgAbsoluteFilename, string baseDirectory, + SnapRelease snapRelease, SnapChannel snapChannel, + ISnapProgressSource snapProgressSource = null, ILog logger = null, bool copyNupkgToPackagesDirectory = true, + CancellationToken cancellationToken = default) + { + if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); + if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); - snapProgressSource?.Raise(0); + snapProgressSource?.Raise(0); - if (!_snapOs.Filesystem.FileExists(nupkgAbsoluteFilename)) - { - logger?.Error($"Unable to find nupkg: {nupkgAbsoluteFilename}"); - return null; - } + if (!_snapOs.Filesystem.FileExists(nupkgAbsoluteFilename)) + { + logger?.Error($"Unable to find nupkg: {nupkgAbsoluteFilename}"); + return null; + } - logger?.Debug("Attempting to get snap app details from nupkg"); - var nupkgFileStream = _snapOs.Filesystem.FileRead(nupkgAbsoluteFilename); - using var packageArchiveReader = new PackageArchiveReader(nupkgFileStream); - var snapApp = await _snapPack.GetSnapAppAsync(packageArchiveReader, cancellationToken); - if (!snapApp.IsFull) - { - logger?.Error($"You can only install full nupkg. Snap id: {snapApp.Id}. Filename: {nupkgAbsoluteFilename}"); - return null; - } + logger?.Debug("Attempting to get snap app details from nupkg"); + var nupkgFileStream = _snapOs.Filesystem.FileRead(nupkgAbsoluteFilename); + using var packageArchiveReader = new PackageArchiveReader(nupkgFileStream); + var snapApp = await _snapPack.GetSnapAppAsync(packageArchiveReader, cancellationToken); + if (!snapApp.IsFull) + { + logger?.Error($"You can only install full nupkg. Snap id: {snapApp.Id}. Filename: {nupkgAbsoluteFilename}"); + return null; + } - snapApp.SetCurrentChannel(snapChannel.Name); + snapApp.SetCurrentChannel(snapChannel.Name); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && snapApp.Target.Os != OSPlatform.Windows) - { - logger?.Error( - $"Unable to install snap because target OS {snapApp.Target.Os} does not match current OS: {OSPlatform.Windows}. Snap id: {snapApp.Id}."); - return null; - } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && snapApp.Target.Os != OSPlatform.Windows) + { + logger?.Error( + $"Unable to install snap because target OS {snapApp.Target.Os} does not match current OS: {OSPlatform.Windows}. Snap id: {snapApp.Id}."); + return null; + } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && snapApp.Target.Os != OSPlatform.Linux) - { - logger?.Error($"Unable to install snap because target OS {snapApp.Target.Os} does not match current OS: {OSPlatform.Linux}. Snap id: {snapApp.Id}."); - return null; - } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && snapApp.Target.Os != OSPlatform.Linux) + { + logger?.Error($"Unable to install snap because target OS {snapApp.Target.Os} does not match current OS: {OSPlatform.Linux}. Snap id: {snapApp.Id}."); + return null; + } - logger?.Info($"Installing snap id: {snapApp.Id}. Version: {snapApp.Version}."); + logger?.Info($"Installing snap id: {snapApp.Id}. Version: {snapApp.Version}."); - if (snapApp.Target.PersistentAssets.Any()) - { - logger?.Info($"Persistent assets: {string.Join(", ", snapApp.Target.PersistentAssets)}"); - } + if (snapApp.Target.PersistentAssets.Any()) + { + logger?.Info($"Persistent assets: {string.Join(", ", snapApp.Target.PersistentAssets)}"); + } - snapProgressSource?.Raise(10); - if (_snapOs.Filesystem.DirectoryExists(baseDirectory)) - { - _snapOs.KillAllProcessesInsideDirectory(baseDirectory); - logger?.Info($"Deleting existing base directory: {baseDirectory}"); - await _snapOs.Filesystem.DirectoryDeleteAsync(baseDirectory, snapApp.Target.PersistentAssets); - } + snapProgressSource?.Raise(10); + if (_snapOs.Filesystem.DirectoryExists(baseDirectory)) + { + _snapOs.KillAllProcessesInsideDirectory(baseDirectory); + logger?.Info($"Deleting existing base directory: {baseDirectory}"); + await _snapOs.Filesystem.DirectoryDeleteAsync(baseDirectory, snapApp.Target.PersistentAssets); + } - snapProgressSource?.Raise(20); - logger?.Info($"Creating base directory: {baseDirectory}"); - _snapOs.Filesystem.DirectoryCreate(baseDirectory); + snapProgressSource?.Raise(20); + logger?.Info($"Creating base directory: {baseDirectory}"); + _snapOs.Filesystem.DirectoryCreate(baseDirectory); - snapProgressSource?.Raise(30); - var packagesDirectory = GetPackagesDirectory(baseDirectory); - logger?.Info($"Creating packages directory: {packagesDirectory}"); - _snapOs.Filesystem.DirectoryCreate(packagesDirectory); + snapProgressSource?.Raise(30); + var packagesDirectory = GetPackagesDirectory(baseDirectory); + logger?.Info($"Creating packages directory: {packagesDirectory}"); + _snapOs.Filesystem.DirectoryCreate(packagesDirectory); - snapProgressSource?.Raise(40); - if (copyNupkgToPackagesDirectory) - { - var dstNupkgFilename = _snapOs.Filesystem.PathCombine(packagesDirectory, snapApp.BuildNugetFilename()); - logger?.Info($"Copying nupkg to {dstNupkgFilename}"); - await _snapOs.Filesystem.FileCopyAsync(nupkgAbsoluteFilename, dstNupkgFilename, cancellationToken); - } + snapProgressSource?.Raise(40); + if (copyNupkgToPackagesDirectory) + { + var dstNupkgFilename = _snapOs.Filesystem.PathCombine(packagesDirectory, snapApp.BuildNugetFilename()); + logger?.Info($"Copying nupkg to {dstNupkgFilename}"); + await _snapOs.Filesystem.FileCopyAsync(nupkgAbsoluteFilename, dstNupkgFilename, cancellationToken); + } - snapProgressSource?.Raise(50); - var appDirectory = GetApplicationDirectory(baseDirectory, snapApp.Version); - logger?.Info($"Creating app directory: {appDirectory}"); - _snapOs.Filesystem.DirectoryCreate(appDirectory); + snapProgressSource?.Raise(50); + var appDirectory = GetApplicationDirectory(baseDirectory, snapApp.Version); + logger?.Info($"Creating app directory: {appDirectory}"); + _snapOs.Filesystem.DirectoryCreate(appDirectory); - snapProgressSource?.Raise(60); - logger?.Info($"Extracting nupkg to app directory: {appDirectory}"); - var extractedFiles = await _snapExtractor.ExtractAsync(appDirectory, snapRelease, packageArchiveReader, cancellationToken); - if (!extractedFiles.Any()) - { - logger?.Error($"Unknown error when attempting to extract nupkg: {nupkgAbsoluteFilename}"); - return null; - } + snapProgressSource?.Raise(60); + logger?.Info($"Extracting nupkg to app directory: {appDirectory}"); + var extractedFiles = await _snapExtractor.ExtractAsync(appDirectory, snapRelease, packageArchiveReader, cancellationToken); + if (!extractedFiles.Any()) + { + logger?.Error($"Unknown error when attempting to extract nupkg: {nupkgAbsoluteFilename}"); + return null; + } - snapProgressSource?.Raise(90); - logger?.Info("Performing post install tasks"); - var nuspecReader = await packageArchiveReader.GetNuspecReaderAsync(cancellationToken); + snapProgressSource?.Raise(90); + logger?.Info("Performing post install tasks"); + var nuspecReader = await packageArchiveReader.GetNuspecReaderAsync(cancellationToken); - await InvokePostInstall(snapApp, nuspecReader, - baseDirectory, appDirectory, snapApp.Version, true, logger, cancellationToken); - logger?.Info("Post install tasks completed"); + await InvokePostInstall(snapApp, nuspecReader, + baseDirectory, appDirectory, snapApp.Version, true, logger, cancellationToken); + logger?.Info("Post install tasks completed"); - snapProgressSource?.Raise(100); + snapProgressSource?.Raise(100); - return snapApp; - } + return snapApp; + } - async Task InvokePostInstall(SnapApp snapApp, NuspecReader nuspecReader, - string baseDirectory, string appDirectory, SemanticVersion currentVersion, - bool isInitialInstall, ILog logger = null, CancellationToken cancellationToken = default) - { - var chmod = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + async Task InvokePostInstall(SnapApp snapApp, NuspecReader nuspecReader, + string baseDirectory, string appDirectory, SemanticVersion currentVersion, + bool isInitialInstall, ILog logger = null, CancellationToken cancellationToken = default) + { + var chmod = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - var coreRunExeAbsolutePath = _snapOs.Filesystem - .PathCombine(baseDirectory, _snapEmbeddedResources.GetCoreRunExeFilenameForSnapApp(snapApp)); - var mainExeAbsolutePath = _snapOs.Filesystem - .PathCombine(appDirectory, _snapEmbeddedResources.GetCoreRunExeFilenameForSnapApp(snapApp)); - var iconAbsolutePath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && snapApp.Target.Icon != null ? - _snapOs.Filesystem.PathCombine(appDirectory, snapApp.Target.Icon) : null; - var snapChannel = snapApp.GetCurrentChannelOrThrow(); + var coreRunExeAbsolutePath = _snapOs.Filesystem + .PathCombine(baseDirectory, _snapEmbeddedResources.GetCoreRunExeFilenameForSnapApp(snapApp)); + var mainExeAbsolutePath = _snapOs.Filesystem + .PathCombine(appDirectory, _snapEmbeddedResources.GetCoreRunExeFilenameForSnapApp(snapApp)); + var iconAbsolutePath = !RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && snapApp.Target.Icon != null ? + _snapOs.Filesystem.PathCombine(appDirectory, snapApp.Target.Icon) : null; + var snapChannel = snapApp.GetCurrentChannelOrThrow(); - logger?.Debug($"{nameof(coreRunExeAbsolutePath)}: {coreRunExeAbsolutePath}"); - logger?.Debug($"{nameof(mainExeAbsolutePath)}: {mainExeAbsolutePath}"); - logger?.Debug($"{nameof(iconAbsolutePath)}: {iconAbsolutePath}"); + logger?.Debug($"{nameof(coreRunExeAbsolutePath)}: {coreRunExeAbsolutePath}"); + logger?.Debug($"{nameof(mainExeAbsolutePath)}: {mainExeAbsolutePath}"); + logger?.Debug($"{nameof(iconAbsolutePath)}: {iconAbsolutePath}"); - async Task ChmodAsync(string exePath) - { - if (exePath == null) throw new ArgumentNullException(nameof(exePath)); - logger?.Info($"Attempting to change file permission for executable: {exePath}."); - var chmodSuccess = await _snapOs.ProcessManager.ChmodExecuteAsync(exePath, cancellationToken); - logger?.Info($"Permissions changed successfully: {(chmodSuccess ? "true" : "false")}."); - } + async Task ChmodAsync(string exePath) + { + if (exePath == null) throw new ArgumentNullException(nameof(exePath)); + logger?.Info($"Attempting to change file permission for executable: {exePath}."); + var chmodSuccess = await _snapOs.ProcessManager.ChmodExecuteAsync(exePath, cancellationToken); + logger?.Info($"Permissions changed successfully: {(chmodSuccess ? "true" : "false")}."); + } - if (chmod) - { - await ChmodAsync(coreRunExeAbsolutePath); - await ChmodAsync(mainExeAbsolutePath); - } + if (chmod) + { + await ChmodAsync(coreRunExeAbsolutePath); + await ChmodAsync(mainExeAbsolutePath); + } - var coreRunExeFilename = _snapOs.Filesystem.PathGetFileName(coreRunExeAbsolutePath); + var coreRunExeFilename = _snapOs.Filesystem.PathGetFileName(coreRunExeAbsolutePath); - if (!snapApp.Target.Shortcuts.Any()) + if (!snapApp.Target.Shortcuts.Any()) + { + logger?.Warn("This application does not specify any shortcut locations."); + } + else + { + var shortcutLocations = snapApp.Target.Shortcuts.First(); + snapApp.Target.Shortcuts.Skip(1).ForEach(x => shortcutLocations |= x); + logger?.Info($"Shortcuts will be created in the following locations: {string.Join(", ", shortcutLocations)}"); + try { - logger?.Warn("This application does not specify any shortcut locations."); + var shortcutDescription = new SnapOsShortcutDescription + { + SnapApp = snapApp, + UpdateOnly = isInitialInstall == false, + NuspecReader = nuspecReader, + ShortcutLocations = shortcutLocations, + ExeAbsolutePath = coreRunExeAbsolutePath, + IconAbsolutePath = iconAbsolutePath + }; + + await _snapOs.CreateShortcutsForExecutableAsync(shortcutDescription, logger, cancellationToken); } - else + catch (Exception e) { - var shortcutLocations = snapApp.Target.Shortcuts.First(); - snapApp.Target.Shortcuts.Skip(1).ForEach(x => shortcutLocations |= x); - logger?.Info($"Shortcuts will be created in the following locations: {string.Join(", ", shortcutLocations)}"); - try - { - var shortcutDescription = new SnapOsShortcutDescription - { - SnapApp = snapApp, - UpdateOnly = isInitialInstall == false, - NuspecReader = nuspecReader, - ShortcutLocations = shortcutLocations, - ExeAbsolutePath = coreRunExeAbsolutePath, - IconAbsolutePath = iconAbsolutePath - }; - - await _snapOs.CreateShortcutsForExecutableAsync(shortcutDescription, logger, cancellationToken); - } - catch (Exception e) - { - logger?.ErrorException($"Exception thrown while creating shortcut for exe: {coreRunExeFilename}", e); - } + logger?.ErrorException($"Exception thrown while creating shortcut for exe: {coreRunExeFilename}", e); } + } - var snapAppDllAbsolutePath = _snapOs.Filesystem.PathCombine(appDirectory, SnapConstants.SnapAppDllFilename); + var snapAppDllAbsolutePath = _snapOs.Filesystem.PathCombine(appDirectory, SnapConstants.SnapAppDllFilename); - try - { - logger?.Info($"Updating {snapAppDllAbsolutePath}. Current channel is: {snapChannel.Name}."); + try + { + logger?.Info($"Updating {snapAppDllAbsolutePath}. Current channel is: {snapChannel.Name}."); - using var snapAppDllAssemblyDefinition = _snapAppWriter.BuildSnapAppAssembly(snapApp); - await using var snapAPpDllDestinationStream = _snapOs.Filesystem.FileWrite(snapAppDllAbsolutePath); - snapAppDllAssemblyDefinition.Write(snapAPpDllDestinationStream); - } - catch(Exception e) - { - logger?.ErrorException($"Unknown error updating {snapAppDllAbsolutePath}", e); - } + using var snapAppDllAssemblyDefinition = _snapAppWriter.BuildSnapAppAssembly(snapApp); + await using var snapAPpDllDestinationStream = _snapOs.Filesystem.FileWrite(snapAppDllAbsolutePath); + snapAppDllAssemblyDefinition.Write(snapAPpDllDestinationStream); + } + catch(Exception e) + { + logger?.ErrorException($"Unknown error updating {snapAppDllAbsolutePath}", e); + } - var allSnapAwareApps = new List + var allSnapAwareApps = new List { mainExeAbsolutePath }.Select(x => - { - var installOrUpdateTxt = isInitialInstall ? "--snapx-installed" : "--snapx-updated"; - return new ProcessStartInfoBuilder(x) - .Add(installOrUpdateTxt) - .Add(currentVersion.ToNormalizedString()); - }) - .ToList(); - - await InvokeSnapApps(allSnapAwareApps, TimeSpan.FromSeconds(15), isInitialInstall, currentVersion, logger, cancellationToken); - } - - async Task InvokeSnapApps([NotNull] List allSnapAwareApps, - TimeSpan cancelInvokeProcessesAfterTs, bool isInitialInstall, [NotNull] SemanticVersion semanticVersion, - ILog logger = null, CancellationToken cancellationToken = default) - { - if (allSnapAwareApps == null) throw new ArgumentNullException(nameof(allSnapAwareApps)); - if (semanticVersion == null) throw new ArgumentNullException(nameof(semanticVersion)); - - logger?.Info( - $"Invoking {allSnapAwareApps.Count} processes. " + - $"Timeout in {cancelInvokeProcessesAfterTs.TotalSeconds:F0} seconds."); - - var firstRunApplicationFilenames = new List(); - - var invocationTasks = allSnapAwareApps.ForEachAsync(async x => { - using var cts = new CancellationTokenSource(); - cts.CancelAfter(cancelInvokeProcessesAfterTs); + var installOrUpdateTxt = isInitialInstall ? "--snapx-installed" : "--snapx-updated"; + return new ProcessStartInfoBuilder(x) + .Add(installOrUpdateTxt) + .Add(currentVersion.ToNormalizedString()); + }) + .ToList(); + + await InvokeSnapApps(allSnapAwareApps, TimeSpan.FromSeconds(15), isInitialInstall, currentVersion, logger, cancellationToken); + } - try - { - logger?.Debug(x.ToString()); + async Task InvokeSnapApps([NotNull] List allSnapAwareApps, + TimeSpan cancelInvokeProcessesAfterTs, bool isInitialInstall, [NotNull] SemanticVersion semanticVersion, + ILog logger = null, CancellationToken cancellationToken = default) + { + if (allSnapAwareApps == null) throw new ArgumentNullException(nameof(allSnapAwareApps)); + if (semanticVersion == null) throw new ArgumentNullException(nameof(semanticVersion)); - var (exitCode, stdout) = await _snapOs.ProcessManager - .RunAsync(x, cancellationToken) // Two cancellation tokens is intentional because of unit tests mocks. - .WithCancellation(cts.Token); // Two cancellation tokens is intentional because of unit tests mocks. + logger?.Info( + $"Invoking {allSnapAwareApps.Count} processes. " + + $"Timeout in {cancelInvokeProcessesAfterTs.TotalSeconds:F0} seconds."); - logger?.Debug($"Processed exited: {exitCode}. Exe: {x.Filename}. Stdout: {stdout}."); - } - catch (Exception ex) - { - logger?.ErrorException($"Exception thrown while running application: {x.Filename}. Arguments: {x.Arguments}", ex); - } + var firstRunApplicationFilenames = new List(); - if (!isInitialInstall) - { - return; - } + var invocationTasks = allSnapAwareApps.ForEachAsync(async x => + { + using var cts = new CancellationTokenSource(); + cts.CancelAfter(cancelInvokeProcessesAfterTs); + + try + { + logger?.Debug(x.ToString()); - firstRunApplicationFilenames.Add(x.Filename); - }, 1 /* at a time */); + var (exitCode, stdout) = await _snapOs.ProcessManager + .RunAsync(x, cancellationToken) // Two cancellation tokens is intentional because of unit tests mocks. + .WithCancellation(cts.Token); // Two cancellation tokens is intentional because of unit tests mocks. - await Task.WhenAll(invocationTasks); + logger?.Debug($"Processed exited: {exitCode}. Exe: {x.Filename}. Stdout: {stdout}."); + } + catch (Exception ex) + { + logger?.ErrorException($"Exception thrown while running application: {x.Filename}. Arguments: {x.Arguments}", ex); + } if (!isInitialInstall) { return; } - firstRunApplicationFilenames.ForEach(filename => - { - var builder = new ProcessStartInfoBuilder(filename) - .Add($"--snapx-first-run {semanticVersion.ToNormalizedString()}"); - try - { - _snapOs.ProcessManager - .StartNonBlocking(builder); - } - catch (Exception ex) - { - logger?.ErrorException($"Exception thrown while running 'first-run' application: {builder.Filename}. Arguments: {builder.Arguments}", ex); - } - }); - } + firstRunApplicationFilenames.Add(x.Filename); + }, 1 /* at a time */); - public string GetApplicationDirectory(string baseDirectory, SemanticVersion version) - { - if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - if (version == null) throw new ArgumentNullException(nameof(version)); - return _snapOs.Filesystem.PathCombine(baseDirectory, "app-" + version); - } + await Task.WhenAll(invocationTasks); - public string GetApplicationDirectory(string baseDirectory, SnapRelease release) + if (!isInitialInstall) { - return GetApplicationDirectory(baseDirectory, release?.Version); + return; } - public string GetPackagesDirectory([NotNull] string baseDirectory) + firstRunApplicationFilenames.ForEach(filename => { - if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - return _snapOs.Filesystem.PathCombine(baseDirectory, "packages"); - } + var builder = new ProcessStartInfoBuilder(filename) + .Add($"--snapx-first-run {semanticVersion.ToNormalizedString()}"); + try + { + _snapOs.ProcessManager + .StartNonBlocking(builder); + } + catch (Exception ex) + { + logger?.ErrorException($"Exception thrown while running 'first-run' application: {builder.Filename}. Arguments: {builder.Arguments}", ex); + } + }); + } + public string GetApplicationDirectory(string baseDirectory, SemanticVersion version) + { + if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); + if (version == null) throw new ArgumentNullException(nameof(version)); + return _snapOs.Filesystem.PathCombine(baseDirectory, "app-" + version); } -} + + public string GetApplicationDirectory(string baseDirectory, SnapRelease release) + { + return GetApplicationDirectory(baseDirectory, release?.Version); + } + + public string GetPackagesDirectory([NotNull] string baseDirectory) + { + if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); + return _snapOs.Filesystem.PathCombine(baseDirectory, "packages"); + } + +} \ No newline at end of file diff --git a/src/Snap/Core/SnapNetworkTimeProvider.cs b/src/Snap/Core/SnapNetworkTimeProvider.cs index 51376d02..3ff7ece3 100644 --- a/src/Snap/Core/SnapNetworkTimeProvider.cs +++ b/src/Snap/Core/SnapNetworkTimeProvider.cs @@ -5,101 +5,100 @@ using System.Threading.Tasks; using Snap.Extensions; -namespace Snap.Core +namespace Snap.Core; + +internal interface ISnapNetworkTimeProvider { - internal interface ISnapNetworkTimeProvider + string Server { get; } + int Port { get; } + Task NowUtcAsync(TimeSpan timeout = default); + Task NowUtcAsync(string server, int port = 123, TimeSpan timeout = default); + DateTime? NowUtc(TimeSpan timeout = default); + DateTime? NowUtc(string server, int port = 123, TimeSpan timeout = default); +} + +internal sealed class SnapNetworkTimeProvider : ISnapNetworkTimeProvider +{ + public string Server { get; } + public int Port { get; } + + public SnapNetworkTimeProvider(string ntpServer, int port) { - string Server { get; } - int Port { get; } - Task NowUtcAsync(TimeSpan timeout = default); - Task NowUtcAsync(string server, int port = 123, TimeSpan timeout = default); - DateTime? NowUtc(TimeSpan timeout = default); - DateTime? NowUtc(string server, int port = 123, TimeSpan timeout = default); + if (string.IsNullOrWhiteSpace(ntpServer)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(ntpServer)); + if (port <= 0) throw new ArgumentOutOfRangeException(nameof(port)); + Server = ntpServer; + Port = port; } - internal sealed class SnapNetworkTimeProvider : ISnapNetworkTimeProvider + public Task NowUtcAsync(TimeSpan timeout = default) { - public string Server { get; } - public int Port { get; } + return NowImpl(Server, Port, timeout); + } - public SnapNetworkTimeProvider(string ntpServer, int port) - { - if (string.IsNullOrWhiteSpace(ntpServer)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(ntpServer)); - if (port <= 0) throw new ArgumentOutOfRangeException(nameof(port)); - Server = ntpServer; - Port = port; - } + public Task NowUtcAsync(string server, int port = 123, TimeSpan timeout = default) + { + return NowImpl(server, port, timeout); + } - public Task NowUtcAsync(TimeSpan timeout = default) - { - return NowImpl(Server, Port, timeout); - } + public DateTime? NowUtc(string server, int port = 123, TimeSpan timeout = default) + { + return TplHelper.RunSync(() => NowUtcAsync(server, port, timeout)); + } - public Task NowUtcAsync(string server, int port = 123, TimeSpan timeout = default) - { - return NowImpl(server, port, timeout); - } + public DateTime? NowUtc(TimeSpan timeout = default) + { + return TplHelper.RunSync(() => NowUtcAsync(timeout)); + } - public DateTime? NowUtc(string server, int port = 123, TimeSpan timeout = default) + static Task NowImpl(string ntpServer, int port, TimeSpan timeout) + { + if (port <= 0) { - return TplHelper.RunSync(() => NowUtcAsync(server, port, timeout)); + throw new ArgumentOutOfRangeException(nameof(port), port, "Must be greater than zero."); } - - public DateTime? NowUtc(TimeSpan timeout = default) + if (timeout == default || timeout <= TimeSpan.MinValue) { - return TplHelper.RunSync(() => NowUtcAsync(timeout)); + timeout = TimeSpan.FromSeconds(10); } - - static Task NowImpl(string ntpServer, int port, TimeSpan timeout) + var tsc = new TaskCompletionSource(); + ThreadPool.QueueUserWorkItem(state => { - if (port <= 0) + try { - throw new ArgumentOutOfRangeException(nameof(port), port, "Must be greater than zero."); + var ntpData = new byte[48]; + ntpData[0] = 0x1B; //LeapIndicator = 0 (no warning), VersionNum = 3 (IPv4 only), Mode = 3 (Client Mode) + + var addresses = Dns.GetHostAddresses(ntpServer); + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) + { + ReceiveTimeout = (int)timeout.TotalSeconds * 1000, + SendTimeout = (int)timeout.TotalSeconds * 1000 + }; + + socket.Connect(addresses, port); + socket.Send(ntpData); + socket.Receive(ntpData); + socket.Close(); + + var intPart = (ulong)ntpData[40] << 24 | (ulong)ntpData[41] << 16 | (ulong)ntpData[42] << 8 | ntpData[43]; + var fractPart = (ulong)ntpData[44] << 24 | (ulong)ntpData[45] << 16 | (ulong)ntpData[46] << 8 | ntpData[47]; + + var milliseconds = intPart * 1000 + fractPart * 1000 / 0x100000000L; + var networkDateTime = new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((long)milliseconds); + + tsc.TrySetResult(networkDateTime); } - if (timeout == default || timeout <= TimeSpan.MinValue) + catch { - timeout = TimeSpan.FromSeconds(10); + tsc.TrySetResult(null); } - var tsc = new TaskCompletionSource(); - ThreadPool.QueueUserWorkItem(state => - { - try - { - var ntpData = new byte[48]; - ntpData[0] = 0x1B; //LeapIndicator = 0 (no warning), VersionNum = 3 (IPv4 only), Mode = 3 (Client Mode) - - var addresses = Dns.GetHostAddresses(ntpServer); - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp) - { - ReceiveTimeout = (int)timeout.TotalSeconds * 1000, - SendTimeout = (int)timeout.TotalSeconds * 1000 - }; - - socket.Connect(addresses, port); - socket.Send(ntpData); - socket.Receive(ntpData); - socket.Close(); - - var intPart = (ulong)ntpData[40] << 24 | (ulong)ntpData[41] << 16 | (ulong)ntpData[42] << 8 | ntpData[43]; - var fractPart = (ulong)ntpData[44] << 24 | (ulong)ntpData[45] << 16 | (ulong)ntpData[46] << 8 | ntpData[47]; - - var milliseconds = intPart * 1000 + fractPart * 1000 / 0x100000000L; - var networkDateTime = new DateTime(1900, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds((long)milliseconds); - - tsc.TrySetResult(networkDateTime); - } - catch - { - tsc.TrySetResult(null); - } - }); + }); - return tsc.Task; - } + return tsc.Task; + } - public override string ToString() - { - return $"{Server}:{Port}"; - } + public override string ToString() + { + return $"{Server}:{Port}"; } -} +} \ No newline at end of file diff --git a/src/Snap/Core/SnapPack.cs b/src/Snap/Core/SnapPack.cs index 6ecb9840..e55d7fe0 100644 --- a/src/Snap/Core/SnapPack.cs +++ b/src/Snap/Core/SnapPack.cs @@ -321,8 +321,7 @@ await BuildFullPackageAsyncInternal(packageDetails, snapAppChannelReleases, snap var nuspecIntermediateStream = new MemoryStream(Encoding.UTF8.GetBytes(nuspecXml)); - var (nuspecStream, packageFiles) = BuildNuspec(nuspecIntermediateStream, nuspecPropertiesResolver, - snapNuspecDetails.NuspecBaseDirectory, fullSnapApp, fullSnapRelease); + var (nuspecStream, packageFiles) = BuildNuspec(nuspecIntermediateStream, nuspecPropertiesResolver, snapNuspecDetails.NuspecBaseDirectory); var nuspecStreamCpy = new MemoryStream(nuspecStream.ToArray()); // PackageBuilder closes stream @@ -966,27 +965,27 @@ async Task AddSnapAssetsAsync([NotNull] ISnapNuspecDetails snapNuspecDetails, [N AddPackageFile(packageBuilder, snapAppMemoryStream, SnapConstants.NuspecAssetsTargetPath, SnapConstants.SnapAppDllFilename, snapRelease); } - (MemoryStream nuspecStream, List<(string filename, string targetPath)> packageFiles) BuildNuspec([NotNull] MemoryStream nuspecStream, [NotNull] Func propertyProvider, - [NotNull] string baseDirectory, [NotNull] SnapApp snapApp, [NotNull] SnapRelease snapRelease) + (MemoryStream nuspecStream, List<(string filename, string targetPath)> packageFiles) BuildNuspec([NotNull] MemoryStream nuspecStream, [NotNull] Func propertyProvider, [NotNull] string baseDirectory) { if (nuspecStream == null) throw new ArgumentNullException(nameof(nuspecStream)); if (nuspecStream == null) throw new ArgumentNullException(nameof(nuspecStream)); if (propertyProvider == null) throw new ArgumentNullException(nameof(propertyProvider)); if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); const string nuspecXmlNs = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"; - var upstreamPackageId = snapApp.BuildNugetUpstreamId(); var packageFiles = new List<(string filename, string targetPath)>(); MemoryStream RewriteNuspecStreamWithEssentials() { - var nuspecDocument = XmlUtility.LoadSafe(nuspecStream); - if (nuspecDocument == null) + XDocument nuspecDocument; + try + { + nuspecDocument = new NuspecReader(nuspecStream, new DefaultFrameworkNameProvider(), true).Xml; + } + catch (Exception e) { - throw new Exception("Failed to parse nuspec"); + throw new Exception("Failed to parse nuspec", e); } var metadata = nuspecDocument.SingleOrDefault(XName.Get("metadata", nuspecXmlNs)); diff --git a/src/Snap/Core/SnapPackageManager.cs b/src/Snap/Core/SnapPackageManager.cs index 5d62d95c..f557e227 100644 --- a/src/Snap/Core/SnapPackageManager.cs +++ b/src/Snap/Core/SnapPackageManager.cs @@ -16,676 +16,675 @@ using Snap.Logging; using Snap.NuGet; -namespace Snap.Core +namespace Snap.Core; + +internal interface ISnapPackageManagerProgressSource { - internal interface ISnapPackageManagerProgressSource - { - Action<(int progressPercentage, long releasesOk, long releasesChecksummed, long releasesToChecksum)> ChecksumProgress { get; set; } + Action<(int progressPercentage, long releasesOk, long releasesChecksummed, long releasesToChecksum)> ChecksumProgress { get; set; } - Action<(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload)> - DownloadProgress { get; set; } + Action<(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload)> + DownloadProgress { get; set; } - Action<(int progressPercentage, long filesRestored, long filesToRestore)> RestoreProgress { get; set; } - void RaiseChecksumProgress(int progressPercentage, long releasesOk, long releasesChecksummed, long releasesToChecksum); + Action<(int progressPercentage, long filesRestored, long filesToRestore)> RestoreProgress { get; set; } + void RaiseChecksumProgress(int progressPercentage, long releasesOk, long releasesChecksummed, long releasesToChecksum); - void RaiseDownloadProgress(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, - long totalBytesToDownload); + void RaiseDownloadProgress(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, + long totalBytesToDownload); - void RaiseRestoreProgress(int progressPercentage, long filesRestored, long filesToRestore); - } + void RaiseRestoreProgress(int progressPercentage, long filesRestored, long filesToRestore); +} - internal sealed class SnapPackageManagerProgressSource : ISnapPackageManagerProgressSource - { - public Action<(int progressPercentage, long releasesOk, long releasesChecksummed, long releasesToChecksum)> ChecksumProgress { get; set; } +internal sealed class SnapPackageManagerProgressSource : ISnapPackageManagerProgressSource +{ + public Action<(int progressPercentage, long releasesOk, long releasesChecksummed, long releasesToChecksum)> ChecksumProgress { get; set; } - public Action<(int progressPercentage, long releasesDownloaded, - long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload)> DownloadProgress { get; set; } + public Action<(int progressPercentage, long releasesDownloaded, + long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload)> DownloadProgress { get; set; } - public Action<(int progressPercentage, long filesRestored, long filesToRestore)> RestoreProgress { get; set; } + public Action<(int progressPercentage, long filesRestored, long filesToRestore)> RestoreProgress { get; set; } - public void RaiseChecksumProgress(int progressPercentage, long releasesOk, long releasesChecksummed, long releasesToChecksum) - { - ChecksumProgress?.Invoke((progressPercentage, releasesOk, releasesChecksummed, releasesToChecksum)); - } - - public void RaiseDownloadProgress(int progressPercentage, long releasesDownloaded, - long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload) - { - DownloadProgress?.Invoke((progressPercentage, releasesDownloaded, releasesToDownload, totalBytesDownloaded, totalBytesToDownload)); - } - - public void RaiseRestoreProgress(int progressPercentage, long filesRestored, long filesToRestore) - { - RestoreProgress?.Invoke((progressPercentage, filesRestored, filesToRestore)); - } + public void RaiseChecksumProgress(int progressPercentage, long releasesOk, long releasesChecksummed, long releasesToChecksum) + { + ChecksumProgress?.Invoke((progressPercentage, releasesOk, releasesChecksummed, releasesToChecksum)); } - public enum SnapPackageManagerRestoreType + public void RaiseDownloadProgress(int progressPercentage, long releasesDownloaded, + long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload) { - Default, - Pack + DownloadProgress?.Invoke((progressPercentage, releasesDownloaded, releasesToDownload, totalBytesDownloaded, totalBytesToDownload)); } - internal interface ISnapPackageManager + public void RaiseRestoreProgress(int progressPercentage, long filesRestored, long filesToRestore) { - Task GetPackageSourceAsync(SnapApp snapApp, ILog logger = null, string applicationId = null); + RestoreProgress?.Invoke((progressPercentage, filesRestored, filesToRestore)); + } +} + +public enum SnapPackageManagerRestoreType +{ + Default, + Pack +} - Task<(SnapAppsReleases snapAppsReleases, PackageSource packageSource, MemoryStream releasesMemoryStream)> GetSnapsReleasesAsync( - [JetBrains.Annotations.NotNull] SnapApp snapApp, ILog logger = null, CancellationToken cancellationToken = default, string applicationId = null); +internal interface ISnapPackageManager +{ + Task GetPackageSourceAsync(SnapApp snapApp, ILog logger = null, string applicationId = null); - Task RestoreAsync([JetBrains.Annotations.NotNull] string packagesDirectory, - [JetBrains.Annotations.NotNull] ISnapAppChannelReleases snapAppChannelReleases, - [JetBrains.Annotations.NotNull] PackageSource packageSource, SnapPackageManagerRestoreType restoreType, - ISnapPackageManagerProgressSource progressSource = null, - ILog logger = null, CancellationToken cancellationToken = default, - int checksumConcurrency = 1, int downloadConcurrency = 2, int restoreConcurrency = 1); - } + Task<(SnapAppsReleases snapAppsReleases, PackageSource packageSource, MemoryStream releasesMemoryStream)> GetSnapsReleasesAsync( + [JetBrains.Annotations.NotNull] SnapApp snapApp, ILog logger = null, CancellationToken cancellationToken = default, string applicationId = null); - internal class SnapPackageManagerNugetHttpFeed + Task RestoreAsync([JetBrains.Annotations.NotNull] string packagesDirectory, + [JetBrains.Annotations.NotNull] ISnapAppChannelReleases snapAppChannelReleases, + [JetBrains.Annotations.NotNull] PackageSource packageSource, SnapPackageManagerRestoreType restoreType, + ISnapPackageManagerProgressSource progressSource = null, + ILog logger = null, CancellationToken cancellationToken = default, + int checksumConcurrency = 1, int downloadConcurrency = 2, int restoreConcurrency = 1); +} + +internal class SnapPackageManagerNugetHttpFeed +{ + public Uri Source { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public string ApiKey { get; set; } + public NuGetProtocolVersion ProtocolVersion { get; set; } +} + +internal class SnapPackageManagerReleaseStatus +{ + public SnapRelease SnapRelease { get; } + public bool Ok { get; } + + public SnapPackageManagerReleaseStatus([JetBrains.Annotations.NotNull] SnapRelease snapRelease, bool ok) { - public Uri Source { get; set; } - public string Username { get; set; } - public string Password { get; set; } - public string ApiKey { get; set; } - public NuGetProtocolVersion ProtocolVersion { get; set; } + SnapRelease = snapRelease ?? throw new ArgumentNullException(nameof(snapRelease)); + Ok = ok; } +} - internal class SnapPackageManagerReleaseStatus - { - public SnapRelease SnapRelease { get; } - public bool Ok { get; } +internal sealed class SnapPackageManagerRestoreSummary +{ + public SnapPackageManagerRestoreType RestoreType { get; } + public List ChecksumSummary { get; private set; } + public List DownloadSummary { get; private set; } + public List ReassembleSummary { get; private set; } + public bool Success { get; set; } - public SnapPackageManagerReleaseStatus([JetBrains.Annotations.NotNull] SnapRelease snapRelease, bool ok) - { - SnapRelease = snapRelease ?? throw new ArgumentNullException(nameof(snapRelease)); - Ok = ok; - } + public SnapPackageManagerRestoreSummary(SnapPackageManagerRestoreType restoreType) + { + RestoreType = restoreType; + ChecksumSummary = new List(); + DownloadSummary = new List(); + ReassembleSummary = new List(); } - internal sealed class SnapPackageManagerRestoreSummary + public void Sort() { - public SnapPackageManagerRestoreType RestoreType { get; } - public List ChecksumSummary { get; private set; } - public List DownloadSummary { get; private set; } - public List ReassembleSummary { get; private set; } - public bool Success { get; set; } - - public SnapPackageManagerRestoreSummary(SnapPackageManagerRestoreType restoreType) - { - RestoreType = restoreType; - ChecksumSummary = new List(); - DownloadSummary = new List(); - ReassembleSummary = new List(); - } + ChecksumSummary = ChecksumSummary.OrderBy(x => x.SnapRelease.Version).ThenBy(x => x.SnapRelease.Filename).ToList(); + DownloadSummary = DownloadSummary.OrderBy(x => x.SnapRelease.Version).ThenBy(x => x.SnapRelease.Filename).ToList(); + ReassembleSummary = ReassembleSummary.OrderBy(x => x.SnapRelease.Version).ThenBy(x => x.SnapRelease.Filename).ToList(); + } +} - public void Sort() - { - ChecksumSummary = ChecksumSummary.OrderBy(x => x.SnapRelease.Version).ThenBy(x => x.SnapRelease.Filename).ToList(); - DownloadSummary = DownloadSummary.OrderBy(x => x.SnapRelease.Version).ThenBy(x => x.SnapRelease.Filename).ToList(); - ReassembleSummary = ReassembleSummary.OrderBy(x => x.SnapRelease.Version).ThenBy(x => x.SnapRelease.Filename).ToList(); - } +internal sealed class SnapPackageManager : ISnapPackageManager +{ + readonly ISnapFilesystem _filesystem; + readonly ISnapOsSpecialFolders _specialFolders; + readonly INugetService _nugetService; + [JetBrains.Annotations.NotNull] readonly ISnapHttpClient _snapHttpClient; + readonly ISnapCryptoProvider _snapCryptoProvider; + readonly ISnapExtractor _snapExtractor; + readonly ISnapAppReader _snapAppReader; + readonly ISnapPack _snapPack; + + public SnapPackageManager([JetBrains.Annotations.NotNull] ISnapFilesystem filesystem, + [JetBrains.Annotations.NotNull] ISnapOsSpecialFolders specialFolders, + [JetBrains.Annotations.NotNull] INugetService nugetService, + [JetBrains.Annotations.NotNull] ISnapHttpClient snapHttpClient, + [JetBrains.Annotations.NotNull] ISnapCryptoProvider snapCryptoProvider, + [JetBrains.Annotations.NotNull] ISnapExtractor snapExtractor, + [JetBrains.Annotations.NotNull] ISnapAppReader snapAppReader, + [JetBrains.Annotations.NotNull] ISnapPack snapPack) + { + _filesystem = filesystem ?? throw new ArgumentNullException(nameof(filesystem)); + _specialFolders = specialFolders ?? throw new ArgumentNullException(nameof(specialFolders)); + _nugetService = nugetService ?? throw new ArgumentNullException(nameof(nugetService)); + _snapHttpClient = snapHttpClient ?? throw new ArgumentNullException(nameof(snapHttpClient)); + _snapCryptoProvider = snapCryptoProvider ?? throw new ArgumentNullException(nameof(snapCryptoProvider)); + _snapExtractor = snapExtractor ?? throw new ArgumentNullException(nameof(snapExtractor)); + _snapAppReader = snapAppReader ?? throw new ArgumentNullException(nameof(snapAppReader)); + _snapPack = snapPack ?? throw new ArgumentNullException(nameof(snapPack)); } - internal sealed class SnapPackageManager : ISnapPackageManager + public async Task GetPackageSourceAsync( + [JetBrains.Annotations.NotNull] SnapApp snapApp, + ILog logger = null, + string applicationId = null) { - readonly ISnapFilesystem _filesystem; - readonly ISnapOsSpecialFolders _specialFolders; - readonly INugetService _nugetService; - [JetBrains.Annotations.NotNull] readonly ISnapHttpClient _snapHttpClient; - readonly ISnapCryptoProvider _snapCryptoProvider; - readonly ISnapExtractor _snapExtractor; - readonly ISnapAppReader _snapAppReader; - readonly ISnapPack _snapPack; - - public SnapPackageManager([JetBrains.Annotations.NotNull] ISnapFilesystem filesystem, - [JetBrains.Annotations.NotNull] ISnapOsSpecialFolders specialFolders, - [JetBrains.Annotations.NotNull] INugetService nugetService, - [JetBrains.Annotations.NotNull] ISnapHttpClient snapHttpClient, - [JetBrains.Annotations.NotNull] ISnapCryptoProvider snapCryptoProvider, - [JetBrains.Annotations.NotNull] ISnapExtractor snapExtractor, - [JetBrains.Annotations.NotNull] ISnapAppReader snapAppReader, - [JetBrains.Annotations.NotNull] ISnapPack snapPack) - { - _filesystem = filesystem ?? throw new ArgumentNullException(nameof(filesystem)); - _specialFolders = specialFolders ?? throw new ArgumentNullException(nameof(specialFolders)); - _nugetService = nugetService ?? throw new ArgumentNullException(nameof(nugetService)); - _snapHttpClient = snapHttpClient ?? throw new ArgumentNullException(nameof(snapHttpClient)); - _snapCryptoProvider = snapCryptoProvider ?? throw new ArgumentNullException(nameof(snapCryptoProvider)); - _snapExtractor = snapExtractor ?? throw new ArgumentNullException(nameof(snapExtractor)); - _snapAppReader = snapAppReader ?? throw new ArgumentNullException(nameof(snapAppReader)); - _snapPack = snapPack ?? throw new ArgumentNullException(nameof(snapPack)); - } + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - public async Task GetPackageSourceAsync( - [JetBrains.Annotations.NotNull] SnapApp snapApp, - ILog logger = null, - string applicationId = null) + try { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + var channel = snapApp.GetCurrentChannelOrThrow(); - try + switch (channel.UpdateFeed) { - var channel = snapApp.GetCurrentChannelOrThrow(); - - switch (channel.UpdateFeed) + case SnapHttpFeed feed: { - case SnapHttpFeed feed: + var headers = new Dictionary { - var headers = new Dictionary - { - { "X-Snapx-App-Id", snapApp.Id}, - { "X-Snapx-Channel", channel.Name }, - { "X-Snapx-Application-Id", applicationId }, - { "X-Snapx-Application-Version", snapApp.Version?.ToNormalizedString() } - }; + { "X-Snapx-App-Id", snapApp.Id}, + { "X-Snapx-Channel", channel.Name }, + { "X-Snapx-Application-Id", applicationId }, + { "X-Snapx-Application-Version", snapApp.Version?.ToNormalizedString() } + }; - await using var stream = await _snapHttpClient.GetStreamAsync(feed.Source, headers); - stream.Seek(0, SeekOrigin.Begin); + await using var stream = await _snapHttpClient.GetStreamAsync(feed.Source, headers); + stream.Seek(0, SeekOrigin.Begin); - var jsonStream = await stream.ReadToEndAsync(); + var jsonStream = await stream.ReadToEndAsync(); - var utf8String = Encoding.UTF8.GetString(jsonStream.ToArray()); + var utf8String = Encoding.UTF8.GetString(jsonStream.ToArray()); - var packageManagerNugetHttp = JsonConvert.DeserializeObject(utf8String, new JsonSerializerSettings - { - Converters = new List - { - new StringEnumConverter() - }, - NullValueHandling = NullValueHandling.Ignore - }); - - if (packageManagerNugetHttp == null) + var packageManagerNugetHttp = JsonConvert.DeserializeObject(utf8String, new JsonSerializerSettings + { + Converters = new List { - throw new Exception($"Unable to deserialize nuget http feed. Url: {feed.Source}. Response length: {stream.Position}"); - } + new StringEnumConverter() + }, + NullValueHandling = NullValueHandling.Ignore + }); - var snapNugetFeed = new SnapNugetFeed(packageManagerNugetHttp) - { - Name = $"{snapApp.Id}-{channel.Name}-http" - }; + if (packageManagerNugetHttp == null) + { + throw new Exception($"Unable to deserialize nuget http feed. Url: {feed.Source}. Response length: {stream.Position}"); + } - return snapNugetFeed.BuildPackageSource(new NugetInMemorySettings(_specialFolders.NugetCacheDirectory)); + var snapNugetFeed = new SnapNugetFeed(packageManagerNugetHttp) + { + Name = $"{snapApp.Id}-{channel.Name}-http" + }; - } - case SnapNugetFeed snapNugetFeed: - var nugetPackageSources = snapApp.BuildNugetSources(_specialFolders.NugetCacheDirectory); - - var packageSource = nugetPackageSources.Items.Single(x => x.Name == snapNugetFeed.Name - && x.SourceUri == snapNugetFeed.Source); - return packageSource; - default: - throw new NotSupportedException(channel.UpdateFeed?.GetType().FullName); - } + return snapNugetFeed.BuildPackageSource(new NugetInMemorySettings(_specialFolders.NugetCacheDirectory)); + } + case SnapNugetFeed snapNugetFeed: + var nugetPackageSources = snapApp.BuildNugetSources(_specialFolders.NugetCacheDirectory); + + var packageSource = nugetPackageSources.Items.Single(x => x.Name == snapNugetFeed.Name + && x.SourceUri == snapNugetFeed.Source); + return packageSource; + default: + throw new NotSupportedException(channel.UpdateFeed?.GetType().FullName); } - catch(Exception e) - { - logger?.ErrorException("Unknown error building package source", e); - return null; - } - } - public async Task<(SnapAppsReleases snapAppsReleases, PackageSource packageSource, MemoryStream releasesMemoryStream)> - GetSnapsReleasesAsync( - SnapApp snapApp, ILog logger = null, CancellationToken cancellationToken = default, string applicationId = null) + } + catch(Exception e) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - - var packageId = snapApp.BuildNugetReleasesUpstreamId(); + logger?.ErrorException("Unknown error building package source", e); + return null; + } + } - try - { - var packageSource = await GetPackageSourceAsync(snapApp, logger, applicationId); + public async Task<(SnapAppsReleases snapAppsReleases, PackageSource packageSource, MemoryStream releasesMemoryStream)> + GetSnapsReleasesAsync( + SnapApp snapApp, ILog logger = null, CancellationToken cancellationToken = default, string applicationId = null) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var snapReleasesDownloadResult = - await _nugetService.DownloadLatestAsync(packageId, packageSource, false, true, cancellationToken); + var packageId = snapApp.BuildNugetReleasesUpstreamId(); - if (!snapReleasesDownloadResult.SuccessSafe()) - { - var sourceLocation = packageSource.IsLocalOrUncPath() - ? $"path: {_filesystem.PathGetFullPath(packageSource.SourceUri.AbsolutePath)}. Does the location exist?" - : packageSource.Name; - logger?.Error($"Unknown error while downloading releases nupkg {packageId} from {sourceLocation}"); - return (null, null, null); - } + try + { + var packageSource = await GetPackageSourceAsync(snapApp, logger, applicationId); - using var packageArchiveReader = new PackageArchiveReader(snapReleasesDownloadResult.PackageStream, true); - var snapReleases = await _snapExtractor.GetSnapAppsReleasesAsync(packageArchiveReader, _snapAppReader, cancellationToken); - if (snapReleases != null) - { - snapReleasesDownloadResult.PackageStream.Seek(0, SeekOrigin.Begin); - return (snapReleases, packageSource, (MemoryStream) snapReleasesDownloadResult.PackageStream); - } + var snapReleasesDownloadResult = + await _nugetService.DownloadLatestAsync(packageId, packageSource, false, true, cancellationToken); - logger?.Error($"Unknown error unpacking releases nupkg: {packageId}."); + if (!snapReleasesDownloadResult.SuccessSafe()) + { + var sourceLocation = packageSource.IsLocalOrUncPath() + ? $"path: {_filesystem.PathGetFullPath(packageSource.SourceUri.AbsolutePath)}. Does the location exist?" + : packageSource.Name; + logger?.Error($"Unknown error while downloading releases nupkg {packageId} from {sourceLocation}"); return (null, null, null); } - catch (Exception e) + + using var packageArchiveReader = new PackageArchiveReader(snapReleasesDownloadResult.PackageStream, true); + var snapReleases = await _snapExtractor.GetSnapAppsReleasesAsync(packageArchiveReader, _snapAppReader, cancellationToken); + if (snapReleases != null) { - logger?.ErrorException($"Exception thrown while downloading releases nupkg: {packageId}.", e); - return (null, null, null); + snapReleasesDownloadResult.PackageStream.Seek(0, SeekOrigin.Begin); + return (snapReleases, packageSource, (MemoryStream) snapReleasesDownloadResult.PackageStream); } - } - public async Task RestoreAsync(string packagesDirectory, ISnapAppChannelReleases snapAppChannelReleases, - PackageSource packageSource, SnapPackageManagerRestoreType restoreType, ISnapPackageManagerProgressSource progressSource = null, - ILog logger = null, CancellationToken cancellationToken = default, int checksumConcurrency = 1, int downloadConcurrency = 2, int restoreConcurrency = 1) + logger?.Error($"Unknown error unpacking releases nupkg: {packageId}."); + return (null, null, null); + } + catch (Exception e) { - if (packagesDirectory == null) throw new ArgumentNullException(nameof(packagesDirectory)); - if (snapAppChannelReleases == null) throw new ArgumentNullException(nameof(snapAppChannelReleases)); - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - if (checksumConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(checksumConcurrency)); - if (downloadConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(downloadConcurrency)); - if (restoreConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(restoreConcurrency)); + logger?.ErrorException($"Exception thrown while downloading releases nupkg: {packageId}.", e); + return (null, null, null); + } + } - var restoreSummary = new SnapPackageManagerRestoreSummary(restoreType); + public async Task RestoreAsync(string packagesDirectory, ISnapAppChannelReleases snapAppChannelReleases, + PackageSource packageSource, SnapPackageManagerRestoreType restoreType, ISnapPackageManagerProgressSource progressSource = null, + ILog logger = null, CancellationToken cancellationToken = default, int checksumConcurrency = 1, int downloadConcurrency = 2, int restoreConcurrency = 1) + { + if (packagesDirectory == null) throw new ArgumentNullException(nameof(packagesDirectory)); + if (snapAppChannelReleases == null) throw new ArgumentNullException(nameof(snapAppChannelReleases)); + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + if (checksumConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(checksumConcurrency)); + if (downloadConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(downloadConcurrency)); + if (restoreConcurrency <= 0) throw new ArgumentOutOfRangeException(nameof(restoreConcurrency)); - var genesisRelease = snapAppChannelReleases.GetGenesisRelease(); - if (genesisRelease == null) - { - logger?.Info($"Nothing to restore. Genesis release does not exist in channel: {snapAppChannelReleases.Channel.Name}."); + var restoreSummary = new SnapPackageManagerRestoreSummary(restoreType); - restoreSummary.Success = true; - return restoreSummary; - } + var genesisRelease = snapAppChannelReleases.GetGenesisRelease(); + if (genesisRelease == null) + { + logger?.Info($"Nothing to restore. Genesis release does not exist in channel: {snapAppChannelReleases.Channel.Name}."); - var stopwatch = new Stopwatch(); - stopwatch.Restart(); + restoreSummary.Success = true; + return restoreSummary; + } - // Checksum - await ChecksumAsync(); - restoreSummary.Sort(); - restoreSummary.Success = restoreSummary.ChecksumSummary.Count > 0 - && restoreSummary.ChecksumSummary.All(x => x.Ok); + var stopwatch = new Stopwatch(); + stopwatch.Restart(); - // Download - stopwatch.Restart(); - restoreSummary.Success = await DownloadAsync(); + // Checksum + await ChecksumAsync(); + restoreSummary.Sort(); + restoreSummary.Success = restoreSummary.ChecksumSummary.Count > 0 + && restoreSummary.ChecksumSummary.All(x => x.Ok); - // Reassamble - stopwatch.Restart(); - restoreSummary.Success = restoreSummary.Success && await ReassembleAsync(); - restoreSummary.Sort(); + // Download + stopwatch.Restart(); + restoreSummary.Success = await DownloadAsync(); + + // Reassamble + stopwatch.Restart(); + restoreSummary.Success = restoreSummary.Success && await ReassembleAsync(); + restoreSummary.Sort(); - return restoreSummary; + return restoreSummary; - async Task ChecksumAsync() - { - var snapReleasesToChecksum = new List(); + async Task ChecksumAsync() + { + var snapReleasesToChecksum = new List(); - switch (restoreType) - { - case SnapPackageManagerRestoreType.Default: - snapReleasesToChecksum.AddRange(snapAppChannelReleases.Where(x => x.IsGenesis || x.IsDelta)); - if (snapAppChannelReleases.HasDeltaReleases()) - { - snapReleasesToChecksum.Add(snapAppChannelReleases.Last().AsFullRelease(false)); - } - break; - case SnapPackageManagerRestoreType.Pack: - snapReleasesToChecksum.AddRange(snapAppChannelReleases.Where(x => x.IsGenesis || x.IsDelta)); - break; - default: - throw new NotSupportedException(restoreType.ToString()); - } + switch (restoreType) + { + case SnapPackageManagerRestoreType.Default: + snapReleasesToChecksum.AddRange(snapAppChannelReleases.Where(x => x.IsGenesis || x.IsDelta)); + if (snapAppChannelReleases.HasDeltaReleases()) + { + snapReleasesToChecksum.Add(snapAppChannelReleases.Last().AsFullRelease(false)); + } + break; + case SnapPackageManagerRestoreType.Pack: + snapReleasesToChecksum.AddRange(snapAppChannelReleases.Where(x => x.IsGenesis || x.IsDelta)); + break; + default: + throw new NotSupportedException(restoreType.ToString()); + } - logger?.Info($"Verifying checksums for {snapReleasesToChecksum.Count} packages."); + logger?.Info($"Verifying checksums for {snapReleasesToChecksum.Count} packages."); - long snapReleasesChecksummed = 0; - long snapReleasesChecksumOk = 0; - long totalSnapReleasesToChecksum = snapReleasesToChecksum.Count; + long snapReleasesChecksummed = 0; + long snapReleasesChecksumOk = 0; + long totalSnapReleasesToChecksum = snapReleasesToChecksum.Count; - progressSource?.RaiseChecksumProgress(0, - 0, snapReleasesChecksummed, totalSnapReleasesToChecksum); + progressSource?.RaiseChecksumProgress(0, + 0, snapReleasesChecksummed, totalSnapReleasesToChecksum); - logger?.Info("Checksum progress: 0%"); + logger?.Info("Checksum progress: 0%"); - await snapReleasesToChecksum.ForEachAsync(snapRelease => + await snapReleasesToChecksum.ForEachAsync(snapRelease => + { + return Task.Run(() => { - return Task.Run(() => + long checksumOkCount = 0; + try { - long checksumOkCount = 0; - try - { - var nupkgAbsolutePath = _filesystem.PathCombine(packagesDirectory, snapRelease.Filename); - var checksumOk = TryChecksum(snapRelease, nupkgAbsolutePath, logger: logger); - restoreSummary.ChecksumSummary.Add(new SnapPackageManagerReleaseStatus(snapRelease, checksumOk)); - checksumOkCount = checksumOk ? Interlocked.Increment(ref snapReleasesChecksumOk) : Interlocked.Read(ref snapReleasesChecksumOk); - } - catch (Exception e) - { - logger?.ErrorException($"Unknown error while checksumming: {snapRelease.Filename}", e); - } + var nupkgAbsolutePath = _filesystem.PathCombine(packagesDirectory, snapRelease.Filename); + var checksumOk = TryChecksum(snapRelease, nupkgAbsolutePath, logger: logger); + restoreSummary.ChecksumSummary.Add(new SnapPackageManagerReleaseStatus(snapRelease, checksumOk)); + checksumOkCount = checksumOk ? Interlocked.Increment(ref snapReleasesChecksumOk) : Interlocked.Read(ref snapReleasesChecksumOk); + } + catch (Exception e) + { + logger?.ErrorException($"Unknown error while checksumming: {snapRelease.Filename}", e); + } - var totalSnapReleasesChecksummedSoFar = Interlocked.Increment(ref snapReleasesChecksummed); - var totalProgressPercentage = (int) Math.Floor(totalSnapReleasesChecksummedSoFar / (double) totalSnapReleasesToChecksum * 100); + var totalSnapReleasesChecksummedSoFar = Interlocked.Increment(ref snapReleasesChecksummed); + var totalProgressPercentage = (int) Math.Floor(totalSnapReleasesChecksummedSoFar / (double) totalSnapReleasesToChecksum * 100); - progressSource?.RaiseChecksumProgress(totalProgressPercentage, checksumOkCount, - snapReleasesChecksummed, totalSnapReleasesToChecksum); + progressSource?.RaiseChecksumProgress(totalProgressPercentage, checksumOkCount, + snapReleasesChecksummed, totalSnapReleasesToChecksum); - if (totalProgressPercentage % 5 == 0 - || totalSnapReleasesToChecksum <= 5) - { - logger?.Info($"Checksum progress: {totalProgressPercentage}% - Completed {snapReleasesChecksummed} of {totalSnapReleasesToChecksum}."); - } + if (totalProgressPercentage % 5 == 0 + || totalSnapReleasesToChecksum <= 5) + { + logger?.Info($"Checksum progress: {totalProgressPercentage}% - Completed {snapReleasesChecksummed} of {totalSnapReleasesToChecksum}."); + } - }, cancellationToken); - }, checksumConcurrency); + }, cancellationToken); + }, checksumConcurrency); - logger?.Info($"Checksummed {snapReleasesChecksummed} packages in {stopwatch.Elapsed.TotalSeconds:0.0}s. "); - } + logger?.Info($"Checksummed {snapReleasesChecksummed} packages in {stopwatch.Elapsed.TotalSeconds:0.0}s. "); + } - async Task DownloadAsync() + async Task DownloadAsync() + { + var releasesToDownload = restoreSummary.ChecksumSummary + .Where(x => !x.Ok && (x.SnapRelease.IsGenesis || x.SnapRelease.IsDelta)) + .Select(x => x.SnapRelease) + .OrderBy(x => x.Version) + .ToList(); + if (!releasesToDownload.Any()) { - var releasesToDownload = restoreSummary.ChecksumSummary - .Where(x => !x.Ok && (x.SnapRelease.IsGenesis || x.SnapRelease.IsDelta)) - .Select(x => x.SnapRelease) - .OrderBy(x => x.Version) - .ToList(); - if (!releasesToDownload.Any()) - { - return true; - } + return true; + } - var totalBytesToDownload = releasesToDownload.Sum(x => x.IsFull ? x.FullFilesize : x.DeltaFilesize); + var totalBytesToDownload = releasesToDownload.Sum(x => x.IsFull ? x.FullFilesize : x.DeltaFilesize); - logger?.Info($"Downloading {releasesToDownload.Count} packages. " + - $"Total download size: {totalBytesToDownload.BytesAsHumanReadable()}."); + logger?.Info($"Downloading {releasesToDownload.Count} packages. " + + $"Total download size: {totalBytesToDownload.BytesAsHumanReadable()}."); - if (_filesystem.DirectoryCreateIfNotExists(packagesDirectory)) - { - logger?.Debug($"Created packages directory: {packagesDirectory}"); - } + if (_filesystem.DirectoryCreateIfNotExists(packagesDirectory)) + { + logger?.Debug($"Created packages directory: {packagesDirectory}"); + } - long totalReleasesToDownload = releasesToDownload.Count; - long totalReleasesDownloaded = default; - long totalBytesDownloadedSoFar = default; - long downloadProgressPercentage = default; - var previousProgressReportDateTime = DateTime.UtcNow; + long totalReleasesToDownload = releasesToDownload.Count; + long totalReleasesDownloaded = default; + long totalBytesDownloadedSoFar = default; + long downloadProgressPercentage = default; + var previousProgressReportDateTime = DateTime.UtcNow; - progressSource?.RaiseDownloadProgress(0, 0, - totalReleasesToDownload, 0, totalBytesToDownload); + progressSource?.RaiseDownloadProgress(0, 0, + totalReleasesToDownload, 0, totalBytesToDownload); - logger?.Info($"Download progress: 0% - Transferred 0 bytes of {totalBytesToDownload.BytesAsHumanReadable()}"); + logger?.Info($"Download progress: 0% - Transferred 0 bytes of {totalBytesToDownload.BytesAsHumanReadable()}"); - await releasesToDownload.ForEachAsync(async snapRelease => + await releasesToDownload.ForEachAsync(async snapRelease => + { + var thisProgressSource = new NugetServiceProgressSource { - var thisProgressSource = new NugetServiceProgressSource + Progress = tuple => { - Progress = tuple => - { - var (progressPercentage, bytesRead, _, _) = tuple; + var (progressPercentage, bytesRead, _, _) = tuple; - var totalReleasesDownloadedVolatile = Interlocked.Read(ref totalReleasesDownloaded); - var totalBytesDownloadedSoFarVolatile = Interlocked.Add(ref totalBytesDownloadedSoFar, bytesRead); + var totalReleasesDownloadedVolatile = Interlocked.Read(ref totalReleasesDownloaded); + var totalBytesDownloadedSoFarVolatile = Interlocked.Add(ref totalBytesDownloadedSoFar, bytesRead); - if (progressPercentage == 100) - { - totalReleasesDownloadedVolatile = Interlocked.Increment(ref totalReleasesDownloaded); - } + if (progressPercentage == 100) + { + totalReleasesDownloadedVolatile = Interlocked.Increment(ref totalReleasesDownloaded); + } - var totalBytesDownloadedPercentage = (int) Math.Floor( - (double) totalBytesDownloadedSoFarVolatile / totalBytesToDownload * 100d); + var totalBytesDownloadedPercentage = (int) Math.Floor( + (double) totalBytesDownloadedSoFarVolatile / totalBytesToDownload * 100d); - Interlocked.Exchange(ref downloadProgressPercentage, totalBytesDownloadedPercentage); + Interlocked.Exchange(ref downloadProgressPercentage, totalBytesDownloadedPercentage); - progressSource?.RaiseDownloadProgress(totalBytesDownloadedPercentage, - totalReleasesDownloadedVolatile, totalReleasesToDownload, - totalBytesDownloadedSoFarVolatile, totalBytesToDownload); + progressSource?.RaiseDownloadProgress(totalBytesDownloadedPercentage, + totalReleasesDownloadedVolatile, totalReleasesToDownload, + totalBytesDownloadedSoFarVolatile, totalBytesToDownload); - if (progressPercentage < 100 - && DateTime.UtcNow - previousProgressReportDateTime <= TimeSpan.FromSeconds(0.5)) - { - return; - } + if (progressPercentage < 100 + && DateTime.UtcNow - previousProgressReportDateTime <= TimeSpan.FromSeconds(0.5)) + { + return; + } - previousProgressReportDateTime = DateTime.UtcNow; + previousProgressReportDateTime = DateTime.UtcNow; - if (totalBytesDownloadedPercentage % 5 == 0 - || totalReleasesToDownload <= 5) - { - logger?.Info($"Download progress: {totalBytesDownloadedPercentage}% - Transferred " + - $"{totalBytesDownloadedSoFarVolatile.BytesAsHumanReadable()} of " + - $"{totalBytesToDownload.BytesAsHumanReadable()}."); - } + if (totalBytesDownloadedPercentage % 5 == 0 + || totalReleasesToDownload <= 5) + { + logger?.Info($"Download progress: {totalBytesDownloadedPercentage}% - Transferred " + + $"{totalBytesDownloadedSoFarVolatile.BytesAsHumanReadable()} of " + + $"{totalBytesToDownload.BytesAsHumanReadable()}."); } - }; - - var success = await TryDownloadAsync(packagesDirectory, snapAppChannelReleases, - snapRelease, packageSource, thisProgressSource, logger, cancellationToken); + } + }; - restoreSummary.DownloadSummary.Add(new SnapPackageManagerReleaseStatus(snapRelease, success)); - }, downloadConcurrency); + var success = await TryDownloadAsync(packagesDirectory, snapAppChannelReleases, + snapRelease, packageSource, thisProgressSource, logger, cancellationToken); - var downloadedReleases = restoreSummary.DownloadSummary.Where(x => x.Ok).Select(x => x.SnapRelease).ToList(); - var allReleasesDownloaded = releasesToDownload.Count == downloadedReleases.Count; - if (!allReleasesDownloaded) - { - logger?.Error("Error downloading one or multiple packages. " + - $"Downloaded {downloadedReleases.Count} of {releasesToDownload.Count}. " + - $"Operation completed in {stopwatch.Elapsed.TotalSeconds:0.0}s."); - return false; - } + restoreSummary.DownloadSummary.Add(new SnapPackageManagerReleaseStatus(snapRelease, success)); + }, downloadConcurrency); - logger?.Info($"Downloaded {downloadedReleases.Count} packages in {stopwatch.Elapsed.TotalSeconds:0.0}s."); - return true; + var downloadedReleases = restoreSummary.DownloadSummary.Where(x => x.Ok).Select(x => x.SnapRelease).ToList(); + var allReleasesDownloaded = releasesToDownload.Count == downloadedReleases.Count; + if (!allReleasesDownloaded) + { + logger?.Error("Error downloading one or multiple packages. " + + $"Downloaded {downloadedReleases.Count} of {releasesToDownload.Count}. " + + $"Operation completed in {stopwatch.Elapsed.TotalSeconds:0.0}s."); + return false; } - async Task ReassembleAsync() - { - SnapAppChannelReleases releasesToReassemble; + logger?.Info($"Downloaded {downloadedReleases.Count} packages in {stopwatch.Elapsed.TotalSeconds:0.0}s."); + return true; + } - switch (restoreType) - { - case SnapPackageManagerRestoreType.Default: - var snapReleases = new List(); + async Task ReassembleAsync() + { + SnapAppChannelReleases releasesToReassemble; - var mostRecentDeltaSnapRelease = snapAppChannelReleases.GetDeltaReleases().LastOrDefault(); - if (restoreSummary.ChecksumSummary.Any(x => !x.Ok) - && mostRecentDeltaSnapRelease != null) - { - var mostRecentFullSnapRelease = restoreSummary.ChecksumSummary.SingleOrDefault(x => - x.Ok - && x.SnapRelease.IsFull - && x.SnapRelease.Version == mostRecentDeltaSnapRelease.Version - ); + switch (restoreType) + { + case SnapPackageManagerRestoreType.Default: + var snapReleases = new List(); + + var mostRecentDeltaSnapRelease = snapAppChannelReleases.GetDeltaReleases().LastOrDefault(); + if (restoreSummary.ChecksumSummary.Any(x => !x.Ok) + && mostRecentDeltaSnapRelease != null) + { + var mostRecentFullSnapRelease = restoreSummary.ChecksumSummary.SingleOrDefault(x => + x.Ok + && x.SnapRelease.IsFull + && x.SnapRelease.Version == mostRecentDeltaSnapRelease.Version + ); - if (mostRecentFullSnapRelease == null) - { - snapReleases.Add(mostRecentDeltaSnapRelease); - } + if (mostRecentFullSnapRelease == null) + { + snapReleases.Add(mostRecentDeltaSnapRelease); } + } - releasesToReassemble = new SnapAppChannelReleases(snapAppChannelReleases, snapReleases); - break; - case SnapPackageManagerRestoreType.Pack: - // Noop - releasesToReassemble = new SnapAppChannelReleases(snapAppChannelReleases, new List()); - break; - default: - throw new NotSupportedException(restoreType.ToString()); - } + releasesToReassemble = new SnapAppChannelReleases(snapAppChannelReleases, snapReleases); + break; + case SnapPackageManagerRestoreType.Pack: + // Noop + releasesToReassemble = new SnapAppChannelReleases(snapAppChannelReleases, new List()); + break; + default: + throw new NotSupportedException(restoreType.ToString()); + } - if (!releasesToReassemble.Any()) - { - return true; - } + if (!releasesToReassemble.Any()) + { + return true; + } - logger?.Info($"Reassembling {releasesToReassemble.Count()} packages: {string.Join(", ", releasesToReassemble.Select(x => x.BuildNugetFullFilename()))}."); + logger?.Info($"Reassembling {releasesToReassemble.Count()} packages: {string.Join(", ", releasesToReassemble.Select(x => x.BuildNugetFullFilename()))}."); - var success = true; + var success = true; - var genesisSnapRelease = snapAppChannelReleases.GetGenesisRelease(); - var newestVersion = releasesToReassemble.Max(x => x.Version); + var genesisSnapRelease = snapAppChannelReleases.GetGenesisRelease(); + var newestVersion = releasesToReassemble.Max(x => x.Version); - long releasesReassembled = default; - var totalFilesToRestore = genesisSnapRelease.Files.Count + - snapAppChannelReleases - .Where(x => x.Version > genesisSnapRelease.Version && x.Version <= newestVersion) - .Sum(x => x.New.Count + x.Modified.Count); - var totalFilesRestored = 0L; - int totalRestorePercentage; + long releasesReassembled = default; + var totalFilesToRestore = genesisSnapRelease.Files.Count + + snapAppChannelReleases + .Where(x => x.Version > genesisSnapRelease.Version && x.Version <= newestVersion) + .Sum(x => x.New.Count + x.Modified.Count); + var totalFilesRestored = 0L; + int totalRestorePercentage; - var compoundProgressSource = new RebuildPackageProgressSource(); - compoundProgressSource.Progress += tuple => - { - if(tuple.filesRestored == 0) return; - totalRestorePercentage = (int) Math.Ceiling((double) ++totalFilesRestored / totalFilesToRestore * 100); - progressSource?.RaiseRestoreProgress(totalRestorePercentage, totalFilesRestored, totalFilesToRestore); - }; + var compoundProgressSource = new RebuildPackageProgressSource(); + compoundProgressSource.Progress += tuple => + { + if(tuple.filesRestored == 0) return; + totalRestorePercentage = (int) Math.Ceiling((double) ++totalFilesRestored / totalFilesToRestore * 100); + progressSource?.RaiseRestoreProgress(totalRestorePercentage, totalFilesRestored, totalFilesToRestore); + }; - progressSource?.RaiseRestoreProgress(0, 0, totalFilesToRestore); + progressSource?.RaiseRestoreProgress(0, 0, totalFilesToRestore); - await releasesToReassemble.ForEachAsync(async x => + await releasesToReassemble.ForEachAsync(async x => + { + try { - try - { - var ( _, fullSnapRelease) = - await _snapPack.RebuildPackageAsync(packagesDirectory, snapAppChannelReleases, x, compoundProgressSource, _filesystem, cancellationToken); + var ( _, fullSnapRelease) = + await _snapPack.RebuildPackageAsync(packagesDirectory, snapAppChannelReleases, x, compoundProgressSource, _filesystem, cancellationToken); - var releasesReassembledSoFarVolatile = Interlocked.Increment(ref releasesReassembled); - restoreSummary.ReassembleSummary.Add(new SnapPackageManagerReleaseStatus(fullSnapRelease, true)); + var releasesReassembledSoFarVolatile = Interlocked.Increment(ref releasesReassembled); + restoreSummary.ReassembleSummary.Add(new SnapPackageManagerReleaseStatus(fullSnapRelease, true)); - logger?.Debug($"Successfully restored {releasesReassembledSoFarVolatile} of {releasesToReassemble.Count()}."); - } - catch (Exception e) - { - restoreSummary.ReassembleSummary.Add(new SnapPackageManagerReleaseStatus(x, false)); - logger?.ErrorException($"Error reassembling full nupkg: {x.BuildNugetFullFilename()}", e); - success = false; - } - }, restoreConcurrency); + logger?.Debug($"Successfully restored {releasesReassembledSoFarVolatile} of {releasesToReassemble.Count()}."); + } + catch (Exception e) + { + restoreSummary.ReassembleSummary.Add(new SnapPackageManagerReleaseStatus(x, false)); + logger?.ErrorException($"Error reassembling full nupkg: {x.BuildNugetFullFilename()}", e); + success = false; + } + }, restoreConcurrency); - logger?.Info($"Reassembled {releasesToReassemble.Count()} packages in {stopwatch.Elapsed.TotalSeconds:0.0}s."); + logger?.Info($"Reassembled {releasesToReassemble.Count()} packages in {stopwatch.Elapsed.TotalSeconds:0.0}s."); - return success; - } + return success; } + } - async Task TryDownloadAsync([JetBrains.Annotations.NotNull] string packagesDirectory, [JetBrains.Annotations.NotNull] ISnapAppChannelReleases snapAppChannelReleases, [JetBrains.Annotations.NotNull] SnapRelease snapRelease, - [JetBrains.Annotations.NotNull] PackageSource packageSource, INugetServiceProgressSource progressSource, ILog logger = null, - CancellationToken cancellationToken = default) - { - if (snapAppChannelReleases == null) throw new ArgumentNullException(nameof(snapAppChannelReleases)); - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - if (packagesDirectory == null) throw new ArgumentNullException(nameof(packagesDirectory)); + async Task TryDownloadAsync([JetBrains.Annotations.NotNull] string packagesDirectory, [JetBrains.Annotations.NotNull] ISnapAppChannelReleases snapAppChannelReleases, [JetBrains.Annotations.NotNull] SnapRelease snapRelease, + [JetBrains.Annotations.NotNull] PackageSource packageSource, INugetServiceProgressSource progressSource, ILog logger = null, + CancellationToken cancellationToken = default) + { + if (snapAppChannelReleases == null) throw new ArgumentNullException(nameof(snapAppChannelReleases)); + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + if (packagesDirectory == null) throw new ArgumentNullException(nameof(packagesDirectory)); - var restoreStopwatch = new Stopwatch(); - restoreStopwatch.Restart(); + var restoreStopwatch = new Stopwatch(); + restoreStopwatch.Restart(); - var nupkgFileSize = snapRelease.IsFull ? snapRelease.FullFilesize : snapRelease.DeltaFilesize; - var nupkgChecksum = snapRelease.IsFull ? snapRelease.FullSha256Checksum : snapRelease.DeltaSha256Checksum; + var nupkgFileSize = snapRelease.IsFull ? snapRelease.FullFilesize : snapRelease.DeltaFilesize; + var nupkgChecksum = snapRelease.IsFull ? snapRelease.FullSha256Checksum : snapRelease.DeltaSha256Checksum; - logger?.Debug($"Downloading nupkg: {snapRelease.Filename}. " + - $"File size: {nupkgFileSize.BytesAsHumanReadable()}. " + - $"Package source: {packageSource.Name}."); + logger?.Debug($"Downloading nupkg: {snapRelease.Filename}. " + + $"File size: {nupkgFileSize.BytesAsHumanReadable()}. " + + $"Package source: {packageSource.Name}."); - try - { - var downloadContext = new DownloadContext(snapRelease); + try + { + var downloadContext = new DownloadContext(snapRelease); - var downloadResult = await _nugetService - .DownloadAsyncWithProgressAsync(packageSource, downloadContext, progressSource, cancellationToken); + var downloadResult = await _nugetService + .DownloadAsyncWithProgressAsync(packageSource, downloadContext, progressSource, cancellationToken); - using (downloadResult) + using (downloadResult) + { + if (!downloadResult.SuccessSafe()) { - if (!downloadResult.SuccessSafe()) - { - logger?.Error($"Unknown error downloading nupkg: {snapRelease.Filename}."); - return false; - } + logger?.Error($"Unknown error downloading nupkg: {snapRelease.Filename}."); + return false; + } - logger?.Debug($"Downloaded nupkg: {snapRelease.Filename}. Flushing to disk."); + logger?.Debug($"Downloaded nupkg: {snapRelease.Filename}. Flushing to disk."); - var dstFilename = _filesystem.PathCombine(packagesDirectory, snapRelease.Filename); - await _filesystem.FileWriteAsync(downloadResult.PackageStream, dstFilename, cancellationToken); + var dstFilename = _filesystem.PathCombine(packagesDirectory, snapRelease.Filename); + await _filesystem.FileWriteAsync(downloadResult.PackageStream, dstFilename, cancellationToken); - downloadResult.PackageStream.Seek(0, SeekOrigin.Begin); + downloadResult.PackageStream.Seek(0, SeekOrigin.Begin); - logger?.Debug("Nupkg flushed to disk. Verifying checksum!"); + logger?.Debug("Nupkg flushed to disk. Verifying checksum!"); - using (var packageArchiveReader = new PackageArchiveReader(downloadResult.PackageStream, true)) + using (var packageArchiveReader = new PackageArchiveReader(downloadResult.PackageStream, true)) + { + var downloadChecksum = _snapCryptoProvider.Sha256(snapRelease, packageArchiveReader, _snapPack); + if (downloadChecksum != nupkgChecksum) { - var downloadChecksum = _snapCryptoProvider.Sha256(snapRelease, packageArchiveReader, _snapPack); - if (downloadChecksum != nupkgChecksum) - { - logger?.Error($"Checksum mismatch for downloaded nupkg: {snapRelease.Filename}"); - return false; - } - } + logger?.Error($"Checksum mismatch for downloaded nupkg: {snapRelease.Filename}"); + return false; + } + } - logger?.Debug($"Successfully verified checksum for downloaded nupkg: {snapRelease.Filename}."); + logger?.Debug($"Successfully verified checksum for downloaded nupkg: {snapRelease.Filename}."); - return true; - } - } - catch (Exception e) - { - logger?.ErrorException($"Unknown error downloading nupkg: {snapRelease.Filename}", e); - return false; + return true; } } - - bool TryChecksum([JetBrains.Annotations.NotNull] SnapRelease snapRelease, string nupkgAbsoluteFilename, bool silent = false, ILog logger = null) + catch (Exception e) { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - if (nupkgAbsoluteFilename == null) throw new ArgumentNullException(nameof(nupkgAbsoluteFilename)); + logger?.ErrorException($"Unknown error downloading nupkg: {snapRelease.Filename}", e); + return false; + } + } - try + bool TryChecksum([JetBrains.Annotations.NotNull] SnapRelease snapRelease, string nupkgAbsoluteFilename, bool silent = false, ILog logger = null) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + if (nupkgAbsoluteFilename == null) throw new ArgumentNullException(nameof(nupkgAbsoluteFilename)); + + try + { + var filename = _filesystem.PathGetFileName(nupkgAbsoluteFilename); + if (!_filesystem.FileExists(nupkgAbsoluteFilename)) { - var filename = _filesystem.PathGetFileName(nupkgAbsoluteFilename); - if (!_filesystem.FileExists(nupkgAbsoluteFilename)) - { - logger?.Warn($"Checksum error - File does not exist: {filename}. This is not fatal! Package will be either downloaded from NuGet server or reassembled."); - return false; - } + logger?.Warn($"Checksum error - File does not exist: {filename}. This is not fatal! Package will be either downloaded from NuGet server or reassembled."); + return false; + } - using var packageArchiveReader = new PackageArchiveReader(nupkgAbsoluteFilename); - if (!silent) - { - logger?.Debug($"Starting to checksum: {filename}."); - } + using var packageArchiveReader = new PackageArchiveReader(nupkgAbsoluteFilename); + if (!silent) + { + logger?.Debug($"Starting to checksum: {filename}."); + } - var sha256Checksum = _snapCryptoProvider.Sha256(snapRelease, packageArchiveReader, _snapPack); - if (snapRelease.IsFull) + var sha256Checksum = _snapCryptoProvider.Sha256(snapRelease, packageArchiveReader, _snapPack); + if (snapRelease.IsFull) + { + if (sha256Checksum == snapRelease.FullSha256Checksum) { - if (sha256Checksum == snapRelease.FullSha256Checksum) + if (!silent) { - if (!silent) - { - logger?.Debug($"Checksum success: {filename}."); - } - return true; + logger?.Debug($"Checksum success: {filename}."); } + return true; } - else if (snapRelease.IsDelta) + } + else if (snapRelease.IsDelta) + { + if (sha256Checksum == snapRelease.DeltaSha256Checksum) { - if (sha256Checksum == snapRelease.DeltaSha256Checksum) + if (!silent) { - if (!silent) - { - logger?.Debug($"Checksum success: {filename}."); - } - return true; + logger?.Debug($"Checksum success: {filename}."); } + return true; } - else - { - throw new NotSupportedException($"Unknown package type: {snapRelease.Filename}"); - } - - logger?.Error($"Checksum mismatch: {filename}."); - - return false; } - catch (Exception e) + else { - logger?.ErrorException($"Unknown error checksumming file: {snapRelease.Filename}.", e); + throw new NotSupportedException($"Unknown package type: {snapRelease.Filename}"); } + + logger?.Error($"Checksum mismatch: {filename}."); + return false; } - + catch (Exception e) + { + logger?.ErrorException($"Unknown error checksumming file: {snapRelease.Filename}.", e); + } + return false; } -} + +} \ No newline at end of file diff --git a/src/Snap/Core/SnapProgressSource.cs b/src/Snap/Core/SnapProgressSource.cs index 10abf73b..e84c6668 100644 --- a/src/Snap/Core/SnapProgressSource.cs +++ b/src/Snap/Core/SnapProgressSource.cs @@ -1,34 +1,33 @@ using System; using JetBrains.Annotations; -namespace Snap.Core +namespace Snap.Core; + +public interface ISnapProgressSource { - public interface ISnapProgressSource + Action Progress { get; set; } + void Raise(int i); + void Raise(int i, [NotNull] Action action); +} + +public sealed class SnapProgressSource : ISnapProgressSource +{ + public Action Progress { get; set; } + + public void Raise(int i) { - Action Progress { get; set; } - void Raise(int i); - void Raise(int i, [NotNull] Action action); + Progress?.Invoke(i); } - public sealed class SnapProgressSource : ISnapProgressSource + public void Reset() { - public Action Progress { get; set; } - - public void Raise(int i) - { - Progress?.Invoke(i); - } - - public void Reset() - { - Raise(0); - } + Raise(0); + } - public void Raise(int i, Action action) - { - if (action == null) throw new ArgumentNullException(nameof(action)); - action(); - Raise(i); - } + public void Raise(int i, Action action) + { + if (action == null) throw new ArgumentNullException(nameof(action)); + action(); + Raise(i); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/SnapUpdateManager.cs b/src/Snap/Core/SnapUpdateManager.cs index ed3d15b8..d58430c9 100644 --- a/src/Snap/Core/SnapUpdateManager.cs +++ b/src/Snap/Core/SnapUpdateManager.cs @@ -15,549 +15,548 @@ using Snap.Logging; using Snap.NuGet; -namespace Snap.Core +namespace Snap.Core; + +public interface ISnapUpdateManagerProgressSource { - public interface ISnapUpdateManagerProgressSource - { - Action<(int progressPercentage, long releasesWithChecksumOk, long releasesChecksummed, long releasesToChecksum)> ChecksumProgress { get; set; } + Action<(int progressPercentage, long releasesWithChecksumOk, long releasesChecksummed, long releasesToChecksum)> ChecksumProgress { get; set; } + + Action<(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload)> + DownloadProgress { get; set; } + + Action<(int progressPercentage, long filesRestored, long filesToRestore)> RestoreProgress { get; set; } + Action TotalProgress { get; set; } + + void RaiseChecksumProgress(int progressPercentage, long releasesWithChecksumOk, long releasesChecksummed, long releasesToChecksum); + + void RaiseDownloadProgress(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, + long totalBytesToDownload); - Action<(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload)> - DownloadProgress { get; set; } + void RaiseRestoreProgress(int progressPercentage, long filesRestored, long filesToRestore); + void RaiseTotalProgress(int percentage); +} - Action<(int progressPercentage, long filesRestored, long filesToRestore)> RestoreProgress { get; set; } - Action TotalProgress { get; set; } +public sealed class SnapUpdateManagerProgressSource : ISnapUpdateManagerProgressSource +{ + public Action<(int progressPercentage, long releasesWithChecksumOk, long releasesChecksummed, long releasesToChecksum)> ChecksumProgress { get; set; } - void RaiseChecksumProgress(int progressPercentage, long releasesWithChecksumOk, long releasesChecksummed, long releasesToChecksum); + public Action<(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload)> + DownloadProgress { get; set; } - void RaiseDownloadProgress(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, - long totalBytesToDownload); + public Action<(int progressPercentage, long filesRestored, long filesToRestore)> RestoreProgress { get; set; } + public Action TotalProgress { get; set; } - void RaiseRestoreProgress(int progressPercentage, long filesRestored, long filesToRestore); - void RaiseTotalProgress(int percentage); + public void RaiseChecksumProgress(int progressPercentage, long releasesWithChecksumOk, long releasesChecksummed, long releasesToChecksum) + { + ChecksumProgress?.Invoke((progressPercentage, releasesWithChecksumOk, releasesChecksummed, releasesToChecksum)); } - public sealed class SnapUpdateManagerProgressSource : ISnapUpdateManagerProgressSource + public void RaiseDownloadProgress(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, + long totalBytesToDownload) { - public Action<(int progressPercentage, long releasesWithChecksumOk, long releasesChecksummed, long releasesToChecksum)> ChecksumProgress { get; set; } + DownloadProgress?.Invoke((progressPercentage, releasesDownloaded, releasesToDownload, totalBytesDownloaded, totalBytesToDownload)); + } - public Action<(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, long totalBytesToDownload)> - DownloadProgress { get; set; } + public void RaiseRestoreProgress(int progressPercentage, long filesRestored, long filesToRestore) + { + RestoreProgress?.Invoke((progressPercentage, filesRestored, filesToRestore)); + } - public Action<(int progressPercentage, long filesRestored, long filesToRestore)> RestoreProgress { get; set; } - public Action TotalProgress { get; set; } + public void RaiseTotalProgress(int percentage) + { + TotalProgress?.Invoke(percentage); + } +} - public void RaiseChecksumProgress(int progressPercentage, long releasesWithChecksumOk, long releasesChecksummed, long releasesToChecksum) - { - ChecksumProgress?.Invoke((progressPercentage, releasesWithChecksumOk, releasesChecksummed, releasesToChecksum)); - } +public interface ISnapUpdateManager : IDisposable +{ + int ReleaseRetentionLimit { get; set; } + string ApplicationId { get; set; } + bool SuperVisorAlwaysStartAfterSuccessfullUpdate { get; set; } + Task GetSnapReleasesAsync(CancellationToken cancellationToken); + Task UpdateToLatestReleaseAsync(ISnapUpdateManagerProgressSource progressSource = default, + Action onUpdatesAvailable = null, + Action onBeforeApplyUpdate = null, + Action onAfterApplyUpdate = null, + Action onApplyUpdateException = null, + CancellationToken cancellationToken = default); +} + +public sealed class SnapUpdateManager : ISnapUpdateManager +{ + readonly string _workingDirectory; + readonly string _packagesDirectory; + readonly SnapApp _snapApp; + readonly INugetService _nugetService; + readonly ISnapOs _snapOs; + readonly ISnapInstaller _snapInstaller; + readonly ISnapPack _snapPack; + readonly ISnapAppReader _snapAppReader; + readonly ISnapExtractor _snapExtractor; + readonly ILog _logger; + readonly ISnapCryptoProvider _snapCryptoProvider; + readonly ISnapPackageManager _snapPackageManager; + readonly ISnapAppWriter _snapAppWriter; + readonly ISnapHttpClient _snapHttpClient; + readonly ISnapBinaryPatcher _snapBinaryPatcher; + + /// + /// The number of releases that should be retained after a new updated has been successfully applied. + /// Default value is 1 - Only the previous version will be retained. + /// + public int ReleaseRetentionLimit { get; set; } = 1; + + /// + /// Specify a unique application id that is sent to a remote http server + /// if a snap http feed is used when retrieving nuget credentials. + /// + public string ApplicationId { get; set; } + + /// + /// Start supervisor an update has been successfully applied. You should set this property to true + /// if you are running mission critical software. It's also safe setting this property to true + /// if the supervisor is already running. + /// + public bool SuperVisorAlwaysStartAfterSuccessfullUpdate { get; set; } + + [UsedImplicitly] + public SnapUpdateManager() : this( + Directory.GetParent( + Path.GetDirectoryName(typeof(SnapUpdateManager).Assembly.Location) ?? throw new InvalidOperationException()) + ?.FullName ?? throw new InvalidOperationException()) + { + } - public void RaiseDownloadProgress(int progressPercentage, long releasesDownloaded, long releasesToDownload, long totalBytesDownloaded, - long totalBytesToDownload) + [UsedImplicitly] + internal SnapUpdateManager([NotNull] string workingDirectory, ILog logger = null) : this(workingDirectory, Snapx.Current, logger) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + } + + internal SnapUpdateManager([NotNull] string workingDirectory, [NotNull] SnapApp snapApp, ILog logger = null, INugetService nugetService = null, + ISnapOs snapOs = null, ISnapCryptoProvider snapCryptoProvider = null, ISnapEmbeddedResources snapEmbeddedResources = null, + ISnapAppReader snapAppReader = null, ISnapAppWriter snapAppWriter = null, ISnapPack snapPack = null, ISnapExtractor snapExtractor = null, + ISnapInstaller snapInstaller = null, ISnapPackageManager snapPackageManager = null, + ISnapHttpClient snapHttpClient = null, ISnapBinaryPatcher snapBinaryPatcher = null) + { + _logger = logger ?? LogProvider.For(); + _snapOs = snapOs ?? SnapOs.AnyOs; + _workingDirectory = workingDirectory ?? throw new ArgumentNullException(nameof(workingDirectory)); + _packagesDirectory = _snapOs.Filesystem.PathCombine(_workingDirectory, "packages"); + _snapApp = snapApp ?? throw new ArgumentNullException(nameof(snapApp)); + + _nugetService = nugetService ?? new NugetService(_snapOs.Filesystem, new NugetLogger(_logger)); + _snapCryptoProvider = snapCryptoProvider ?? new SnapCryptoProvider(); + snapEmbeddedResources ??= new SnapEmbeddedResources(); + _snapAppReader = snapAppReader ?? new SnapAppReader(); + _snapAppWriter = snapAppWriter ?? new SnapAppWriter(); + _snapBinaryPatcher = snapBinaryPatcher ?? new SnapBinaryPatcher(); + _snapPack = snapPack ?? new SnapPack(_snapOs.Filesystem, _snapAppReader, _snapAppWriter, + _snapCryptoProvider, snapEmbeddedResources, _snapBinaryPatcher); + _snapExtractor = snapExtractor ?? new SnapExtractor(_snapOs.Filesystem, _snapPack, snapEmbeddedResources); + _snapInstaller = snapInstaller ?? new SnapInstaller(_snapExtractor, _snapPack, _snapOs, snapEmbeddedResources, _snapAppWriter); + _snapHttpClient = snapHttpClient ?? new SnapHttpClient(new HttpClient()); + _snapPackageManager = snapPackageManager ?? new SnapPackageManager( + _snapOs.Filesystem, _snapOs.SpecialFolders, _nugetService, _snapHttpClient, _snapCryptoProvider, + _snapExtractor, _snapAppReader, _snapPack); + + _snapOs.Filesystem.DirectoryCreateIfNotExists(_packagesDirectory); + + _logger.Debug($"Root directory: {_workingDirectory}"); + _logger.Debug($"Packages directory: {_packagesDirectory}"); + _logger.Debug($"Current version: {_snapApp?.Version}"); + _logger.Debug($"Retention limit: {ReleaseRetentionLimit}"); + } + + /// + /// Get all snap releases metadata. Useful if you want to show a version history, releases notes etc. + /// + /// + /// + public async Task GetSnapReleasesAsync(CancellationToken cancellationToken) + { + var (snapAppsReleases, _, releasesMemoryStream) = await _snapPackageManager + .GetSnapsReleasesAsync(_snapApp, _logger, cancellationToken, ApplicationId); + if (releasesMemoryStream != null) { - DownloadProgress?.Invoke((progressPercentage, releasesDownloaded, releasesToDownload, totalBytesDownloaded, totalBytesToDownload)); + await releasesMemoryStream.DisposeAsync(); } + return snapAppsReleases?.GetReleases(_snapApp); + } - public void RaiseRestoreProgress(int progressPercentage, long filesRestored, long filesToRestore) + /// + /// Updates current application to latest upstream release. + /// + /// + /// + /// + /// + /// + /// + /// Returns FALSE if there are no new releases available to install. + public async Task UpdateToLatestReleaseAsync(ISnapUpdateManagerProgressSource snapProgressSource = null, + Action onUpdatesAvailable = null, + Action onBeforeApplyUpdate = null, + Action onAfterApplyUpdate = null, + Action onApplyUpdateException = null, + CancellationToken cancellationToken = default) + { + try { - RestoreProgress?.Invoke((progressPercentage, filesRestored, filesToRestore)); + return await UpdateToLatestReleaseAsyncImpl(snapProgressSource, onUpdatesAvailable, onBeforeApplyUpdate, onAfterApplyUpdate, onApplyUpdateException, cancellationToken); } - - public void RaiseTotalProgress(int percentage) + catch (Exception e) { - TotalProgress?.Invoke(percentage); + _logger.ErrorException("Exception thrown when attempting to update to latest release", e); + return null; } } - public interface ISnapUpdateManager : IDisposable + async Task UpdateToLatestReleaseAsyncImpl(ISnapUpdateManagerProgressSource progressSource = null, + Action onUpdatesAvailable = null, + Action onBeforeApplyUpdate = null, + Action onAfterApplyUpdate = null, + Action onApplyUpdateException = null, + CancellationToken cancellationToken = default) { - int ReleaseRetentionLimit { get; set; } - string ApplicationId { get; set; } - bool SuperVisorAlwaysStartAfterSuccessfullUpdate { get; set; } - Task GetSnapReleasesAsync(CancellationToken cancellationToken); - Task UpdateToLatestReleaseAsync(ISnapUpdateManagerProgressSource progressSource = default, - Action onUpdatesAvailable = null, - Action onBeforeApplyUpdate = null, - Action onAfterApplyUpdate = null, - Action onApplyUpdateException = null, - CancellationToken cancellationToken = default); - } + var sw = new Stopwatch(); + sw.Restart(); - public sealed class SnapUpdateManager : ISnapUpdateManager - { - readonly string _workingDirectory; - readonly string _packagesDirectory; - readonly SnapApp _snapApp; - readonly INugetService _nugetService; - readonly ISnapOs _snapOs; - readonly ISnapInstaller _snapInstaller; - readonly ISnapPack _snapPack; - readonly ISnapAppReader _snapAppReader; - readonly ISnapExtractor _snapExtractor; - readonly ILog _logger; - readonly ISnapCryptoProvider _snapCryptoProvider; - readonly ISnapPackageManager _snapPackageManager; - readonly ISnapAppWriter _snapAppWriter; - readonly ISnapHttpClient _snapHttpClient; - readonly ISnapBinaryPatcher _snapBinaryPatcher; - - /// - /// The number of releases that should be retained after a new updated has been successfully applied. - /// Default value is 1 - Only the previous version will be retained. - /// - public int ReleaseRetentionLimit { get; set; } = 1; - - /// - /// Specify a unique application id that is sent to a remote http server - /// if a snap http feed is used when retrieving nuget credentials. - /// - public string ApplicationId { get; set; } - - /// - /// Start supervisor an update has been successfully applied. You should set this property to true - /// if you are running mission critical software. It's also safe setting this property to true - /// if the supervisor is already running. - /// - public bool SuperVisorAlwaysStartAfterSuccessfullUpdate { get; set; } - - [UsedImplicitly] - public SnapUpdateManager() : this( - Directory.GetParent( - Path.GetDirectoryName(typeof(SnapUpdateManager).Assembly.Location) ?? throw new InvalidOperationException()) - ?.FullName ?? throw new InvalidOperationException()) + var packageSource = await _snapPackageManager.GetPackageSourceAsync(_snapApp, _logger, ApplicationId); + if (packageSource == null) { + _logger.Error("Unknown error resolving update feed."); + return null; } - [UsedImplicitly] - internal SnapUpdateManager([NotNull] string workingDirectory, ILog logger = null) : this(workingDirectory, Snapx.Current, logger) + NuGetPackageSearchMedatadata[] medatadatas; + + try { - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - } + var fullUpstreamId = _snapApp.BuildNugetFullUpstreamId(); + var deltaUpstreamId = _snapApp.BuildNugetDeltaUpstreamId(); - internal SnapUpdateManager([NotNull] string workingDirectory, [NotNull] SnapApp snapApp, ILog logger = null, INugetService nugetService = null, - ISnapOs snapOs = null, ISnapCryptoProvider snapCryptoProvider = null, ISnapEmbeddedResources snapEmbeddedResources = null, - ISnapAppReader snapAppReader = null, ISnapAppWriter snapAppWriter = null, ISnapPack snapPack = null, ISnapExtractor snapExtractor = null, - ISnapInstaller snapInstaller = null, ISnapPackageManager snapPackageManager = null, - ISnapHttpClient snapHttpClient = null, ISnapBinaryPatcher snapBinaryPatcher = null) + medatadatas = await Task.WhenAll( + _nugetService.GetLatestMetadataAsync(fullUpstreamId, packageSource, true, true, cancellationToken), + _nugetService.GetLatestMetadataAsync(deltaUpstreamId, packageSource, true, true, cancellationToken) + ); + } + catch (Exception e) { - _logger = logger ?? LogProvider.For(); - _snapOs = snapOs ?? SnapOs.AnyOs; - _workingDirectory = workingDirectory ?? throw new ArgumentNullException(nameof(workingDirectory)); - _packagesDirectory = _snapOs.Filesystem.PathCombine(_workingDirectory, "packages"); - _snapApp = snapApp ?? throw new ArgumentNullException(nameof(snapApp)); - - _nugetService = nugetService ?? new NugetService(_snapOs.Filesystem, new NugetLogger(_logger)); - _snapCryptoProvider = snapCryptoProvider ?? new SnapCryptoProvider(); - snapEmbeddedResources ??= new SnapEmbeddedResources(); - _snapAppReader = snapAppReader ?? new SnapAppReader(); - _snapAppWriter = snapAppWriter ?? new SnapAppWriter(); - _snapBinaryPatcher = snapBinaryPatcher ?? new SnapBinaryPatcher(); - _snapPack = snapPack ?? new SnapPack(_snapOs.Filesystem, _snapAppReader, _snapAppWriter, - _snapCryptoProvider, snapEmbeddedResources, _snapBinaryPatcher); - _snapExtractor = snapExtractor ?? new SnapExtractor(_snapOs.Filesystem, _snapPack, snapEmbeddedResources); - _snapInstaller = snapInstaller ?? new SnapInstaller(_snapExtractor, _snapPack, _snapOs, snapEmbeddedResources, _snapAppWriter); - _snapHttpClient = snapHttpClient ?? new SnapHttpClient(new HttpClient()); - _snapPackageManager = snapPackageManager ?? new SnapPackageManager( - _snapOs.Filesystem, _snapOs.SpecialFolders, _nugetService, _snapHttpClient, _snapCryptoProvider, - _snapExtractor, _snapAppReader, _snapPack); - - _snapOs.Filesystem.DirectoryCreateIfNotExists(_packagesDirectory); - - _logger.Debug($"Root directory: {_workingDirectory}"); - _logger.Debug($"Packages directory: {_packagesDirectory}"); - _logger.Debug($"Current version: {_snapApp?.Version}"); - _logger.Debug($"Retention limit: {ReleaseRetentionLimit}"); + _logger.ErrorException("Unknown error retrieving full / delta metadatas.", e); + return null; } - /// - /// Get all snap releases metadata. Useful if you want to show a version history, releases notes etc. - /// - /// - /// - public async Task GetSnapReleasesAsync(CancellationToken cancellationToken) + var metadatasThatAreNewerThanCurrentVersion = medatadatas.Where(x => x?.Identity?.Version > _snapApp.Version).Select(x => x.Identity).ToList(); + if (!metadatasThatAreNewerThanCurrentVersion.Any()) { - var (snapAppsReleases, _, releasesMemoryStream) = await _snapPackageManager - .GetSnapsReleasesAsync(_snapApp, _logger, cancellationToken, ApplicationId); - if (releasesMemoryStream != null) - { - await releasesMemoryStream.DisposeAsync(); - } - return snapAppsReleases?.GetReleases(_snapApp); + return null; } - /// - /// Updates current application to latest upstream release. - /// - /// - /// - /// - /// - /// - /// - /// Returns FALSE if there are no new releases available to install. - public async Task UpdateToLatestReleaseAsync(ISnapUpdateManagerProgressSource snapProgressSource = null, - Action onUpdatesAvailable = null, - Action onBeforeApplyUpdate = null, - Action onAfterApplyUpdate = null, - Action onApplyUpdateException = null, - CancellationToken cancellationToken = default) + var (snapAppsReleases, _, releasesMemoryStream) = await _snapPackageManager.GetSnapsReleasesAsync(_snapApp, _logger, cancellationToken); + if(releasesMemoryStream != null) { - try - { - return await UpdateToLatestReleaseAsyncImpl(snapProgressSource, onUpdatesAvailable, onBeforeApplyUpdate, onAfterApplyUpdate, onApplyUpdateException, cancellationToken); - } - catch (Exception e) - { - _logger.ErrorException("Exception thrown when attempting to update to latest release", e); - return null; - } + await releasesMemoryStream.DisposeAsync(); } - - async Task UpdateToLatestReleaseAsyncImpl(ISnapUpdateManagerProgressSource progressSource = null, - Action onUpdatesAvailable = null, - Action onBeforeApplyUpdate = null, - Action onAfterApplyUpdate = null, - Action onApplyUpdateException = null, - CancellationToken cancellationToken = default) + if (snapAppsReleases == null) { - var sw = new Stopwatch(); - sw.Restart(); - - var packageSource = await _snapPackageManager.GetPackageSourceAsync(_snapApp, _logger, ApplicationId); - if (packageSource == null) - { - _logger.Error("Unknown error resolving update feed."); - return null; - } - - NuGetPackageSearchMedatadata[] medatadatas; - - try - { - var fullUpstreamId = _snapApp.BuildNugetFullUpstreamId(); - var deltaUpstreamId = _snapApp.BuildNugetDeltaUpstreamId(); - - medatadatas = await Task.WhenAll( - _nugetService.GetLatestMetadataAsync(fullUpstreamId, packageSource, true, true, cancellationToken), - _nugetService.GetLatestMetadataAsync(deltaUpstreamId, packageSource, true, true, cancellationToken) - ); - } - catch (Exception e) - { - _logger.ErrorException("Unknown error retrieving full / delta metadatas.", e); - return null; - } - - var metadatasThatAreNewerThanCurrentVersion = medatadatas.Where(x => x?.Identity?.Version > _snapApp.Version).Select(x => x.Identity).ToList(); - if (!metadatasThatAreNewerThanCurrentVersion.Any()) - { - return null; - } - - var (snapAppsReleases, _, releasesMemoryStream) = await _snapPackageManager.GetSnapsReleasesAsync(_snapApp, _logger, cancellationToken); - if(releasesMemoryStream != null) - { - await releasesMemoryStream.DisposeAsync(); - } - if (snapAppsReleases == null) - { - return null; - } + return null; + } - var snapChannel = _snapApp.GetCurrentChannelOrThrow(); + var snapChannel = _snapApp.GetCurrentChannelOrThrow(); - _logger.Debug($"Channel: {snapChannel.Name}"); + _logger.Debug($"Channel: {snapChannel.Name}"); - var snapAppChannelReleases = snapAppsReleases.GetReleases(_snapApp, snapChannel); + var snapAppChannelReleases = snapAppsReleases.GetReleases(_snapApp, snapChannel); - var snapReleases = snapAppChannelReleases.GetReleasesNewerThan(_snapApp.Version).ToList(); - if (!snapReleases.Any()) - { - _logger.Warn($"Unable to find any releases newer than {_snapApp.Version}. " + - $"Is your nuget server caching responses? Metadatas: {string.Join(",", metadatasThatAreNewerThanCurrentVersion)}"); - return null; - } + var snapReleases = snapAppChannelReleases.GetReleasesNewerThan(_snapApp.Version).ToList(); + if (!snapReleases.Any()) + { + _logger.Warn($"Unable to find any releases newer than {_snapApp.Version}. " + + $"Is your nuget server caching responses? Metadatas: {string.Join(",", metadatasThatAreNewerThanCurrentVersion)}"); + return null; + } - onUpdatesAvailable?.Invoke(snapAppChannelReleases); + onUpdatesAvailable?.Invoke(snapAppChannelReleases); - _logger.Info($"Found new releases({snapReleases.Count}): {string.Join(",", snapReleases.Select(x => x.Filename))}"); + _logger.Info($"Found new releases({snapReleases.Count}): {string.Join(",", snapReleases.Select(x => x.Filename))}"); - progressSource?.RaiseTotalProgress(0); + progressSource?.RaiseTotalProgress(0); - // Todo: Refactor - Delete all nupkgs that are no longer required - // https://github.com/youpark/snapx/issues/9 + // Todo: Refactor - Delete all nupkgs that are no longer required + // https://github.com/youpark/snapx/issues/9 - SnapRelease snapGenisisRelease; - if (snapAppChannelReleases.Count() == 1) + SnapRelease snapGenisisRelease; + if (snapAppChannelReleases.Count() == 1) + { + snapGenisisRelease = snapReleases.Single(); + if (snapGenisisRelease.IsGenesis && snapGenisisRelease.Gc) { - snapGenisisRelease = snapReleases.Single(); - if (snapGenisisRelease.IsGenesis && snapGenisisRelease.Gc) + try { - try + var nugetPackages = _snapOs.Filesystem + .DirectoryGetAllFiles(_packagesDirectory) + .Where(x => + !string.Equals(snapGenisisRelease.Filename, x, StringComparison.OrdinalIgnoreCase) + && x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + if (nugetPackages.Count > 0) { - var nugetPackages = _snapOs.Filesystem - .DirectoryGetAllFiles(_packagesDirectory) - .Where(x => - !string.Equals(snapGenisisRelease.Filename, x, StringComparison.OrdinalIgnoreCase) - && x.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) - .ToList(); - - if (nugetPackages.Count > 0) - { - _logger.Debug($"Garbage collecting (removing) previous nuget packages. Packages that will be removed: {nugetPackages.Count}."); + _logger.Debug($"Garbage collecting (removing) previous nuget packages. Packages that will be removed: {nugetPackages.Count}."); - foreach (var nugetPackageAbsolutePath in nugetPackages) + foreach (var nugetPackageAbsolutePath in nugetPackages) + { + try + { + SnapUtility.Retry(() => _snapOs.Filesystem.FileDelete(nugetPackageAbsolutePath), 3); + } + catch (Exception e) { - try - { - SnapUtility.Retry(() => _snapOs.Filesystem.FileDelete(nugetPackageAbsolutePath), 3); - } - catch (Exception e) - { - _logger.ErrorException($"Failed to delete: {nugetPackageAbsolutePath}", e); - continue; - } + _logger.ErrorException($"Failed to delete: {nugetPackageAbsolutePath}", e); + continue; + } - _logger.Debug($"Deleted nuget package: {nugetPackageAbsolutePath}."); - } - } - } - catch (Exception e) - { - _logger.ErrorException($"Unknown error listing files in packages directory: {_packagesDirectory}.", e); + _logger.Debug($"Deleted nuget package: {nugetPackageAbsolutePath}."); + } } } + catch (Exception e) + { + _logger.ErrorException($"Unknown error listing files in packages directory: {_packagesDirectory}.", e); + } } + } - var snapPackageManagerProgressSource = new SnapPackageManagerProgressSource - { - ChecksumProgress = x => - progressSource?.RaiseChecksumProgress( - x.progressPercentage, - x.releasesOk, - x.releasesChecksummed, - x.releasesToChecksum - ), - DownloadProgress = x => - progressSource?.RaiseDownloadProgress( - x.progressPercentage, - x.releasesDownloaded, - x.releasesToDownload, - x.totalBytesDownloaded, - x.totalBytesToDownload - ), - RestoreProgress = x => - progressSource?.RaiseRestoreProgress( - x.progressPercentage, - x.filesRestored, - x.filesToRestore + var snapPackageManagerProgressSource = new SnapPackageManagerProgressSource + { + ChecksumProgress = x => + progressSource?.RaiseChecksumProgress( + x.progressPercentage, + x.releasesOk, + x.releasesChecksummed, + x.releasesToChecksum + ), + DownloadProgress = x => + progressSource?.RaiseDownloadProgress( + x.progressPercentage, + x.releasesDownloaded, + x.releasesToDownload, + x.totalBytesDownloaded, + x.totalBytesToDownload + ), + RestoreProgress = x => + progressSource?.RaiseRestoreProgress( + x.progressPercentage, + x.filesRestored, + x.filesToRestore ) - }; + }; - var restoreSummary = await _snapPackageManager.RestoreAsync(_packagesDirectory, snapAppChannelReleases, - packageSource, SnapPackageManagerRestoreType.Default, snapPackageManagerProgressSource, _logger, cancellationToken, - 1, 2, 1); - if (!restoreSummary.Success) - { - _logger.Error("Unknown error restoring nuget packages."); - return null; - } + var restoreSummary = await _snapPackageManager.RestoreAsync(_packagesDirectory, snapAppChannelReleases, + packageSource, SnapPackageManagerRestoreType.Default, snapPackageManagerProgressSource, _logger, cancellationToken, + 1, 2, 1); + if (!restoreSummary.Success) + { + _logger.Error("Unknown error restoring nuget packages."); + return null; + } - progressSource?.RaiseTotalProgress(50); + progressSource?.RaiseTotalProgress(50); - var snapReleaseToInstall = snapAppChannelReleases.GetMostRecentRelease(); + var snapReleaseToInstall = snapAppChannelReleases.GetMostRecentRelease(); - if (!snapReleaseToInstall.IsFull) - { - // A delta package always has a corresponding full package after restore. - snapReleaseToInstall = snapReleaseToInstall.AsFullRelease(false); - } + if (!snapReleaseToInstall.IsFull) + { + // A delta package always has a corresponding full package after restore. + snapReleaseToInstall = snapReleaseToInstall.AsFullRelease(false); + } - var nupkgToInstallAbsolutePath = _snapOs.Filesystem.PathCombine(_packagesDirectory, snapReleaseToInstall.Filename); - var updateInstallAbsolutePath = _snapInstaller.GetApplicationDirectory(_workingDirectory, snapReleaseToInstall); + var nupkgToInstallAbsolutePath = _snapOs.Filesystem.PathCombine(_packagesDirectory, snapReleaseToInstall.Filename); + var updateInstallAbsolutePath = _snapInstaller.GetApplicationDirectory(_workingDirectory, snapReleaseToInstall); - _logger.Info($"Installing update {nupkgToInstallAbsolutePath} to application directory: {updateInstallAbsolutePath}"); + _logger.Info($"Installing update {nupkgToInstallAbsolutePath} to application directory: {updateInstallAbsolutePath}"); - if (!_snapOs.Filesystem.FileExists(nupkgToInstallAbsolutePath)) - { - _logger.Error($"Unable to find full nupkg: {nupkgToInstallAbsolutePath}."); - return null; - } + if (!_snapOs.Filesystem.FileExists(nupkgToInstallAbsolutePath)) + { + _logger.Error($"Unable to find full nupkg: {nupkgToInstallAbsolutePath}."); + return null; + } - progressSource?.RaiseTotalProgress(60); + progressSource?.RaiseTotalProgress(60); - SnapApp updatedSnapApp = null; + SnapApp updatedSnapApp = null; - _snapApp.GetCoreRunExecutableFullPath(_snapOs.Filesystem, _workingDirectory, out var superVisorAbsolutePath); + _snapApp.GetCoreRunExecutableFullPath(_snapOs.Filesystem, _workingDirectory, out var superVisorAbsolutePath); - var superVisorBackupAbsolutePath = superVisorAbsolutePath + ".bak"; - var superVisorRestartArguments = Snapx.SupervisorProcessRestartArguments; - var backupSuperVisor = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + var superVisorBackupAbsolutePath = superVisorAbsolutePath + ".bak"; + var superVisorRestartArguments = Snapx.SupervisorProcessRestartArguments; + var backupSuperVisor = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - try - { - var superVisorStopped = Snapx.StopSupervisor(); + try + { + var superVisorStopped = Snapx.StopSupervisor(); - _logger.Debug($"Supervisor stopped: {superVisorStopped}."); + _logger.Debug($"Supervisor stopped: {superVisorStopped}."); - // Prevent "The process cannot access the file because it is being used by another process." exception on Windows - // if the supervisor is started during update. - if (backupSuperVisor) + // Prevent "The process cannot access the file because it is being used by another process." exception on Windows + // if the supervisor is started during update. + if (backupSuperVisor) + { + _logger.Warn("This OS requires us to move supervisor to a backup location in order to prevent it from being started during update." + + $"Current path: {superVisorAbsolutePath}. " + + $"Destination path: {superVisorBackupAbsolutePath}. "); + var backupSupervisorSuccess = _snapOs.Filesystem.TryFileMove(superVisorAbsolutePath, superVisorBackupAbsolutePath, beforeMoveAction: () => { - _logger.Warn("This OS requires us to move supervisor to a backup location in order to prevent it from being started during update." + - $"Current path: {superVisorAbsolutePath}. " + - $"Destination path: {superVisorBackupAbsolutePath}. "); - var backupSupervisorSuccess = _snapOs.Filesystem.TryFileMove(superVisorAbsolutePath, superVisorBackupAbsolutePath, beforeMoveAction: () => - { - Snapx.StopSupervisor(); - }); - _logger.Warn($"Supervisor backed up: {backupSupervisorSuccess}."); - } + Snapx.StopSupervisor(); + }); + _logger.Warn($"Supervisor backed up: {backupSupervisorSuccess}."); + } - onBeforeApplyUpdate?.Invoke(snapReleaseToInstall); + onBeforeApplyUpdate?.Invoke(snapReleaseToInstall); - updatedSnapApp = await _snapInstaller.UpdateAsync( - _workingDirectory, snapReleaseToInstall, snapChannel, - logger: _logger, cancellationToken: cancellationToken); - if (updatedSnapApp == null) - { - throw new Exception($"{nameof(updatedSnapApp)} was null after attempting to install full nupkg: {nupkgToInstallAbsolutePath}"); - } + updatedSnapApp = await _snapInstaller.UpdateAsync( + _workingDirectory, snapReleaseToInstall, snapChannel, + logger: _logger, cancellationToken: cancellationToken); + if (updatedSnapApp == null) + { + throw new Exception($"{nameof(updatedSnapApp)} was null after attempting to install full nupkg: {nupkgToInstallAbsolutePath}"); + } - onAfterApplyUpdate?.Invoke(snapReleaseToInstall); + onAfterApplyUpdate?.Invoke(snapReleaseToInstall); - if (superVisorStopped || SuperVisorAlwaysStartAfterSuccessfullUpdate) - { - var supervisorStarted = Snapx.StartSupervisor(superVisorRestartArguments); - _logger.Debug($"Supervisor started: {supervisorStarted}."); - } + if (superVisorStopped || SuperVisorAlwaysStartAfterSuccessfullUpdate) + { + var supervisorStarted = Snapx.StartSupervisor(superVisorRestartArguments); + _logger.Debug($"Supervisor started: {supervisorStarted}."); + } - if (!updatedSnapApp.IsGenesis) - { - _snapOs.Filesystem.FileDelete(nupkgToInstallAbsolutePath); - _logger.Debug($"Deleted nupkg: {nupkgToInstallAbsolutePath}."); - } - else - { - // Genisis nupkg must be retained so we don't have to download it again - // when a new delta release is available. This should only happen if all releases has - // been garbage collected (removed). - _logger.Debug($"Retaining genesis nupkg: {nupkgToInstallAbsolutePath}."); - } + if (!updatedSnapApp.IsGenesis) + { + _snapOs.Filesystem.FileDelete(nupkgToInstallAbsolutePath); + _logger.Debug($"Deleted nupkg: {nupkgToInstallAbsolutePath}."); + } + else + { + // Genisis nupkg must be retained so we don't have to download it again + // when a new delta release is available. This should only happen if all releases has + // been garbage collected (removed). + _logger.Debug($"Retaining genesis nupkg: {nupkgToInstallAbsolutePath}."); + } - var deletableDirectories = _snapOs.Filesystem - .EnumerateDirectories(_workingDirectory) - .Select(x => + var deletableDirectories = _snapOs.Filesystem + .EnumerateDirectories(_workingDirectory) + .Select(x => + { + if (!x.Contains("app-", StringComparison.OrdinalIgnoreCase)) { - if (!x.Contains("app-", StringComparison.OrdinalIgnoreCase)) - { - return (null, null); - } + return (null, null); + } - var appDirIndexPosition = x.LastIndexOf("app-", StringComparison.OrdinalIgnoreCase); - _ = SemanticVersion.TryParse(x[(appDirIndexPosition + 4)..], out var semanticVersion); + var appDirIndexPosition = x.LastIndexOf("app-", StringComparison.OrdinalIgnoreCase); + _ = SemanticVersion.TryParse(x[(appDirIndexPosition + 4)..], out var semanticVersion); - return (absolutePath: x, version: semanticVersion); - }) - .Where(x => x.version != null && x.version != updatedSnapApp.Version) - .OrderBy(x => x.version) - .ToList(); + return (absolutePath: x, version: semanticVersion); + }) + .Where(x => x.version != null && x.version != updatedSnapApp.Version) + .OrderBy(x => x.version) + .ToList(); - const int deleteRetries = 3; + const int deleteRetries = 3; - if (ReleaseRetentionLimit >= 1 - && deletableDirectories.Count > ReleaseRetentionLimit) - { - var directoriesToDelete = deletableDirectories.Count - ReleaseRetentionLimit; + if (ReleaseRetentionLimit >= 1 + && deletableDirectories.Count > ReleaseRetentionLimit) + { + var directoriesToDelete = deletableDirectories.Count - ReleaseRetentionLimit; - _logger.Debug($"Exceeded application directories retention limit: {ReleaseRetentionLimit}. " + - $"Number of directories that will be deleted: {directoriesToDelete}."); + _logger.Debug($"Exceeded application directories retention limit: {ReleaseRetentionLimit}. " + + $"Number of directories that will be deleted: {directoriesToDelete}."); - for (var i = 0; i < directoriesToDelete; i++) - { - var (directoryAbsolutePath, version) = deletableDirectories[i]; + for (var i = 0; i < directoriesToDelete; i++) + { + var (directoryAbsolutePath, version) = deletableDirectories[i]; - _logger.Debug($"Deleting old application version: {version}."); + _logger.Debug($"Deleting old application version: {version}."); - await DeleteDirectorySafeAsync(directoryAbsolutePath, deleteRetries); - } - } - - if (backupSuperVisor) - { - _logger.Warn($"Removing supervisor backup: {superVisorBackupAbsolutePath}."); - var removeSupervisorBackupSuccess = _snapOs.Filesystem.FileDeleteWithRetries(superVisorBackupAbsolutePath, true); - _logger.Warn($"Supervisor backup deleted: {removeSupervisorBackupSuccess}."); - backupSuperVisor = false; - } + await DeleteDirectorySafeAsync(directoryAbsolutePath, deleteRetries); + } + } + if (backupSuperVisor) + { + _logger.Warn($"Removing supervisor backup: {superVisorBackupAbsolutePath}."); + var removeSupervisorBackupSuccess = _snapOs.Filesystem.FileDeleteWithRetries(superVisorBackupAbsolutePath, true); + _logger.Warn($"Supervisor backup deleted: {removeSupervisorBackupSuccess}."); + backupSuperVisor = false; } - catch (Exception e) + + } + catch (Exception e) + { + _logger.ErrorException($"Exception thrown error updating application. Filename: {nupkgToInstallAbsolutePath}.", e); + + if (updatedSnapApp == null) { - _logger.ErrorException($"Exception thrown error updating application. Filename: {nupkgToInstallAbsolutePath}.", e); + onApplyUpdateException?.Invoke(snapReleaseToInstall, e); - if (updatedSnapApp == null) + if (backupSuperVisor) { - onApplyUpdateException?.Invoke(snapReleaseToInstall, e); - - if (backupSuperVisor) - { - _logger.Warn("Restoring supervisor because of failed update. " + - $"Backup path: {superVisorBackupAbsolutePath}. " + - $"Destination path: {superVisorAbsolutePath}."); + _logger.Warn("Restoring supervisor because of failed update. " + + $"Backup path: {superVisorBackupAbsolutePath}. " + + $"Destination path: {superVisorAbsolutePath}."); - var backupRestoreSupervisorSuccess = _snapOs.Filesystem.TryFileMove(superVisorBackupAbsolutePath, superVisorAbsolutePath); - _logger.Warn($"Supervisor backup restored: {backupRestoreSupervisorSuccess}"); - } + var backupRestoreSupervisorSuccess = _snapOs.Filesystem.TryFileMove(superVisorBackupAbsolutePath, superVisorAbsolutePath); + _logger.Warn($"Supervisor backup restored: {backupRestoreSupervisorSuccess}"); + } - _logger.Warn($"Attempting to delete to delete failed application update directory: {updateInstallAbsolutePath}."); + _logger.Warn($"Attempting to delete to delete failed application update directory: {updateInstallAbsolutePath}."); - var success = await DeleteDirectorySafeAsync(updateInstallAbsolutePath, 3); + var success = await DeleteDirectorySafeAsync(updateInstallAbsolutePath, 3); - _logger.Warn($"Failed application update directory deleted: {success}."); + _logger.Warn($"Failed application update directory deleted: {success}."); - return null; - } + return null; } + } - progressSource?.RaiseTotalProgress(100); + progressSource?.RaiseTotalProgress(100); - _logger.Info($"Successfully updated to {updatedSnapApp.Version}. Update completed in {sw.Elapsed.TotalSeconds:F1}s."); + _logger.Info($"Successfully updated to {updatedSnapApp.Version}. Update completed in {sw.Elapsed.TotalSeconds:F1}s."); - return new SnapApp(updatedSnapApp); - } + return new SnapApp(updatedSnapApp); + } - public void Dispose() - { - } + public void Dispose() + { + } - async Task DeleteDirectorySafeAsync([NotNull] string directoryAbsolutePath, int deleteRetries) - { - if (directoryAbsolutePath == null) throw new ArgumentNullException(nameof(directoryAbsolutePath)); + async Task DeleteDirectorySafeAsync([NotNull] string directoryAbsolutePath, int deleteRetries) + { + if (directoryAbsolutePath == null) throw new ArgumentNullException(nameof(directoryAbsolutePath)); - var success = false; - await SnapUtility.RetryAsync(async () => + var success = false; + await SnapUtility.RetryAsync(async () => + { + try { - try - { - if (!_snapOs.Filesystem.DirectoryExists(directoryAbsolutePath)) - { - return; - } - - _snapOs.KillAllProcessesInsideDirectory(directoryAbsolutePath); - } - catch (Exception e) + if (!_snapOs.Filesystem.DirectoryExists(directoryAbsolutePath)) { - _logger.ErrorException($"Exception thrown while killing processes in directory: {directoryAbsolutePath}.", e); + return; } - await _snapOs.Filesystem.DirectoryDeleteAsync(directoryAbsolutePath); - success = true; + _snapOs.KillAllProcessesInsideDirectory(directoryAbsolutePath); + } + catch (Exception e) + { + _logger.ErrorException($"Exception thrown while killing processes in directory: {directoryAbsolutePath}.", e); + } - }, deleteRetries, throwException: false); + await _snapOs.Filesystem.DirectoryDeleteAsync(directoryAbsolutePath); + success = true; - return success; - } + }, deleteRetries, throwException: false); + return success; } -} + +} \ No newline at end of file diff --git a/src/Snap/Core/SnapUtility.cs b/src/Snap/Core/SnapUtility.cs index 1cec20b9..fd8f6f3a 100644 --- a/src/Snap/Core/SnapUtility.cs +++ b/src/Snap/Core/SnapUtility.cs @@ -3,109 +3,108 @@ using System.Text; using System.Threading; -namespace Snap.Core +namespace Snap.Core; + +internal static class SnapUtility { - internal static class SnapUtility - { - /// - /// The namespace for ISO OIDs (from RFC 4122, Appendix C). - /// - static readonly Guid IsoOidNamespace = new("6ba7b812-9dad-11d1-80b4-00c04fd430c8"); - - // Converts a GUID (expressed as a byte array) to/from network order (MSB-first). - static void SwapByteOrder(byte[] guid) - { - SwapBytes(guid, 0, 3); - SwapBytes(guid, 1, 2); - SwapBytes(guid, 4, 5); - SwapBytes(guid, 6, 7); - } + /// + /// The namespace for ISO OIDs (from RFC 4122, Appendix C). + /// + static readonly Guid IsoOidNamespace = new("6ba7b812-9dad-11d1-80b4-00c04fd430c8"); - static void SwapBytes(byte[] guid, int left, int right) - { - var temp = guid[left]; - guid[left] = guid[right]; - guid[right] = temp; - } + // Converts a GUID (expressed as a byte array) to/from network order (MSB-first). + static void SwapByteOrder(byte[] guid) + { + SwapBytes(guid, 0, 3); + SwapBytes(guid, 1, 2); + SwapBytes(guid, 4, 5); + SwapBytes(guid, 6, 7); + } - public static Guid CreateGuidFromHash(string text) - { - return CreateGuidFromHash(text, IsoOidNamespace); - } + static void SwapBytes(byte[] guid, int left, int right) + { + var temp = guid[left]; + guid[left] = guid[right]; + guid[right] = temp; + } - public static Guid CreateGuidFromHash(string text, Guid namespaceId) - { - return CreateGuidFromHash(Encoding.UTF8.GetBytes(text), namespaceId); - } + public static Guid CreateGuidFromHash(string text) + { + return CreateGuidFromHash(text, IsoOidNamespace); + } - public static Guid CreateGuidFromHash(byte[] nameBytes, Guid namespaceId) - { - // convert the namespace UUID to network order (step 3) - var namespaceBytes = namespaceId.ToByteArray(); - SwapByteOrder(namespaceBytes); - - // comput the hash of the name space ID concatenated with the - // name (step 4) - byte[] hash; - using (var algorithm = SHA512.Create()) { - algorithm.TransformBlock(namespaceBytes, 0, namespaceBytes.Length, null, 0); - algorithm.TransformFinalBlock(nameBytes, 0, nameBytes.Length); - hash = algorithm.Hash ?? throw new InvalidOperationException(); - } + public static Guid CreateGuidFromHash(string text, Guid namespaceId) + { + return CreateGuidFromHash(Encoding.UTF8.GetBytes(text), namespaceId); + } - // most bytes from the hash are copied straight to the bytes of - // the new GUID (steps 5-7, 9, 11-12) - var newGuid = new byte[16]; - Array.Copy(hash, 0, newGuid, 0, 16); + public static Guid CreateGuidFromHash(byte[] nameBytes, Guid namespaceId) + { + // convert the namespace UUID to network order (step 3) + var namespaceBytes = namespaceId.ToByteArray(); + SwapByteOrder(namespaceBytes); + + // comput the hash of the name space ID concatenated with the + // name (step 4) + byte[] hash; + using (var algorithm = SHA512.Create()) { + algorithm.TransformBlock(namespaceBytes, 0, namespaceBytes.Length, null, 0); + algorithm.TransformFinalBlock(nameBytes, 0, nameBytes.Length); + hash = algorithm.Hash ?? throw new InvalidOperationException(); + } - // set the four most significant bits (bits 12 through 15) of - // the time_hi_and_version field to the appropriate 4-bit - // version number from Section 4.1.3 (step 8) - newGuid[6] = (byte)((newGuid[6] & 0x0F) | (5 << 4)); + // most bytes from the hash are copied straight to the bytes of + // the new GUID (steps 5-7, 9, 11-12) + var newGuid = new byte[16]; + Array.Copy(hash, 0, newGuid, 0, 16); - // set the two most significant bits (bits 6 and 7) of the - // clock_seq_hi_and_reserved to zero and one, respectively - // (step 10) - newGuid[8] = (byte)((newGuid[8] & 0x3F) | 0x80); + // set the four most significant bits (bits 12 through 15) of + // the time_hi_and_version field to the appropriate 4-bit + // version number from Section 4.1.3 (step 8) + newGuid[6] = (byte)((newGuid[6] & 0x0F) | (5 << 4)); - // convert the resulting UUID to local byte order (step 13) - SwapByteOrder(newGuid); - return new Guid(newGuid); - } + // set the two most significant bits (bits 6 and 7) of the + // clock_seq_hi_and_reserved to zero and one, respectively + // (step 10) + newGuid[8] = (byte)((newGuid[8] & 0x3F) | 0x80); - public static void Retry(this Action block, int retries = 2, int delayInMilliseconds = 250, bool throwException = true) - { - Func thunk = () => { - block(); - return null; - }; + // convert the resulting UUID to local byte order (step 13) + SwapByteOrder(newGuid); + return new Guid(newGuid); + } - thunk.RetryAsync(retries, delayInMilliseconds, throwException); - } + public static void Retry(this Action block, int retries = 2, int delayInMilliseconds = 250, bool throwException = true) + { + Func thunk = () => { + block(); + return null; + }; - public static T RetryAsync(this Func block, int retries = 2, int delayInMilliseconds = 250, bool throwException = true) - { - while (true) { - try { - var ret = block(); - return ret; - } catch (Exception) { - if (retries == 0) { - if (throwException) - { - throw; - } - - return default; - } + thunk.RetryAsync(retries, delayInMilliseconds, throwException); + } - retries--; - if (delayInMilliseconds > 0) + public static T RetryAsync(this Func block, int retries = 2, int delayInMilliseconds = 250, bool throwException = true) + { + while (true) { + try { + var ret = block(); + return ret; + } catch (Exception) { + if (retries == 0) { + if (throwException) { - Thread.Sleep(delayInMilliseconds); + throw; } + + return default; + } + + retries--; + if (delayInMilliseconds > 0) + { + Thread.Sleep(delayInMilliseconds); } } } } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Snapx.cs b/src/Snap/Core/Snapx.cs index 2af573e8..47d39030 100644 --- a/src/Snap/Core/Snapx.cs +++ b/src/Snap/Core/Snapx.cs @@ -13,275 +13,274 @@ using Snap.Logging; using Snap.Logging.LogProviders; -namespace Snap.Core +namespace Snap.Core; + +public static class Snapx { - public static class Snapx + static readonly ILog Logger; + static readonly object SyncRoot = new(); + // ReSharper disable once InconsistentNaming + internal static SnapApp _current; + internal static ISnapOs SnapOs { get; set; } + internal static List SupervisorProcessRestartArguments { get; private set; } + + static Snapx() { - static readonly ILog Logger; - static readonly object SyncRoot = new(); - // ReSharper disable once InconsistentNaming - internal static SnapApp _current; - internal static ISnapOs SnapOs { get; set; } - internal static List SupervisorProcessRestartArguments { get; private set; } - - static Snapx() + lock (SyncRoot) { - lock (SyncRoot) + if (SnapOs != null) { - if (SnapOs != null) - { - return; - } + return; + } - try - { - Logger = LogProvider.GetLogger(nameof(Snapx)); - var informationalVersion = typeof(Snapx).Assembly.GetCustomAttribute()?.InformationalVersion; - Version = !SemanticVersion.TryParse(informationalVersion, out var currentVersion) ? null : currentVersion; - - SnapOs = AnyOS.SnapOs.AnyOs; - WorkingDirectory = SnapOs.Filesystem.PathGetDirectoryName(typeof(Snapx).Assembly.Location); + try + { + Logger = LogProvider.GetLogger(nameof(Snapx)); + var informationalVersion = typeof(Snapx).Assembly.GetCustomAttribute()?.InformationalVersion; + Version = !SemanticVersion.TryParse(informationalVersion, out var currentVersion) ? null : currentVersion; + + SnapOs = AnyOS.SnapOs.AnyOs; + WorkingDirectory = SnapOs.Filesystem.PathGetDirectoryName(typeof(Snapx).Assembly.Location); - _current = WorkingDirectory.GetSnapAppFromDirectory(SnapOs.Filesystem, new SnapAppReader()); + _current = WorkingDirectory.GetSnapAppFromDirectory(SnapOs.Filesystem, new SnapAppReader()); - typeof(Snapx).Assembly - .GetCoreRunExecutableFullPath(SnapOs.Filesystem, new SnapAppReader(), out var supervisorExecutableAbsolutePath); + typeof(Snapx).Assembly + .GetCoreRunExecutableFullPath(SnapOs.Filesystem, new SnapAppReader(), out var supervisorExecutableAbsolutePath); - SuperVisorProcessExeDirectory = supervisorExecutableAbsolutePath; - } - catch (Exception e) - { - Logger.ErrorException("Unknown error during initialization", e); - } + SuperVisorProcessExeDirectory = supervisorExecutableAbsolutePath; } + catch (Exception e) + { + Logger.ErrorException("Unknown error during initialization", e); + } } + } - public static void EnableNLogLogProvider() - { - LogProvider.SetCurrentLogProvider(new NLogLogProvider()); - } + public static void EnableNLogLogProvider() + { + LogProvider.SetCurrentLogProvider(new NLogLogProvider()); + } - public static void EnableSerilogLogProvider() - { - LogProvider.SetCurrentLogProvider(new SerilogLogProvider()); - } + public static void EnableSerilogLogProvider() + { + LogProvider.SetCurrentLogProvider(new SerilogLogProvider()); + } - public static void EnableLog4NetLogProvider() - { - LogProvider.SetCurrentLogProvider(new Log4NetLogProvider()); - } + public static void EnableLog4NetLogProvider() + { + LogProvider.SetCurrentLogProvider(new Log4NetLogProvider()); + } - public static void EnableLoupeLogProvider() - { - LogProvider.SetCurrentLogProvider(new LoupeLogProvider()); - } + public static void EnableLoupeLogProvider() + { + LogProvider.SetCurrentLogProvider(new LoupeLogProvider()); + } - /// - /// Current application release information. - /// - public static SnapApp Current + /// + /// Current application release information. + /// + public static SnapApp Current + { + get { - get + lock (SyncRoot) { - lock (SyncRoot) - { - return _current == null ? null : new SnapApp(_current); - } + return _current == null ? null : new SnapApp(_current); } } - /// - /// Current application working directory. - /// - public static string WorkingDirectory { get; } - /// - /// Current Snapx.Core version. - /// - public static SemanticVersion Version { get; } - /// - /// Current supervisor process. - /// - public static Process SuperVisorProcess { get; private set; } - /// - /// Current supervisor process absolute path. - /// - public static string SuperVisorProcessExeDirectory { get; } - - /// - /// Call this method as early as possible in app startup. This method - /// will dispatch to your methods to set up your app. Depending on the - /// parameter, your app will exit after this method is called, which - /// is required by Snap. - /// - /// Called the first time an app is run after - /// being installed. Your application will **not** exit after this is - /// dispatched, you should use this as a hint (i.e. show a 'Welcome' message) - /// - /// Called when your app is initially - /// installed. Your application will exit afterwards. - /// - /// Called when your app is updated to a new - /// version. Your application will exit afterwards. - /// Use in a unit-test runner to mock the - /// arguments. In your app, leave this as null. - /// If this methods returns TRUE then you should exit your program immediately. - /// If is null. - public static bool ProcessEvents([NotNull] string[] arguments, - Action onFirstRun = null, - Action onInstalled = null, - Action onUpdated = null) + } + /// + /// Current application working directory. + /// + public static string WorkingDirectory { get; } + /// + /// Current Snapx.Core version. + /// + public static SemanticVersion Version { get; } + /// + /// Current supervisor process. + /// + public static Process SuperVisorProcess { get; private set; } + /// + /// Current supervisor process absolute path. + /// + public static string SuperVisorProcessExeDirectory { get; } + + /// + /// Call this method as early as possible in app startup. This method + /// will dispatch to your methods to set up your app. Depending on the + /// parameter, your app will exit after this method is called, which + /// is required by Snap. + /// + /// Called the first time an app is run after + /// being installed. Your application will **not** exit after this is + /// dispatched, you should use this as a hint (i.e. show a 'Welcome' message) + /// + /// Called when your app is initially + /// installed. Your application will exit afterwards. + /// + /// Called when your app is updated to a new + /// version. Your application will exit afterwards. + /// Use in a unit-test runner to mock the + /// arguments. In your app, leave this as null. + /// If this methods returns TRUE then you should exit your program immediately. + /// If is null. + public static bool ProcessEvents([NotNull] string[] arguments, + Action onFirstRun = null, + Action onInstalled = null, + Action onUpdated = null) + { + if (arguments == null) throw new ArgumentNullException(nameof(arguments)); + if (arguments.Length != 2) { - if (arguments == null) throw new ArgumentNullException(nameof(arguments)); - if (arguments.Length != 2) - { - return false; - } + return false; + } - var invoke = new[] { - new { Key = "--snapx-first-run", Value = onFirstRun ?? DefaultAction }, - new { Key = "--snapx-installed", Value = onInstalled ?? DefaultAction }, - new { Key = "--snapx-updated", Value = onUpdated ?? DefaultAction } - }.ToDictionary(k => k.Key, v => v.Value); + var invoke = new[] { + new { Key = "--snapx-first-run", Value = onFirstRun ?? DefaultAction }, + new { Key = "--snapx-installed", Value = onInstalled ?? DefaultAction }, + new { Key = "--snapx-updated", Value = onUpdated ?? DefaultAction } + }.ToDictionary(k => k.Key, v => v.Value); - var actionName = arguments[0]; - if (!invoke.ContainsKey(actionName)) - { - return false; - } + var actionName = arguments[0]; + if (!invoke.ContainsKey(actionName)) + { + return false; + } - var doNotExitActions = new[] - { - "--snapx-first-run" - }; + var doNotExitActions = new[] + { + "--snapx-first-run" + }; - try - { - Logger.Trace($"Handling event: {actionName}."); + try + { + Logger.Trace($"Handling event: {actionName}."); - var currentVersion = SemanticVersion.Parse(arguments[1]); + var currentVersion = SemanticVersion.Parse(arguments[1]); - invoke[actionName](currentVersion); + invoke[actionName](currentVersion); - Logger.Trace($"Handled event: {actionName}."); + Logger.Trace($"Handled event: {actionName}."); - if (doNotExitActions.Any(x => string.Equals(x, actionName))) - { - return false; - } + if (doNotExitActions.Any(x => string.Equals(x, actionName))) + { + return false; + } - SnapOs.Exit(); + SnapOs.Exit(); - return true; - } - catch (Exception ex) - { - Logger.ErrorException($"Exception thrown while handling snap event. Action: {actionName}", ex); + return true; + } + catch (Exception ex) + { + Logger.ErrorException($"Exception thrown while handling snap event. Action: {actionName}", ex); - SnapOs.Exit(1); + SnapOs.Exit(1); - return true; - } + return true; } + } - /// - /// Supervises your application and if it exits or crashes it will be automatically restarted. - /// NB! This method _MUST_ be invoked after . You can stop the supervisor - /// process by invoking before exiting the application. - /// - /// - public static bool StartSupervisor(List restartArguments = null) - { - StopSupervisor(); + /// + /// Supervises your application and if it exits or crashes it will be automatically restarted. + /// NB! This method _MUST_ be invoked after . You can stop the supervisor + /// process by invoking before exiting the application. + /// + /// + public static bool StartSupervisor(List restartArguments = null) + { + StopSupervisor(); - if (!SnapOs.Filesystem.FileExists(SuperVisorProcessExeDirectory)) - { - Logger.Error($"Unable to find supervisor executable: {SuperVisorProcessExeDirectory}"); - return false; - } + if (!SnapOs.Filesystem.FileExists(SuperVisorProcessExeDirectory)) + { + Logger.Error($"Unable to find supervisor executable: {SuperVisorProcessExeDirectory}"); + return false; + } - var superVisorId = Current.SuperVisorId; + var superVisorId = Current.SuperVisorId; - var coreRunArgument = $"--corerun-supervise-pid={SnapOs.ProcessManager.Current.Id} --corerun-supervise-id={superVisorId}"; + var coreRunArgument = $"--corerun-supervise-pid={SnapOs.ProcessManager.Current.Id} --corerun-supervise-id={superVisorId}"; - SuperVisorProcess = SnapOs.ProcessManager.StartNonBlocking(new ProcessStartInfoBuilder(SuperVisorProcessExeDirectory) - .AddRange(restartArguments ?? new List()) - .Add(coreRunArgument) - ); + SuperVisorProcess = SnapOs.ProcessManager.StartNonBlocking(new ProcessStartInfoBuilder(SuperVisorProcessExeDirectory) + .AddRange(restartArguments ?? new List()) + .Add(coreRunArgument) + ); - SupervisorProcessRestartArguments = restartArguments ?? new List(); + SupervisorProcessRestartArguments = restartArguments ?? new List(); - Logger.Debug($"Enabled supervision of process with id: {SnapOs.ProcessManager.Current.Id}. Supervisor id: {superVisorId}. " + - $"Restart arguments({SupervisorProcessRestartArguments.Count}): {string.Join(",", SupervisorProcessRestartArguments)}. "); + Logger.Debug($"Enabled supervision of process with id: {SnapOs.ProcessManager.Current.Id}. Supervisor id: {superVisorId}. " + + $"Restart arguments({SupervisorProcessRestartArguments.Count}): {string.Join(",", SupervisorProcessRestartArguments)}. "); - SuperVisorProcess.Refresh(); + SuperVisorProcess.Refresh(); - return !SuperVisorProcess.HasExited; - } + return !SuperVisorProcess.HasExited; + } - public static bool StopSupervisor() + public static bool StopSupervisor() + { + try { - try + if (SuperVisorProcess == null) { - if (SuperVisorProcess == null) - { - return false; - } + return false; + } - SuperVisorProcess.Refresh(); - var supervisorRunning = !SuperVisorProcess.HasExited; - if (!supervisorRunning) + SuperVisorProcess.Refresh(); + var supervisorRunning = !SuperVisorProcess.HasExited; + if (!supervisorRunning) + { + return false; + } + + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // ReSharper disable once InconsistentNaming + const int SIGTERM = 15; + // We have to signal the supervisor so we can release the machine wide semaphore. + var killResult = CoreRunLib.NativeMethodsUnix.kill(SuperVisorProcess.Id, SIGTERM); + var killSuccess = killResult == 0; + + if (!killSuccess) { + Logger.Warn($"Failed to signal ({nameof(SIGTERM)}) supervisor. Return code: {killResult}."); return false; } - - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // ReSharper disable once InconsistentNaming - const int SIGTERM = 15; - // We have to signal the supervisor so we can release the machine wide semaphore. - var killResult = CoreRunLib.NativeMethodsUnix.kill(SuperVisorProcess.Id, SIGTERM); - var killSuccess = killResult == 0; - if (!killSuccess) - { - Logger.Warn($"Failed to signal ({nameof(SIGTERM)}) supervisor. Return code: {killResult}."); - return false; - } + Logger.Info($"Successfully signaled ({nameof(SIGTERM)}) supervisor."); - Logger.Info($"Successfully signaled ({nameof(SIGTERM)}) supervisor."); + var attempts = 3; + while (attempts-- >= 0) + { + SuperVisorProcess.Refresh(); - var attempts = 3; - while (attempts-- >= 0) + supervisorRunning = !SuperVisorProcess.HasExited; + if (!supervisorRunning) { - SuperVisorProcess.Refresh(); - - supervisorRunning = !SuperVisorProcess.HasExited; - if (!supervisorRunning) - { - break; - } - - Thread.Sleep(100); + break; } - return !supervisorRunning; + Thread.Sleep(100); } - SuperVisorProcess.Kill(); - SuperVisorProcess.Refresh(); - supervisorRunning = !SuperVisorProcess.HasExited; - return !supervisorRunning; } - catch (Exception e) - { - Logger.ErrorException($"Exception thrown when killing supervisor process with pid: {SuperVisorProcess?.Id}", e); - } - SuperVisorProcess = null; - return false; - } - static void DefaultAction(SemanticVersion version) + SuperVisorProcess.Kill(); + SuperVisorProcess.Refresh(); + supervisorRunning = !SuperVisorProcess.HasExited; + + return !supervisorRunning; + } + catch (Exception e) { + Logger.ErrorException($"Exception thrown when killing supervisor process with pid: {SuperVisorProcess?.Id}", e); } + SuperVisorProcess = null; + return false; + } + + static void DefaultAction(SemanticVersion version) + { } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Yaml/Emitters/AbstractClassTagEventEmitter.cs b/src/Snap/Core/Yaml/Emitters/AbstractClassTagEventEmitter.cs index e711470f..683ce6aa 100644 --- a/src/Snap/Core/Yaml/Emitters/AbstractClassTagEventEmitter.cs +++ b/src/Snap/Core/Yaml/Emitters/AbstractClassTagEventEmitter.cs @@ -6,28 +6,25 @@ using YamlDotNet.Serialization; using YamlDotNet.Serialization.EventEmitters; -namespace Snap.Core.Yaml.Emitters -{ - // https://github.com/aaubry/YamlDotNet/pull/229/files +namespace Snap.Core.Yaml.Emitters; +// https://github.com/aaubry/YamlDotNet/pull/229/files - internal class AbstractClassTagEventEmitter : ChainedEventEmitter - { - readonly IDictionary _tagMappings; +internal class AbstractClassTagEventEmitter : ChainedEventEmitter +{ + readonly IDictionary _tagMappings; - public AbstractClassTagEventEmitter(IEventEmitter inner, [NotNull] IDictionary tagMappings) : base(inner) - { - if (tagMappings == null) throw new ArgumentNullException(nameof(tagMappings)); - _tagMappings = tagMappings.ToDictionary(x => x.Value, x => $"!{x.Key}"); - } + public AbstractClassTagEventEmitter(IEventEmitter inner, [NotNull] IDictionary tagMappings) : base(inner) + { + if (tagMappings == null) throw new ArgumentNullException(nameof(tagMappings)); + _tagMappings = tagMappings.ToDictionary(x => x.Value, x => $"!{x.Key}"); + } - public override void Emit(MappingStartEventInfo eventInfo, IEmitter emitter) + public override void Emit(MappingStartEventInfo eventInfo, IEmitter emitter) + { + if(_tagMappings.ContainsKey(eventInfo.Source.Type)) { - if(_tagMappings.ContainsKey(eventInfo.Source.Type)) - { - eventInfo.Tag = _tagMappings[eventInfo.Source.Type]; - } - base.Emit(eventInfo, emitter); + eventInfo.Tag = _tagMappings[eventInfo.Source.Type]; } + base.Emit(eventInfo, emitter); } -} - +} \ No newline at end of file diff --git a/src/Snap/Core/Yaml/NodeTypeResolvers/AbstractClassTypeResolver.cs b/src/Snap/Core/Yaml/NodeTypeResolvers/AbstractClassTypeResolver.cs index 53f9d71e..a5b3ef32 100644 --- a/src/Snap/Core/Yaml/NodeTypeResolvers/AbstractClassTypeResolver.cs +++ b/src/Snap/Core/Yaml/NodeTypeResolvers/AbstractClassTypeResolver.cs @@ -6,45 +6,48 @@ using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -namespace Snap.Core.Yaml.NodeTypeResolvers +namespace Snap.Core.Yaml.NodeTypeResolvers; +// https://github.com/aaubry/YamlDotNet/issues/343#issuecomment-424882014 + +public sealed class AbstractClassTypeResolver : INodeTypeResolver { - // https://github.com/aaubry/YamlDotNet/issues/343#issuecomment-424882014 + readonly IDictionary _tagMappings; - public sealed class AbstractClassTypeResolver : INodeTypeResolver + public AbstractClassTypeResolver([NotNull] Dictionary typesByName) { - readonly IDictionary _tagMappings; + if (typesByName == null) throw new ArgumentNullException(nameof(typesByName)); + var tagMappings = typesByName.ToDictionary(kv => "!" + kv.Key, kv => kv.Value); + _tagMappings = tagMappings; + } + + bool INodeTypeResolver.Resolve(NodeEvent nodeEvent, ref Type currentType) + { + if (nodeEvent.Tag.IsEmpty) + { + return false; + } - public AbstractClassTypeResolver([NotNull] Dictionary typesByName) + var typeName = nodeEvent.Tag.Value; // this is what gets the "!MyDotnetClass" tag from the yaml + if (string.IsNullOrWhiteSpace(typeName)) { - if (typesByName == null) throw new ArgumentNullException(nameof(typesByName)); - var tagMappings = typesByName.ToDictionary(kv => "!" + kv.Key, kv => kv.Value); - _tagMappings = tagMappings; + return false; } - bool INodeTypeResolver.Resolve(NodeEvent nodeEvent, ref Type currentType) + var arrayType = false; + if (typeName.EndsWith("[]")) // this handles tags for array types like "!MyDotnetClass[]" { - var typeName = nodeEvent.Tag; // this is what gets the "!MyDotnetClass" tag from the yaml - if (string.IsNullOrEmpty(typeName)) - { - return false; - } - - var arrayType = false; - if (typeName.EndsWith("[]")) // this handles tags for array types like "!MyDotnetClass[]" - { - arrayType = true; - typeName = typeName[0..^2]; - } - - if (!_tagMappings.TryGetValue(typeName, out var predefinedType)) - { - throw new YamlException( - $"I can't find the type '{nodeEvent.Tag}'. Is it spelled correctly? If there are" + - $" multiple types named '{nodeEvent.Tag}', you must used the fully qualified type name."); - } - - currentType = arrayType ? predefinedType.MakeArrayType() : predefinedType; - return true; + arrayType = true; + typeName = typeName[..^2]; } + + if (!_tagMappings.TryGetValue(typeName, out var predefinedType)) + { + throw new YamlException( + $"I can't find the type '{nodeEvent.Tag}'. Is it spelled correctly? If there are" + + $" multiple types named '{nodeEvent.Tag}', you must used the fully qualified type name."); + } + + currentType = arrayType ? predefinedType.MakeArrayType() : predefinedType; + return true; } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Yaml/TypeConverters/OsPlatformYamlTypeConverter.cs b/src/Snap/Core/Yaml/TypeConverters/OsPlatformYamlTypeConverter.cs index 61d78872..96d73b8f 100644 --- a/src/Snap/Core/Yaml/TypeConverters/OsPlatformYamlTypeConverter.cs +++ b/src/Snap/Core/Yaml/TypeConverters/OsPlatformYamlTypeConverter.cs @@ -4,36 +4,35 @@ using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -namespace Snap.Core.Yaml.TypeConverters +namespace Snap.Core.Yaml.TypeConverters; + +internal sealed class OsPlatformYamlTypeConverter : IYamlTypeConverter { - internal sealed class OsPlatformYamlTypeConverter : IYamlTypeConverter + public bool Accepts(Type type) { - public bool Accepts(Type type) - { - return type == typeof(OSPlatform); - } + return type == typeof(OSPlatform); + } - public object ReadYaml(IParser parser, Type type) - { - var osPlatform = ((Scalar)parser.Current)?.Value; - parser.MoveNext(); - return TryCreateOsPlatform(osPlatform); - } + public object ReadYaml(IParser parser, Type type) + { + var osPlatform = ((Scalar)parser.Current)?.Value; + parser.MoveNext(); + return TryCreateOsPlatform(osPlatform); + } - public void WriteYaml(IEmitter emitter, object value, Type type) - { - var osPlatformStr = ((OSPlatform)value).ToString().ToLowerInvariant(); - emitter.Emit(new Scalar(osPlatformStr)); - } + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var osPlatformStr = ((OSPlatform)value).ToString().ToLowerInvariant(); + emitter.Emit(new Scalar(osPlatformStr)); + } - static OSPlatform TryCreateOsPlatform(string osPlatform) + static OSPlatform TryCreateOsPlatform(string osPlatform) + { + if (string.IsNullOrWhiteSpace(osPlatform)) { - if (string.IsNullOrWhiteSpace(osPlatform)) - { - osPlatform = "unknown"; - } - - return OSPlatform.Create(osPlatform.ToUpperInvariant()); + osPlatform = "unknown"; } + + return OSPlatform.Create(osPlatform.ToUpperInvariant()); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Yaml/TypeConverters/SemanticVersionYamlTypeConverter.cs b/src/Snap/Core/Yaml/TypeConverters/SemanticVersionYamlTypeConverter.cs index 2f3d66a6..24b085b7 100644 --- a/src/Snap/Core/Yaml/TypeConverters/SemanticVersionYamlTypeConverter.cs +++ b/src/Snap/Core/Yaml/TypeConverters/SemanticVersionYamlTypeConverter.cs @@ -4,27 +4,26 @@ using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -namespace Snap.Core.Yaml.TypeConverters +namespace Snap.Core.Yaml.TypeConverters; + +internal sealed class SemanticVersionYamlTypeConverter : IYamlTypeConverter { - internal sealed class SemanticVersionYamlTypeConverter : IYamlTypeConverter + public bool Accepts(Type type) { - public bool Accepts(Type type) - { - return type == typeof(SemanticVersion); - } + return type == typeof(SemanticVersion); + } - public object ReadYaml(IParser parser, Type type) - { - var semanticVersionStr = ((Scalar)parser.Current)?.Value; - parser.MoveNext(); - SemanticVersion.TryParse(semanticVersionStr, out var semanticVersion); - return semanticVersion; - } + public object ReadYaml(IParser parser, Type type) + { + var semanticVersionStr = ((Scalar)parser.Current)?.Value; + parser.MoveNext(); + SemanticVersion.TryParse(semanticVersionStr, out var semanticVersion); + return semanticVersion; + } - public void WriteYaml(IEmitter emitter, object value, Type type) - { - var semanticVersionStr = ((SemanticVersion)value)?.ToNormalizedString() ?? string.Empty; - emitter.Emit(new Scalar(semanticVersionStr)); - } + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var semanticVersionStr = ((SemanticVersion)value)?.ToNormalizedString() ?? string.Empty; + emitter.Emit(new Scalar(semanticVersionStr)); } -} +} \ No newline at end of file diff --git a/src/Snap/Core/Yaml/TypeConverters/UriYamlTypeConverter.cs b/src/Snap/Core/Yaml/TypeConverters/UriYamlTypeConverter.cs index 86960099..301f3e99 100644 --- a/src/Snap/Core/Yaml/TypeConverters/UriYamlTypeConverter.cs +++ b/src/Snap/Core/Yaml/TypeConverters/UriYamlTypeConverter.cs @@ -3,27 +3,26 @@ using YamlDotNet.Core.Events; using YamlDotNet.Serialization; -namespace Snap.Core.Yaml.TypeConverters +namespace Snap.Core.Yaml.TypeConverters; + +internal sealed class UriYamlTypeConverter : IYamlTypeConverter { - internal sealed class UriYamlTypeConverter : IYamlTypeConverter + public bool Accepts(Type type) { - public bool Accepts(Type type) - { - return type == typeof(Uri); - } + return type == typeof(Uri); + } - public object ReadYaml(IParser parser, Type type) - { - var uriStr = ((Scalar)parser.Current)?.Value; - parser.MoveNext(); - Uri.TryCreate(uriStr, UriKind.Absolute, out var uri); - return uri; - } + public object ReadYaml(IParser parser, Type type) + { + var uriStr = ((Scalar)parser.Current)?.Value; + parser.MoveNext(); + Uri.TryCreate(uriStr, UriKind.Absolute, out var uri); + return uri; + } - public void WriteYaml(IEmitter emitter, object value, Type type) - { - var uriStr = ((Uri)value)?.ToString() ?? string.Empty; - emitter.Emit(new Scalar(uriStr)); - } + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var uriStr = ((Uri)value)?.ToString() ?? string.Empty; + emitter.Emit(new Scalar(uriStr)); } -} +} \ No newline at end of file diff --git a/src/Snap/CoreRunLib.cs b/src/Snap/CoreRunLib.cs index c465e69a..767e16ab 100644 --- a/src/Snap/CoreRunLib.cs +++ b/src/Snap/CoreRunLib.cs @@ -4,276 +4,275 @@ using Snap.Core; using Snap.Extensions; -namespace Snap +namespace Snap; + +internal interface ICoreRunLib : IDisposable { - internal interface ICoreRunLib : IDisposable - { - bool Chmod(string filename, int mode); - bool IsElevated(); - bool SetIcon(string exeAbsolutePath, string iconAbsolutePath); - bool FileExists(string filename); - } + bool Chmod(string filename, int mode); + bool IsElevated(); + bool SetIcon(string exeAbsolutePath, string iconAbsolutePath); + bool FileExists(string filename); +} - internal sealed class CoreRunLib : ICoreRunLib +internal sealed class CoreRunLib : ICoreRunLib +{ + IntPtr _libPtr; + readonly OSPlatform _osPlatform; + + // generic + [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Unicode)] + delegate int pal_is_elevated_delegate(); + readonly Delegate pal_is_elevated; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Unicode)] + delegate int pal_set_icon_delegate( + [MarshalAs(UnmanagedType.LPUTF8Str)] string exeFilename, + [MarshalAs(UnmanagedType.LPUTF8Str)] string iconFilename + ); + readonly Delegate pal_set_icon; + + // filesystem + [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Unicode)] + delegate int pal_fs_chmod_delegate( + [MarshalAs(UnmanagedType.LPUTF8Str)] string filename, + int mode); + readonly Delegate pal_fs_chmod; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Unicode)] + delegate int pal_fs_file_exists_delegate( + [MarshalAs(UnmanagedType.LPUTF8Str)] string filename + ); + readonly Delegate pal_fs_file_exists; + + public CoreRunLib([JetBrains.Annotations.NotNull] ISnapFilesystem filesystem, OSPlatform osPlatform, [JetBrains.Annotations.NotNull] string workingDirectory) { - IntPtr _libPtr; - readonly OSPlatform _osPlatform; + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - // generic - [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Unicode)] - delegate int pal_is_elevated_delegate(); - readonly Delegate pal_is_elevated; - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Unicode)] - delegate int pal_set_icon_delegate( - [MarshalAs(UnmanagedType.LPUTF8Str)] string exeFilename, - [MarshalAs(UnmanagedType.LPUTF8Str)] string iconFilename - ); - readonly Delegate pal_set_icon; - - // filesystem - [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Unicode)] - delegate int pal_fs_chmod_delegate( - [MarshalAs(UnmanagedType.LPUTF8Str)] string filename, - int mode); - readonly Delegate pal_fs_chmod; - - [UnmanagedFunctionPointer(CallingConvention.Cdecl, SetLastError = true, CharSet = CharSet.Unicode)] - delegate int pal_fs_file_exists_delegate( - [MarshalAs(UnmanagedType.LPUTF8Str)] string filename - ); - readonly Delegate pal_fs_file_exists; - - public CoreRunLib([JetBrains.Annotations.NotNull] ISnapFilesystem filesystem, OSPlatform osPlatform, [JetBrains.Annotations.NotNull] string workingDirectory) + if (!osPlatform.IsSupportedOsVersion()) { - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - - if (!osPlatform.IsSupportedOsVersion()) - { - throw new PlatformNotSupportedException(); - } + throw new PlatformNotSupportedException(); + } - _osPlatform = osPlatform; + _osPlatform = osPlatform; - var filename = filesystem.PathCombine(workingDirectory, "libcorerun-"); + var filename = filesystem.PathCombine(workingDirectory, "libcorerun-"); - #if SNAP_BOOTSTRAP +#if SNAP_BOOTSTRAP return; - #endif +#endif - var rid = osPlatform.BuildRid(); - if (osPlatform == OSPlatform.Windows) - { - filename += $"{rid}.dll"; - _libPtr = NativeMethodsWindows.dlopen(filename); - } - else if (osPlatform == OSPlatform.Linux) - { - filename += $"{rid}.so"; - _libPtr = NativeMethodsUnix.dlopen(filename, NativeMethodsUnix.libdl_RTLD_NOW | NativeMethodsUnix.libdl_RTLD_LOCAL); - } - - if (_libPtr == IntPtr.Zero) - { - throw new FileNotFoundException($"Failed to load corerun: {filename}. " + - $"OS: {osPlatform}. " + - $"64-bit OS: {Environment.Is64BitOperatingSystem}. " + - $"Last error: {Marshal.GetLastWin32Error()}."); - } - - // generic - pal_is_elevated = new Delegate(_libPtr, osPlatform); - pal_set_icon = new Delegate(_libPtr, osPlatform); - - // filesystem - pal_fs_chmod = new Delegate(_libPtr, osPlatform); - pal_fs_file_exists = new Delegate(_libPtr, osPlatform); + var rid = osPlatform.BuildRid(); + if (osPlatform == OSPlatform.Windows) + { + filename += $"{rid}.dll"; + _libPtr = NativeMethodsWindows.dlopen(filename); } - - public bool Chmod([JetBrains.Annotations.NotNull] string filename, int mode) + else if (osPlatform == OSPlatform.Linux) { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - pal_fs_chmod.ThrowIfDangling(); - return pal_fs_chmod.Invoke(filename, mode) == 1; + filename += $"{rid}.so"; + _libPtr = NativeMethodsUnix.dlopen(filename, NativeMethodsUnix.libdl_RTLD_NOW | NativeMethodsUnix.libdl_RTLD_LOCAL); } - public bool IsElevated() + if (_libPtr == IntPtr.Zero) { - pal_is_elevated.ThrowIfDangling(); - return pal_is_elevated.Invoke() == 1; + throw new FileNotFoundException($"Failed to load corerun: {filename}. " + + $"OS: {osPlatform}. " + + $"64-bit OS: {Environment.Is64BitOperatingSystem}. " + + $"Last error: {Marshal.GetLastWin32Error()}."); } - public bool SetIcon([JetBrains.Annotations.NotNull] string exeAbsolutePath, [JetBrains.Annotations.NotNull] string iconAbsolutePath) + // generic + pal_is_elevated = new Delegate(_libPtr, osPlatform); + pal_set_icon = new Delegate(_libPtr, osPlatform); + + // filesystem + pal_fs_chmod = new Delegate(_libPtr, osPlatform); + pal_fs_file_exists = new Delegate(_libPtr, osPlatform); + } + + public bool Chmod([JetBrains.Annotations.NotNull] string filename, int mode) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + pal_fs_chmod.ThrowIfDangling(); + return pal_fs_chmod.Invoke(filename, mode) == 1; + } + + public bool IsElevated() + { + pal_is_elevated.ThrowIfDangling(); + return pal_is_elevated.Invoke() == 1; + } + + public bool SetIcon([JetBrains.Annotations.NotNull] string exeAbsolutePath, [JetBrains.Annotations.NotNull] string iconAbsolutePath) + { + if (exeAbsolutePath == null) throw new ArgumentNullException(nameof(exeAbsolutePath)); + if (iconAbsolutePath == null) throw new ArgumentNullException(nameof(iconAbsolutePath)); + pal_set_icon.ThrowIfDangling(); + if (!FileExists(exeAbsolutePath)) { - if (exeAbsolutePath == null) throw new ArgumentNullException(nameof(exeAbsolutePath)); - if (iconAbsolutePath == null) throw new ArgumentNullException(nameof(iconAbsolutePath)); - pal_set_icon.ThrowIfDangling(); - if (!FileExists(exeAbsolutePath)) - { - throw new FileNotFoundException(exeAbsolutePath); - } - if (!FileExists(iconAbsolutePath)) - { - throw new FileNotFoundException(iconAbsolutePath); - } - return pal_set_icon.Invoke(exeAbsolutePath, iconAbsolutePath) == 1; + throw new FileNotFoundException(exeAbsolutePath); } - - public bool FileExists([JetBrains.Annotations.NotNull] string filename) + if (!FileExists(iconAbsolutePath)) { - if (filename == null) throw new ArgumentNullException(nameof(filename)); - pal_fs_file_exists.ThrowIfDangling(); - return pal_fs_file_exists.Invoke(filename) == 1; + throw new FileNotFoundException(iconAbsolutePath); } + return pal_set_icon.Invoke(exeAbsolutePath, iconAbsolutePath) == 1; + } + + public bool FileExists([JetBrains.Annotations.NotNull] string filename) + { + if (filename == null) throw new ArgumentNullException(nameof(filename)); + pal_fs_file_exists.ThrowIfDangling(); + return pal_fs_file_exists.Invoke(filename) == 1; + } - public void Dispose() + public void Dispose() + { + if (_libPtr == IntPtr.Zero) { - if (_libPtr == IntPtr.Zero) - { - return; - } + return; + } - void DisposeDelegates() - { - // generic - pal_is_elevated.Unref(); - pal_set_icon.Unref(); + void DisposeDelegates() + { + // generic + pal_is_elevated.Unref(); + pal_set_icon.Unref(); - // filesystem - pal_fs_chmod.Unref(); - pal_fs_file_exists.Unref(); - } - - if (_osPlatform == OSPlatform.Windows) - { - NativeMethodsWindows.dlclose(_libPtr); - _libPtr = IntPtr.Zero; - DisposeDelegates(); - return; - } - - if (_osPlatform == OSPlatform.Linux) - { - _ = NativeMethodsUnix.dlclose(_libPtr); - _libPtr = IntPtr.Zero; - DisposeDelegates(); - return; - } - - throw new PlatformNotSupportedException(); + // filesystem + pal_fs_chmod.Unref(); + pal_fs_file_exists.Unref(); } -#pragma warning disable IDE1006 // Naming Styles - static class NativeMethodsWindows + + if (_osPlatform == OSPlatform.Windows) { - [DllImport("kernel32", SetLastError = true, EntryPoint = "GetProcAddress", CharSet = CharSet.Ansi)] - public static extern IntPtr dlsym(IntPtr hModule, string procname); - [DllImport("kernel32", SetLastError = true, EntryPoint = "LoadLibraryA", CharSet = CharSet.Ansi)] - public static extern IntPtr dlopen([MarshalAs(UnmanagedType.LPStr)] string filename); - [DllImport("kernel32", SetLastError = true, EntryPoint = "FreeLibrary")] - public static extern bool dlclose(IntPtr hModule); + NativeMethodsWindows.dlclose(_libPtr); + _libPtr = IntPtr.Zero; + DisposeDelegates(); + return; } - internal static class NativeMethodsUnix + if (_osPlatform == OSPlatform.Linux) { - public const int libdl_RTLD_LOCAL = 1; - public const int libdl_RTLD_NOW = 2; + _ = NativeMethodsUnix.dlclose(_libPtr); + _libPtr = IntPtr.Zero; + DisposeDelegates(); + return; + } - // https://github.com/tmds/Tmds.LibC/blob/f336956facd8f6a0f8dcfa1c652828237dc032fb/src/Sources/linux.common/types.cs#L162 - public readonly struct pid_t : IEquatable - { - internal int Value { get; } + throw new PlatformNotSupportedException(); + } +#pragma warning disable IDE1006 // Naming Styles + static class NativeMethodsWindows + { + [DllImport("kernel32", SetLastError = true, EntryPoint = "GetProcAddress", CharSet = CharSet.Ansi)] + public static extern IntPtr dlsym(IntPtr hModule, string procname); + [DllImport("kernel32", SetLastError = true, EntryPoint = "LoadLibraryA", CharSet = CharSet.Ansi)] + public static extern IntPtr dlopen([MarshalAs(UnmanagedType.LPStr)] string filename); + [DllImport("kernel32", SetLastError = true, EntryPoint = "FreeLibrary")] + public static extern bool dlclose(IntPtr hModule); + } - pid_t(int value) => Value = value; + internal static class NativeMethodsUnix + { + public const int libdl_RTLD_LOCAL = 1; + public const int libdl_RTLD_NOW = 2; - public static implicit operator int(pid_t arg) => arg.Value; - public static implicit operator pid_t(int arg) => new(arg); + // https://github.com/tmds/Tmds.LibC/blob/f336956facd8f6a0f8dcfa1c652828237dc032fb/src/Sources/linux.common/types.cs#L162 + public readonly struct pid_t : IEquatable + { + internal int Value { get; } - public override string ToString() => Value.ToString(); + pid_t(int value) => Value = value; - public override int GetHashCode() => Value.GetHashCode(); + public static implicit operator int(pid_t arg) => arg.Value; + public static implicit operator pid_t(int arg) => new(arg); - public override bool Equals(object obj) - { - if (obj != null && obj is pid_t v) - { - return this == v; - } + public override string ToString() => Value.ToString(); - return false; - } + public override int GetHashCode() => Value.GetHashCode(); - public bool Equals(pid_t v) => this == v; + public override bool Equals(object obj) + { + if (obj != null && obj is pid_t v) + { + return this == v; + } - public static pid_t operator -(pid_t v) => new(-v.Value); - public static bool operator ==(pid_t v1, pid_t v2) => v1.Value == v2.Value; - public static bool operator !=(pid_t v1, pid_t v2) => v1.Value != v2.Value; + return false; } - - [DllImport("libdl", SetLastError = true, EntryPoint = "dlsym", CharSet = CharSet.Ansi)] - public static extern IntPtr dlsym(IntPtr handle, string symbol); - [DllImport("libdl", SetLastError = true, EntryPoint = "dlopen", CharSet = CharSet.Ansi)] - public static extern IntPtr dlopen(string filename, int flags); - [DllImport("libdl", SetLastError = true, EntryPoint = "dlclose")] - public static extern int dlclose(IntPtr hModule); - [DllImport("libc", SetLastError = true, EntryPoint = "kill")] - public static extern int kill (pid_t pid, int sig); - } - sealed class Delegate where T: Delegate - { - public T Invoke { get; } - public IntPtr Ptr { get; private set; } - public string Symbol { get; } + public bool Equals(pid_t v) => this == v; - public Delegate(IntPtr instancePtr, OSPlatform osPlatform) - { - Ptr = IntPtr.Zero; - Invoke = null; + public static pid_t operator -(pid_t v) => new(-v.Value); + public static bool operator ==(pid_t v1, pid_t v2) => v1.Value == v2.Value; + public static bool operator !=(pid_t v1, pid_t v2) => v1.Value != v2.Value; + } + + [DllImport("libdl", SetLastError = true, EntryPoint = "dlsym", CharSet = CharSet.Ansi)] + public static extern IntPtr dlsym(IntPtr handle, string symbol); + [DllImport("libdl", SetLastError = true, EntryPoint = "dlopen", CharSet = CharSet.Ansi)] + public static extern IntPtr dlopen(string filename, int flags); + [DllImport("libdl", SetLastError = true, EntryPoint = "dlclose")] + public static extern int dlclose(IntPtr hModule); + [DllImport("libc", SetLastError = true, EntryPoint = "kill")] + public static extern int kill (pid_t pid, int sig); + } - Symbol = typeof(T).Name; - const string delegatePrefix = "_delegate"; + sealed class Delegate where T: Delegate + { + public T Invoke { get; } + public IntPtr Ptr { get; private set; } + public string Symbol { get; } - if (Symbol.EndsWith(delegatePrefix, StringComparison.OrdinalIgnoreCase)) - { - Symbol = Symbol.Substring(0, Symbol.Length - delegatePrefix.Length); - } + public Delegate(IntPtr instancePtr, OSPlatform osPlatform) + { + Ptr = IntPtr.Zero; + Invoke = null; - if (!osPlatform.IsSupportedOsVersion()) - { - throw new PlatformNotSupportedException(); - } + Symbol = typeof(T).Name; + const string delegatePrefix = "_delegate"; - if (osPlatform == OSPlatform.Windows) - { - Ptr = NativeMethodsWindows.dlsym(instancePtr, Symbol); - } else if (osPlatform == OSPlatform.Linux) - { - Ptr = NativeMethodsUnix.dlsym(instancePtr, Symbol); - } + if (Symbol.EndsWith(delegatePrefix, StringComparison.OrdinalIgnoreCase)) + { + Symbol = Symbol.Substring(0, Symbol.Length - delegatePrefix.Length); + } - if (Ptr == IntPtr.Zero) - { - throw new Exception( - $"Failed load function: {Symbol}. Last error: {Marshal.GetLastWin32Error()}."); - } + if (!osPlatform.IsSupportedOsVersion()) + { + throw new PlatformNotSupportedException(); + } - Invoke = Marshal.GetDelegateForFunctionPointer(Ptr); + if (osPlatform == OSPlatform.Windows) + { + Ptr = NativeMethodsWindows.dlsym(instancePtr, Symbol); + } else if (osPlatform == OSPlatform.Linux) + { + Ptr = NativeMethodsUnix.dlsym(instancePtr, Symbol); } - public void ThrowIfDangling() + if (Ptr == IntPtr.Zero) { - if (Ptr == IntPtr.Zero) - { - throw new Exception($"Delegate disposed: {Symbol}."); - } + throw new Exception( + $"Failed load function: {Symbol}. Last error: {Marshal.GetLastWin32Error()}."); } - public void Unref() + Invoke = Marshal.GetDelegateForFunctionPointer(Ptr); + } + + public void ThrowIfDangling() + { + if (Ptr == IntPtr.Zero) { - Ptr = IntPtr.Zero; + throw new Exception($"Delegate disposed: {Symbol}."); } } - + + public void Unref() + { + Ptr = IntPtr.Zero; + } } -} + +} \ No newline at end of file diff --git a/src/Snap/Extensions/CecilExtensions.cs b/src/Snap/Extensions/CecilExtensions.cs index 3bc57a91..16680e43 100644 --- a/src/Snap/Extensions/CecilExtensions.cs +++ b/src/Snap/Extensions/CecilExtensions.cs @@ -5,91 +5,90 @@ using JetBrains.Annotations; using Mono.Cecil; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class CecilExtensions { - internal static class CecilExtensions - { - const string ExpressionCannotBeNullMessage = "The expression cannot be null"; - const string InvalidExpressionMessage = "Invalid expression"; + const string ExpressionCannotBeNullMessage = "The expression cannot be null"; + const string InvalidExpressionMessage = "Invalid expression"; - public static byte[] ToByteArray([NotNull] this AssemblyDefinition assemblyDefinition, WriterParameters writerParameters = null) - { - if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); - using var srcStream = new MemoryStream(); - assemblyDefinition.Write(srcStream, writerParameters ?? new WriterParameters()); - return srcStream.ToArray(); - } + public static byte[] ToByteArray([NotNull] this AssemblyDefinition assemblyDefinition, WriterParameters writerParameters = null) + { + if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); + using var srcStream = new MemoryStream(); + assemblyDefinition.Write(srcStream, writerParameters ?? new WriterParameters()); + return srcStream.ToArray(); + } - public static string BuildMemberName(this Expression> expression) - { - return BuildMemberName(expression.Body); - } + public static string BuildMemberName(this Expression> expression) + { + return BuildMemberName(expression.Body); + } - public static string BuildPropertyGetterSyntax(this Expression> expression) - { - var propertyName = expression.BuildMemberName(); - return $"get_{propertyName}"; - } + public static string BuildPropertyGetterSyntax(this Expression> expression) + { + var propertyName = expression.BuildMemberName(); + return $"get_{propertyName}"; + } - public static string BuildPropertySetterSyntax(this Expression> expression) - { - var propertyName = expression.BuildMemberName(); - return $"set_{propertyName}"; - } + public static string BuildPropertySetterSyntax(this Expression> expression) + { + var propertyName = expression.BuildMemberName(); + return $"set_{propertyName}"; + } - public static TypeDefinition ResolveTypeDefinition([NotNull] this AssemblyDefinition assemblyDefinition) - { - return ResolveTypeDefinitionImpl(assemblyDefinition); - } + public static TypeDefinition ResolveTypeDefinition([NotNull] this AssemblyDefinition assemblyDefinition) + { + return ResolveTypeDefinitionImpl(assemblyDefinition); + } - public static (TypeDefinition typeDefinition, PropertyDefinition propertyDefinition, string getterName, string setterName) ResolveAutoProperty([NotNull] this AssemblyDefinition assemblyDefinition, [NotNull] Expression> selector) - { - if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); - if (selector == null) throw new ArgumentNullException(nameof(selector)); + public static (TypeDefinition typeDefinition, PropertyDefinition propertyDefinition, string getterName, string setterName) ResolveAutoProperty([NotNull] this AssemblyDefinition assemblyDefinition, [NotNull] Expression> selector) + { + if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); + if (selector == null) throw new ArgumentNullException(nameof(selector)); - var getter = selector.BuildPropertyGetterSyntax(); - var setter = selector.BuildPropertySetterSyntax(); + var getter = selector.BuildPropertyGetterSyntax(); + var setter = selector.BuildPropertySetterSyntax(); - var typeDefinition = assemblyDefinition.ResolveTypeDefinition(); - var propertyDefinition = typeDefinition?.Properties.SingleOrDefault(m => - m.GetMethod?.Name == getter || m.SetMethod?.Name == setter); + var typeDefinition = assemblyDefinition.ResolveTypeDefinition(); + var propertyDefinition = typeDefinition?.Properties.SingleOrDefault(m => + m.GetMethod?.Name == getter || m.SetMethod?.Name == setter); - return (typeDefinition, propertyDefinition, getter, setter); - } + return (typeDefinition, propertyDefinition, getter, setter); + } - static string BuildMemberName(Expression expression) + static string BuildMemberName(Expression expression) + { + return expression switch { - return expression switch - { - null => throw new ArgumentException(ExpressionCannotBeNullMessage), - MemberExpression memberExpression => - // Reference type property or field - memberExpression.Member.Name, - MethodCallExpression methodCallExpression => - // Reference type method - methodCallExpression.Method.Name, - UnaryExpression unaryExpression => - // Property, field of method returning value type - BuildMemberName(unaryExpression), - _ => throw new ArgumentException(InvalidExpressionMessage) - }; - } + null => throw new ArgumentException(ExpressionCannotBeNullMessage), + MemberExpression memberExpression => + // Reference type property or field + memberExpression.Member.Name, + MethodCallExpression methodCallExpression => + // Reference type method + methodCallExpression.Method.Name, + UnaryExpression unaryExpression => + // Property, field of method returning value type + BuildMemberName(unaryExpression), + _ => throw new ArgumentException(InvalidExpressionMessage) + }; + } - static string BuildMemberName(UnaryExpression unaryExpression) + static string BuildMemberName(UnaryExpression unaryExpression) + { + if (unaryExpression.Operand is MethodCallExpression methodExpression) { - if (unaryExpression.Operand is MethodCallExpression methodExpression) - { - return methodExpression.Method.Name; - } - - return ((MemberExpression)unaryExpression.Operand).Member.Name; + return methodExpression.Method.Name; } - static TypeDefinition ResolveTypeDefinitionImpl([NotNull] AssemblyDefinition assemblyDefinition) - { - var tSourceFullName = typeof(T).FullName; - var tSource = assemblyDefinition.MainModule.Types.SingleOrDefault(x => x.FullName == tSourceFullName); - return tSource; - } + return ((MemberExpression)unaryExpression.Operand).Member.Name; + } + + static TypeDefinition ResolveTypeDefinitionImpl([NotNull] AssemblyDefinition assemblyDefinition) + { + var tSourceFullName = typeof(T).FullName; + var tSource = assemblyDefinition.MainModule.Types.SingleOrDefault(x => x.FullName == tSourceFullName); + return tSource; } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/EnumerableExtensions.cs b/src/Snap/Extensions/EnumerableExtensions.cs index ba24cd7b..a4763703 100644 --- a/src/Snap/Extensions/EnumerableExtensions.cs +++ b/src/Snap/Extensions/EnumerableExtensions.cs @@ -5,46 +5,46 @@ using System.Threading.Tasks; using JetBrains.Annotations; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class EnumerableExtensions { - internal static class EnumerableExtensions + public static Dictionary> ToDictionaryByKey(this IEnumerable list, Func predicate) { - public static Dictionary> ToDictionaryByKey(this IEnumerable list, Func predicate) - { - return list.GroupBy(predicate).ToDictionary(g => g.Key, values => values.ToList()); - } + return list.GroupBy(predicate).ToDictionary(g => g.Key, values => values.ToList()); + } - public static void ForEach(this IEnumerable source, Action onNext) - { - if (source == null) - throw new ArgumentNullException(nameof(source)); - if (onNext == null) - throw new ArgumentNullException(nameof(onNext)); + public static void ForEach(this IEnumerable source, Action onNext) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (onNext == null) + throw new ArgumentNullException(nameof(onNext)); - foreach (var item in source) onNext(item); - } + foreach (var item in source) onNext(item); + } - public static Task ForEachAsync([NotNull] this IEnumerable source, [NotNull] Func body, int concurrency = 0) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (body == null) throw new ArgumentNullException(nameof(body)); - if (concurrency < 0) throw new ArgumentOutOfRangeException(nameof(concurrency)); + public static Task ForEachAsync([NotNull] this IEnumerable source, [NotNull] Func body, int concurrency = 0) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + if (body == null) throw new ArgumentNullException(nameof(body)); + if (concurrency < 0) throw new ArgumentOutOfRangeException(nameof(concurrency)); - const int maxConcurrency = 8; + const int maxConcurrency = 8; - if (concurrency == 0) - { - concurrency = Environment.ProcessorCount; - } + if (concurrency == 0) + { + concurrency = Environment.ProcessorCount; + } - if (concurrency > maxConcurrency) - { - concurrency = maxConcurrency; - } + if (concurrency > maxConcurrency) + { + concurrency = maxConcurrency; + } - // https://devblogs.microsoft.com/pfxteam/implementing-a-simple-foreachasync-part-2/ - return Task.WhenAll( - Partitioner + // https://devblogs.microsoft.com/pfxteam/implementing-a-simple-foreachasync-part-2/ + return Task.WhenAll( + Partitioner .Create(source) .GetPartitions(concurrency) .Select(partition => Task.Run(async delegate @@ -58,6 +58,5 @@ public static Task ForEachAsync([NotNull] this IEnumerable source, [NotNul } }))); - } - } -} + } +} \ No newline at end of file diff --git a/src/Snap/Extensions/ListExtensions.cs b/src/Snap/Extensions/ListExtensions.cs index fbd83dcc..e779fd42 100644 --- a/src/Snap/Extensions/ListExtensions.cs +++ b/src/Snap/Extensions/ListExtensions.cs @@ -3,15 +3,14 @@ using System.Linq; using JetBrains.Annotations; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class ListExtensions { - internal static class ListExtensions + public static IEnumerable DistinctBy([NotNull] this IEnumerable list, [NotNull] Func keySelector) { - public static IEnumerable DistinctBy([NotNull] this IEnumerable list, [NotNull] Func keySelector) - { - if (list == null) throw new ArgumentNullException(nameof(list)); - if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); - return list.GroupBy(keySelector).Select(x => x.First()); - } + if (list == null) throw new ArgumentNullException(nameof(list)); + if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); + return list.GroupBy(keySelector).Select(x => x.First()); } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/LoggerExtensions.cs b/src/Snap/Extensions/LoggerExtensions.cs index 2f966691..a7452694 100644 --- a/src/Snap/Extensions/LoggerExtensions.cs +++ b/src/Snap/Extensions/LoggerExtensions.cs @@ -4,154 +4,153 @@ using JetBrains.Annotations; using Snap.Logging; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class LoggerExtensions { - internal static class LoggerExtensions + public static bool Prompt([NotNull] this ILog logger, [NotNull] string verbsStr, [NotNull] string question, char delimeter = '|', bool warn = false, bool infoOnly = false) { - public static bool Prompt([NotNull] this ILog logger, [NotNull] string verbsStr, [NotNull] string question, char delimeter = '|', bool warn = false, bool infoOnly = false) + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (verbsStr == null) throw new ArgumentNullException(nameof(verbsStr)); + if (question == null) throw new ArgumentNullException(nameof(question)); + if (delimeter <= 0) throw new ArgumentOutOfRangeException(nameof(delimeter)); + var foregroundColor = Console.ForegroundColor; + if (warn) { - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (verbsStr == null) throw new ArgumentNullException(nameof(verbsStr)); - if (question == null) throw new ArgumentNullException(nameof(question)); - if (delimeter <= 0) throw new ArgumentOutOfRangeException(nameof(delimeter)); - var foregroundColor = Console.ForegroundColor; - if (warn) - { - Console.ForegroundColor = ConsoleColor.Magenta; - } - logger.Info(question); - if (warn) - { - Console.ForegroundColor = foregroundColor; - } - if (infoOnly) - { - Console.Write("y"); - return true; - } - var verbs = verbsStr.Split(delimeter).ToList(); - var value = Console.ReadLine(); - return verbs.Any(verb => string.Equals(value, verb, StringComparison.OrdinalIgnoreCase)); + Console.ForegroundColor = ConsoleColor.Magenta; } - - public static void InfoWithDashses([NotNull] this ILog This, [NotNull] string message) + logger.Info(question); + if (warn) + { + Console.ForegroundColor = foregroundColor; + } + if (infoOnly) { - if (This == null) throw new ArgumentNullException(nameof(This)); - if (message == null) throw new ArgumentNullException(nameof(message)); - This.Info(message); - This.Info('-'.Repeat(message.Length)); + Console.Write("y"); + return true; } + var verbs = verbsStr.Split(delimeter).ToList(); + var value = Console.ReadLine(); + return verbs.Any(verb => string.Equals(value, verb, StringComparison.OrdinalIgnoreCase)); + } + + public static void InfoWithDashses([NotNull] this ILog This, [NotNull] string message) + { + if (This == null) throw new ArgumentNullException(nameof(This)); + if (message == null) throw new ArgumentNullException(nameof(message)); + This.Info(message); + This.Info('-'.Repeat(message.Length)); + } - public static void LogIfThrows(this ILog This, LogLevel level, string message, Action block) + public static void LogIfThrows(this ILog This, LogLevel level, string message, Action block) + { + try + { + block(); + } + catch (Exception ex) { - try + switch (level) { - block(); + case LogLevel.Debug: + This.DebugException(message ?? "", ex); + break; + case LogLevel.Info: + This.InfoException(message ?? "", ex); + break; + case LogLevel.Warn: + This.WarnException(message ?? "", ex); + break; + case LogLevel.Error: + This.ErrorException(message ?? "", ex); + break; } - catch (Exception ex) - { - switch (level) - { - case LogLevel.Debug: - This.DebugException(message ?? "", ex); - break; - case LogLevel.Info: - This.InfoException(message ?? "", ex); - break; - case LogLevel.Warn: - This.WarnException(message ?? "", ex); - break; - case LogLevel.Error: - This.ErrorException(message ?? "", ex); - break; - } - throw; - } + throw; } + } - public static async Task LogIfThrows(this ILog This, LogLevel level, string message, Func block) + public static async Task LogIfThrows(this ILog This, LogLevel level, string message, Func block) + { + try { - try - { - await block(); - } - catch (Exception ex) - { - switch (level) - { - case LogLevel.Debug: - This.DebugException(message ?? "", ex); - break; - case LogLevel.Info: - This.InfoException(message ?? "", ex); - break; - case LogLevel.Warn: - This.WarnException(message ?? "", ex); - break; - case LogLevel.Error: - This.ErrorException(message ?? "", ex); - break; - } - throw; - } + await block(); } - - public static async Task LogIfThrows(this ILog This, LogLevel level, string message, Func> block) + catch (Exception ex) { - try + switch (level) { - return await block(); - } - catch (Exception ex) - { - switch (level) - { - case LogLevel.Debug: - This.DebugException(message ?? "", ex); - break; - case LogLevel.Info: - This.InfoException(message ?? "", ex); - break; - case LogLevel.Warn: - This.WarnException(message ?? "", ex); - break; - case LogLevel.Error: - This.ErrorException(message ?? "", ex); - break; - } - throw; + case LogLevel.Debug: + This.DebugException(message ?? "", ex); + break; + case LogLevel.Info: + This.InfoException(message ?? "", ex); + break; + case LogLevel.Warn: + This.WarnException(message ?? "", ex); + break; + case LogLevel.Error: + This.ErrorException(message ?? "", ex); + break; } + throw; } + } - public static void WarnIfThrows(this ILog This, Action block, string message = null) + public static async Task LogIfThrows(this ILog This, LogLevel level, string message, Func> block) + { + try { - This.LogIfThrows(LogLevel.Warn, message, block); + return await block(); } - - public static Task WarnIfThrows(this ILog This, Func block, string message = null) + catch (Exception ex) { - return This.LogIfThrows(LogLevel.Warn, message, block); + switch (level) + { + case LogLevel.Debug: + This.DebugException(message ?? "", ex); + break; + case LogLevel.Info: + This.InfoException(message ?? "", ex); + break; + case LogLevel.Warn: + This.WarnException(message ?? "", ex); + break; + case LogLevel.Error: + This.ErrorException(message ?? "", ex); + break; + } + throw; } + } - public static Task WarnIfThrows(this ILog This, Func> block, string message = null) - { - return This.LogIfThrows(LogLevel.Warn, message, block); - } + public static void WarnIfThrows(this ILog This, Action block, string message = null) + { + This.LogIfThrows(LogLevel.Warn, message, block); + } - public static void ErrorIfThrows(this ILog This, Action block, string message = null) - { - This.LogIfThrows(LogLevel.Error, message, block); - } + public static Task WarnIfThrows(this ILog This, Func block, string message = null) + { + return This.LogIfThrows(LogLevel.Warn, message, block); + } - public static Task ErrorIfThrows(this ILog This, Func block, string message = null) - { - return This.LogIfThrows(LogLevel.Error, message, block); - } + public static Task WarnIfThrows(this ILog This, Func> block, string message = null) + { + return This.LogIfThrows(LogLevel.Warn, message, block); + } - public static Task ErrorIfThrows(this ILog This, Func> block, string message = null) - { - return This.LogIfThrows(LogLevel.Error, message, block); - } + public static void ErrorIfThrows(this ILog This, Action block, string message = null) + { + This.LogIfThrows(LogLevel.Error, message, block); + } + + public static Task ErrorIfThrows(this ILog This, Func block, string message = null) + { + return This.LogIfThrows(LogLevel.Error, message, block); } -} + + public static Task ErrorIfThrows(this ILog This, Func> block, string message = null) + { + return This.LogIfThrows(LogLevel.Error, message, block); + } +} \ No newline at end of file diff --git a/src/Snap/Extensions/NuGetExtensions.cs b/src/Snap/Extensions/NuGetExtensions.cs index 1e28a82a..eab99db9 100644 --- a/src/Snap/Extensions/NuGetExtensions.cs +++ b/src/Snap/Extensions/NuGetExtensions.cs @@ -17,57 +17,57 @@ using Snap.Core.Models; using Snap.NuGet; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class NuGetExtensions { - internal static class NuGetExtensions + public static bool IsUncPathSafe(this PackageSource packageSource) { - public static bool IsUncPathSafe(this PackageSource packageSource) + try { - try - { - return packageSource != null - && packageSource.SourceUri != null - && packageSource.SourceUri.IsUnc - && packageSource.SourceUri.IsAbsoluteUri; - } - catch - { - return false; - } + return packageSource != null + && packageSource.SourceUri != null + && packageSource.SourceUri.IsUnc + && packageSource.SourceUri.IsAbsoluteUri; } - - public static bool IsLocalPathSafe(this PackageSource packageSource) + catch { - try - { - return packageSource != null - && packageSource.SourceUri != null - && packageSource.SourceUri.IsAbsoluteUri - && packageSource.IsLocal; - } - catch - { - return false; - } + return false; } + } - public static bool IsLocalOrUncPath(this PackageSource packageSource) + public static bool IsLocalPathSafe(this PackageSource packageSource) + { + try { - return packageSource.IsLocalPathSafe() || packageSource.IsUncPathSafe(); + return packageSource != null + && packageSource.SourceUri != null + && packageSource.SourceUri.IsAbsoluteUri + && packageSource.IsLocal; } - - public static XElement SingleOrDefault([NotNull] this XDocument xDocument, [NotNull] XName name, bool ignoreCase = true) + catch { - if (xDocument == null) throw new ArgumentNullException(nameof(xDocument)); - if (name == null) throw new ArgumentNullException(nameof(name)); - return xDocument.Descendants().SingleOrDefault(name, ignoreCase); + return false; } + } - public static XElement SingleOrDefault([NotNull] this IEnumerable xElements, [NotNull] XName name, bool ignoreCase = true) - { - if (xElements == null) throw new ArgumentNullException(nameof(xElements)); - if (name == null) throw new ArgumentNullException(nameof(name)); - return ( + public static bool IsLocalOrUncPath(this PackageSource packageSource) + { + return packageSource.IsLocalPathSafe() || packageSource.IsUncPathSafe(); + } + + public static XElement SingleOrDefault([NotNull] this XDocument xDocument, [NotNull] XName name, bool ignoreCase = true) + { + if (xDocument == null) throw new ArgumentNullException(nameof(xDocument)); + if (name == null) throw new ArgumentNullException(nameof(name)); + return xDocument.Descendants().SingleOrDefault(name, ignoreCase); + } + + public static XElement SingleOrDefault([NotNull] this IEnumerable xElements, [NotNull] XName name, bool ignoreCase = true) + { + if (xElements == null) throw new ArgumentNullException(nameof(xElements)); + if (name == null) throw new ArgumentNullException(nameof(name)); + return ( from node in xElements let comperator = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal where @@ -75,176 +75,175 @@ from node in xElements && string.Equals(node.Name.NamespaceName, name.NamespaceName, comperator) select node) .FirstOrDefault(); - } - - internal static async Task BuildDownloadUrlV3Async([NotNull] this DownloadResourceV3 downloadResourceV3, [NotNull] PackageIdentity identity, - [NotNull] ILogger logger, CancellationToken token) - { - if (downloadResourceV3 == null) throw new ArgumentNullException(nameof(downloadResourceV3)); - if (identity == null) throw new ArgumentNullException(nameof(identity)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (downloadResourceV3 == null) throw new ArgumentNullException(nameof(downloadResourceV3)); - - var type = downloadResourceV3.GetType(); - - const string getDownloadUrlMethodName = "GetDownloadUrl"; - var getDownloadUrlMethod = type.GetMethod(getDownloadUrlMethodName, BindingFlags.NonPublic | BindingFlags.Instance); - if (getDownloadUrlMethod == null) - { - throw new MissingMethodException(getDownloadUrlMethodName); - } + } - var getDownloadUrlTask = (dynamic) getDownloadUrlMethod.Invoke(downloadResourceV3, new object[] {identity, logger, token}); - if (getDownloadUrlTask != null) - { - var downloadUri = await getDownloadUrlTask; + internal static async Task BuildDownloadUrlV3Async([NotNull] this DownloadResourceV3 downloadResourceV3, [NotNull] PackageIdentity identity, + [NotNull] ILogger logger, CancellationToken token) + { + if (downloadResourceV3 == null) throw new ArgumentNullException(nameof(downloadResourceV3)); + if (identity == null) throw new ArgumentNullException(nameof(identity)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (downloadResourceV3 == null) throw new ArgumentNullException(nameof(downloadResourceV3)); - return downloadUri as Uri; - } + var type = downloadResourceV3.GetType(); - return null; + const string getDownloadUrlMethodName = "GetDownloadUrl"; + var getDownloadUrlMethod = type.GetMethod(getDownloadUrlMethodName, BindingFlags.NonPublic | BindingFlags.Instance); + if (getDownloadUrlMethod == null) + { + throw new MissingMethodException(getDownloadUrlMethodName); } - internal static HttpSource BuildHttpSource([NotNull] this DownloadResourceV3 downloadResourceV3) + var getDownloadUrlTask = (dynamic) getDownloadUrlMethod.Invoke(downloadResourceV3, new object[] {identity, logger, token}); + if (getDownloadUrlTask != null) { - if (downloadResourceV3 == null) throw new ArgumentNullException(nameof(downloadResourceV3)); + var downloadUri = await getDownloadUrlTask; + + return downloadUri as Uri; + } - var type = downloadResourceV3.GetType(); + return null; + } - const string httpSourcePrivateReadonlyFieldName = "_client"; - var httpSource = type.GetField(httpSourcePrivateReadonlyFieldName, BindingFlags.NonPublic | BindingFlags.Instance); - if (httpSource == null) - { - throw new MissingFieldException(httpSourcePrivateReadonlyFieldName); - } + internal static HttpSource BuildHttpSource([NotNull] this DownloadResourceV3 downloadResourceV3) + { + if (downloadResourceV3 == null) throw new ArgumentNullException(nameof(downloadResourceV3)); - return httpSource.GetValue(downloadResourceV3) as HttpSource; - } + var type = downloadResourceV3.GetType(); - internal static HttpSource BuildHttpSource([NotNull] this V2FeedParser v2FeedParser) + const string httpSourcePrivateReadonlyFieldName = "_client"; + var httpSource = type.GetField(httpSourcePrivateReadonlyFieldName, BindingFlags.NonPublic | BindingFlags.Instance); + if (httpSource == null) { - if (v2FeedParser == null) throw new ArgumentNullException(nameof(v2FeedParser)); + throw new MissingFieldException(httpSourcePrivateReadonlyFieldName); + } - var type = v2FeedParser.GetType(); + return httpSource.GetValue(downloadResourceV3) as HttpSource; + } - const string httpSourcePrivateReadonlyFieldName = "_httpSource"; - var httpSource = type.GetField(httpSourcePrivateReadonlyFieldName, BindingFlags.NonPublic | BindingFlags.Instance); - if (httpSource == null) - { - throw new MissingFieldException(httpSourcePrivateReadonlyFieldName); - } + internal static HttpSource BuildHttpSource([NotNull] this V2FeedParser v2FeedParser) + { + if (v2FeedParser == null) throw new ArgumentNullException(nameof(v2FeedParser)); - return httpSource.GetValue(v2FeedParser) as HttpSource; - } + var type = v2FeedParser.GetType(); - internal static V2FeedParser BuildV2FeedParser([NotNull] this DownloadResourceV2Feed downloadResourceV2Feed) + const string httpSourcePrivateReadonlyFieldName = "_httpSource"; + var httpSource = type.GetField(httpSourcePrivateReadonlyFieldName, BindingFlags.NonPublic | BindingFlags.Instance); + if (httpSource == null) { - if (downloadResourceV2Feed == null) throw new ArgumentNullException(nameof(downloadResourceV2Feed)); + throw new MissingFieldException(httpSourcePrivateReadonlyFieldName); + } - var type = downloadResourceV2Feed.GetType(); + return httpSource.GetValue(v2FeedParser) as HttpSource; + } - const string feedParserPrivateReadonlyFieldName = "_feedParser"; - var v2FeedParser = type.GetField(feedParserPrivateReadonlyFieldName, BindingFlags.NonPublic | BindingFlags.Instance); - if (v2FeedParser == null) - { - throw new MissingFieldException(feedParserPrivateReadonlyFieldName); - } + internal static V2FeedParser BuildV2FeedParser([NotNull] this DownloadResourceV2Feed downloadResourceV2Feed) + { + if (downloadResourceV2Feed == null) throw new ArgumentNullException(nameof(downloadResourceV2Feed)); - return v2FeedParser.GetValue(downloadResourceV2Feed) as V2FeedParser; - } + var type = downloadResourceV2Feed.GetType(); - internal static PackageIdentity BuildPackageIdentity([NotNull] this SnapRelease snapRelease) + const string feedParserPrivateReadonlyFieldName = "_feedParser"; + var v2FeedParser = type.GetField(feedParserPrivateReadonlyFieldName, BindingFlags.NonPublic | BindingFlags.Instance); + if (v2FeedParser == null) { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - return new PackageIdentity(snapRelease.UpstreamId, snapRelease.Version.ToNuGetVersion()); + throw new MissingFieldException(feedParserPrivateReadonlyFieldName); } - internal static PackageIdentity BuildPackageIdentity([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return new PackageIdentity(snapApp.BuildNugetUpstreamId(), snapApp.Version.ToNuGetVersion()); - } + return v2FeedParser.GetValue(downloadResourceV2Feed) as V2FeedParser; + } - internal static NuGetPackageSearchMedatadata BuildPackageSearchMetadata([NotNull] this SnapApp snapApp, - [NotNull] INuGetPackageSources nugetSources) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (nugetSources == null) throw new ArgumentNullException(nameof(nugetSources)); + internal static PackageIdentity BuildPackageIdentity([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + return new PackageIdentity(snapRelease.UpstreamId, snapRelease.Version.ToNuGetVersion()); + } - var channel = snapApp.GetCurrentChannelOrThrow(); - var updateFeed = (SnapNugetFeed) channel.UpdateFeed; - var packageSource = nugetSources.Items.Single(x => x.Name == updateFeed.Name && x.SourceUri == updateFeed.Source); + internal static PackageIdentity BuildPackageIdentity([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return new PackageIdentity(snapApp.BuildNugetUpstreamId(), snapApp.Version.ToNuGetVersion()); + } - return new NuGetPackageSearchMedatadata(snapApp.BuildPackageIdentity(), packageSource, DateTimeOffset.Now, new List()); - } + internal static NuGetPackageSearchMedatadata BuildPackageSearchMetadata([NotNull] this SnapApp snapApp, + [NotNull] INuGetPackageSources nugetSources) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (nugetSources == null) throw new ArgumentNullException(nameof(nugetSources)); - internal static DownloadResourceResult BuildDownloadResourceResult([NotNull] this SnapApp snapApp, - [NotNull] MemoryStream packageStream, [NotNull] INuGetPackageSources nugetSources) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (packageStream == null) throw new ArgumentNullException(nameof(packageStream)); - if (nugetSources == null) throw new ArgumentNullException(nameof(nugetSources)); + var channel = snapApp.GetCurrentChannelOrThrow(); + var updateFeed = (SnapNugetFeed) channel.UpdateFeed; + var packageSource = nugetSources.Items.Single(x => x.Name == updateFeed.Name && x.SourceUri == updateFeed.Source); - var channel = snapApp.GetCurrentChannelOrThrow(); - var updateFeed = (SnapNugetFeed) channel.UpdateFeed; - var packageSource = nugetSources.Items.Single(x => x.Name == updateFeed.Name && x.SourceUri == updateFeed.Source); + return new NuGetPackageSearchMedatadata(snapApp.BuildPackageIdentity(), packageSource, DateTimeOffset.Now, new List()); + } - return new DownloadResourceResult(new MemoryStream(packageStream.ToArray()), new PackageArchiveReader(packageStream), packageSource.Name); - } + internal static DownloadResourceResult BuildDownloadResourceResult([NotNull] this SnapApp snapApp, + [NotNull] MemoryStream packageStream, [NotNull] INuGetPackageSources nugetSources) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (packageStream == null) throw new ArgumentNullException(nameof(packageStream)); + if (nugetSources == null) throw new ArgumentNullException(nameof(nugetSources)); - internal static bool SuccessSafe(this DownloadResourceResult downloadResourceResult) - { - return downloadResourceResult != null && downloadResourceResult.Status == DownloadResourceResultStatus.Available; - } + var channel = snapApp.GetCurrentChannelOrThrow(); + var updateFeed = (SnapNugetFeed) channel.UpdateFeed; + var packageSource = nugetSources.Items.Single(x => x.Name == updateFeed.Name && x.SourceUri == updateFeed.Source); - public static bool RemovePackageFile([NotNull] this PackageBuilder packageBuilder, [NotNull] string targetPath, StringComparison stringComparisonType) - { - if (packageBuilder == null) throw new ArgumentNullException(nameof(packageBuilder)); - if (targetPath == null) throw new ArgumentNullException(nameof(targetPath)); - var packageFile = packageBuilder.GetPackageFile(targetPath, stringComparisonType); - return packageFile != null && packageBuilder.Files.Remove(packageFile); - } + return new DownloadResourceResult(new MemoryStream(packageStream.ToArray()), new PackageArchiveReader(packageStream), packageSource.Name); + } - public static IPackageFile GetPackageFile([NotNull] this PackageBuilder packageBuilder, [NotNull] string filename, StringComparison stringComparisonType) - { - if (packageBuilder == null) throw new ArgumentNullException(nameof(packageBuilder)); - if (filename == null) throw new ArgumentNullException(nameof(filename)); + internal static bool SuccessSafe(this DownloadResourceResult downloadResourceResult) + { + return downloadResourceResult != null && downloadResourceResult.Status == DownloadResourceResultStatus.Available; + } - return packageBuilder.Files.SingleOrDefault(x => string.Equals( - filename.ForwardSlashesSafe(), - x.Path.ForwardSlashesSafe(), - stringComparisonType)); - } + public static bool RemovePackageFile([NotNull] this PackageBuilder packageBuilder, [NotNull] string targetPath, StringComparison stringComparisonType) + { + if (packageBuilder == null) throw new ArgumentNullException(nameof(packageBuilder)); + if (targetPath == null) throw new ArgumentNullException(nameof(targetPath)); + var packageFile = packageBuilder.GetPackageFile(targetPath, stringComparisonType); + return packageFile != null && packageBuilder.Files.Remove(packageFile); + } - internal static async Task GetManifestMetadataAsync([NotNull] this IAsyncPackageCoreReader asyncPackageCoreReader, - CancellationToken cancellationToken) - { - if (asyncPackageCoreReader == null) throw new ArgumentNullException(nameof(asyncPackageCoreReader)); - await using var nuspecStream = await asyncPackageCoreReader.GetNuspecAsync(cancellationToken).ReadToEndAsync(cancellationToken: cancellationToken); - return Manifest.ReadFrom(nuspecStream, false)?.Metadata; - } + public static IPackageFile GetPackageFile([NotNull] this PackageBuilder packageBuilder, [NotNull] string filename, StringComparison stringComparisonType) + { + if (packageBuilder == null) throw new ArgumentNullException(nameof(packageBuilder)); + if (filename == null) throw new ArgumentNullException(nameof(filename)); - static bool IsPasswordEncryptionSupportedImpl() - { - return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - } + return packageBuilder.Files.SingleOrDefault(x => string.Equals( + filename.ForwardSlashesSafe(), + x.Path.ForwardSlashesSafe(), + stringComparisonType)); + } - internal static bool IsPasswordEncryptionSupported([NotNull] this SnapNugetFeed nugetFeed) - { - if (nugetFeed == null) throw new ArgumentNullException(nameof(nugetFeed)); - return IsPasswordEncryptionSupportedImpl(); - } + internal static async Task GetManifestMetadataAsync([NotNull] this IAsyncPackageCoreReader asyncPackageCoreReader, + CancellationToken cancellationToken) + { + if (asyncPackageCoreReader == null) throw new ArgumentNullException(nameof(asyncPackageCoreReader)); + await using var nuspecStream = await asyncPackageCoreReader.GetNuspecAsync(cancellationToken).ReadToEndAsync(cancellationToken: cancellationToken); + return Manifest.ReadFrom(nuspecStream, false)?.Metadata; + } - internal static bool IsPasswordEncryptionSupported([NotNull] this ISettings settings) - { - if (settings == null) throw new ArgumentNullException(nameof(settings)); - return IsPasswordEncryptionSupportedImpl(); - } + static bool IsPasswordEncryptionSupportedImpl() + { + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + } - internal static bool IsPasswordEncryptionSupported([NotNull] this INuGetPackageSources packageSources) - { - if (packageSources == null) throw new ArgumentNullException(nameof(packageSources)); - return IsPasswordEncryptionSupportedImpl(); - } + internal static bool IsPasswordEncryptionSupported([NotNull] this SnapNugetFeed nugetFeed) + { + if (nugetFeed == null) throw new ArgumentNullException(nameof(nugetFeed)); + return IsPasswordEncryptionSupportedImpl(); + } + + internal static bool IsPasswordEncryptionSupported([NotNull] this ISettings settings) + { + if (settings == null) throw new ArgumentNullException(nameof(settings)); + return IsPasswordEncryptionSupportedImpl(); + } + + internal static bool IsPasswordEncryptionSupported([NotNull] this INuGetPackageSources packageSources) + { + if (packageSources == null) throw new ArgumentNullException(nameof(packageSources)); + return IsPasswordEncryptionSupportedImpl(); } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/NumberExtensions.cs b/src/Snap/Extensions/NumberExtensions.cs index a15a224e..f6cf5632 100644 --- a/src/Snap/Extensions/NumberExtensions.cs +++ b/src/Snap/Extensions/NumberExtensions.cs @@ -1,22 +1,21 @@ using System; using System.Globalization; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class NumberExtensions { - internal static class NumberExtensions - { - static readonly string[] ByteSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; + static readonly string[] ByteSuffixes = { "B", "KB", "MB", "GB", "TB", "PB", "EB" }; - internal static string BytesAsHumanReadable(this long byteCount) + internal static string BytesAsHumanReadable(this long byteCount) + { + if (byteCount == 0) { - if (byteCount == 0) - { - return "0" + ByteSuffixes[0]; - } - var bytes = Math.Abs(byteCount); - var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); - var num = Math.Round(bytes / Math.Pow(1024, place), 1); - return (Math.Sign(byteCount) * num).ToString(CultureInfo.InvariantCulture) + ByteSuffixes[place]; + return "0" + ByteSuffixes[0]; } + var bytes = Math.Abs(byteCount); + var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024))); + var num = Math.Round(bytes / Math.Pow(1024, place), 1); + return (Math.Sign(byteCount) * num).ToString(CultureInfo.InvariantCulture) + ByteSuffixes[place]; } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/PeUtilityExtensions.cs b/src/Snap/Extensions/PeUtilityExtensions.cs index 808e8848..a828a6f6 100644 --- a/src/Snap/Extensions/PeUtilityExtensions.cs +++ b/src/Snap/Extensions/PeUtilityExtensions.cs @@ -5,64 +5,63 @@ using Snap.AnyOS.Windows; using Snap.Logging; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class PeUtilityExtensions { - internal static class PeUtilityExtensions + [UsedImplicitly] + public static (PeUtility.SubSystemType subSystemType, bool is32Bit, bool is64Bit) GetPeDetails([NotNull] this Stream srcStream) { - [UsedImplicitly] - public static (PeUtility.SubSystemType subSystemType, bool is32Bit, bool is64Bit) GetPeDetails([NotNull] this Stream srcStream) - { - if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); - using var peFile = new PeUtility(srcStream); - var subsysVal = peFile.Is32BitHeader ? (PeUtility.SubSystemType)peFile.OptionalHeader32.Subsystem : (PeUtility.SubSystemType)peFile.OptionalHeader64.Subsystem; + if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); + using var peFile = new PeUtility(srcStream); + var subsysVal = peFile.Is32BitHeader ? (PeUtility.SubSystemType)peFile.OptionalHeader32.Subsystem : (PeUtility.SubSystemType)peFile.OptionalHeader64.Subsystem; - return (subsysVal, peFile.Is32BitHeader, !peFile.Is32BitHeader); - } + return (subsysVal, peFile.Is32BitHeader, !peFile.Is32BitHeader); + } - public static bool ChangeSubsystemToWindowsGui([NotNull] this Stream srcStream, ILog logger = null) - { - if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); - using var peFile = new PeUtility(srcStream); - var subsysOffset = peFile.MainHeaderOffset; - var headerType = peFile.Is32BitHeader ? - typeof(PeUtility.IMAGE_OPTIONAL_HEADER32) : - typeof(PeUtility.IMAGE_OPTIONAL_HEADER64); + public static bool ChangeSubsystemToWindowsGui([NotNull] this Stream srcStream, ILog logger = null) + { + if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); + using var peFile = new PeUtility(srcStream); + var subsysOffset = peFile.MainHeaderOffset; + var headerType = peFile.Is32BitHeader ? + typeof(PeUtility.IMAGE_OPTIONAL_HEADER32) : + typeof(PeUtility.IMAGE_OPTIONAL_HEADER64); - var subsysVal = peFile.Is32BitHeader ? - (PeUtility.SubSystemType)peFile.OptionalHeader32.Subsystem : - (PeUtility.SubSystemType)peFile.OptionalHeader64.Subsystem; + var subsysVal = peFile.Is32BitHeader ? + (PeUtility.SubSystemType)peFile.OptionalHeader32.Subsystem : + (PeUtility.SubSystemType)peFile.OptionalHeader64.Subsystem; - subsysOffset += Marshal.OffsetOf(headerType, "Subsystem").ToInt32(); + subsysOffset += Marshal.OffsetOf(headerType, "Subsystem").ToInt32(); - switch (subsysVal) - { - case PeUtility.SubSystemType.IMAGE_SUBSYSTEM_WINDOWS_GUI: - logger?.Info("Executable file is already a Win32 App!"); - return true; - case PeUtility.SubSystemType.IMAGE_SUBSYSTEM_WINDOWS_CUI: + switch (subsysVal) + { + case PeUtility.SubSystemType.IMAGE_SUBSYSTEM_WINDOWS_GUI: + logger?.Info("Executable file is already a Win32 App!"); + return true; + case PeUtility.SubSystemType.IMAGE_SUBSYSTEM_WINDOWS_CUI: - var subsysSetting = BitConverter.GetBytes((ushort)PeUtility.SubSystemType.IMAGE_SUBSYSTEM_WINDOWS_GUI); + var subsysSetting = BitConverter.GetBytes((ushort)PeUtility.SubSystemType.IMAGE_SUBSYSTEM_WINDOWS_GUI); - if (!BitConverter.IsLittleEndian) - { - Array.Reverse(subsysSetting); - } + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(subsysSetting); + } - if (peFile.Stream.CanWrite) - { - peFile.Stream.Seek(subsysOffset, SeekOrigin.Begin); - peFile.Stream.Write(subsysSetting, 0, subsysSetting.Length); - } - else - { - logger.Error("Can't write changes! Conversion failed..."); - } + if (peFile.Stream.CanWrite) + { + peFile.Stream.Seek(subsysOffset, SeekOrigin.Begin); + peFile.Stream.Write(subsysSetting, 0, subsysSetting.Length); + } + else + { + logger.Error("Can't write changes! Conversion failed..."); + } - return true; - default: - logger.Error($"Unsupported subsystem : {Enum.GetName(typeof(PeUtility.SubSystemType), subsysVal)}."); - return false; - } + return true; + default: + logger.Error($"Unsupported subsystem : {Enum.GetName(typeof(PeUtility.SubSystemType), subsysVal)}."); + return false; } } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/SnapExtensions.cs b/src/Snap/Extensions/SnapExtensions.cs index ef1a6d94..8fb8c1ec 100644 --- a/src/Snap/Extensions/SnapExtensions.cs +++ b/src/Snap/Extensions/SnapExtensions.cs @@ -15,18 +15,18 @@ using Snap.NuGet; using Snap.Reflection; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class SnapExtensions { - internal static class SnapExtensions - { - static readonly Regex AppIdRegex = new(@"^\w+([._-]\w+)*$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); - static readonly Regex ChannelNameRegex = new(@"^[a-zA-Z0-9]+$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); - static readonly Regex NetAppRegex = new("^(netcoreapp|net)\\d{1}.\\d{1}$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); - static readonly Regex ExpansionRegex = new("((\\$[0-9A-Za-z\\\\_]*)\\$)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); + static readonly Regex AppIdRegex = new(@"^\w+([._-]\w+)*$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); + static readonly Regex ChannelNameRegex = new(@"^[a-zA-Z0-9]+$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); + static readonly Regex NetAppRegex = new("^(netcoreapp|net)\\d{1}.\\d{1}$", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); + static readonly Regex ExpansionRegex = new("((\\$[0-9A-Za-z\\\\_]*)\\$)", RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture | RegexOptions.Compiled); - internal static string BuildRid(this OSPlatform osPlatform) - { - #if SNAPX_ALLOW_RID_OVERRIDE + internal static string BuildRid(this OSPlatform osPlatform) + { +#if SNAPX_ALLOW_RID_OVERRIDE var supportedRids = new List { "win-x86", @@ -44,682 +44,681 @@ internal static string BuildRid(this OSPlatform osPlatform) } return ridOverride; } - #endif - - if (osPlatform == OSPlatform.Windows) - { - return RuntimeInformation.ProcessArchitecture == Architecture.X86 ? "win-x86" : "win-x64"; - } - - if (osPlatform == OSPlatform.Linux) - { - return RuntimeInformation.ProcessArchitecture == Architecture.X64 ? "linux-x64" : "linux-arm64"; - } +#endif - throw new PlatformNotSupportedException(); + if (osPlatform == OSPlatform.Windows) + { + return RuntimeInformation.ProcessArchitecture == Architecture.X86 ? "win-x86" : "win-x64"; } - internal static void SetRidUsingCurrentOsPlatformAndProcessArchitecture([NotNull] this SnapApp snapApp) + if (osPlatform == OSPlatform.Linux) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return RuntimeInformation.ProcessArchitecture == Architecture.X64 ? "linux-x64" : "linux-arm64"; + } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - snapApp.Target.Os = OSPlatform.Windows; - snapApp.Target.Rid = snapApp.Target.Os.BuildRid(); - return; - } + throw new PlatformNotSupportedException(); + } - if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - snapApp.Target.Os = OSPlatform.Linux; - snapApp.Target.Rid = snapApp.Target.Os.BuildRid(); - return; - } + internal static void SetRidUsingCurrentOsPlatformAndProcessArchitecture([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - throw new PlatformNotSupportedException(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + snapApp.Target.Os = OSPlatform.Windows; + snapApp.Target.Rid = snapApp.Target.Os.BuildRid(); + return; } - internal static string ExpandProperties([NotNull] this string value, [NotNull] Dictionary properties) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { - if (value == null) throw new ArgumentNullException(nameof(value)); - if (properties == null) throw new ArgumentNullException(nameof(properties)); - if (properties.Count == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(properties)); + snapApp.Target.Os = OSPlatform.Linux; + snapApp.Target.Rid = snapApp.Target.Os.BuildRid(); + return; + } - // ReSharper disable once RedundantEnumerableCastCall - foreach (var match in ExpansionRegex.Matches(value).Cast()) - { - var key = match.Value.Replace("$", string.Empty); + throw new PlatformNotSupportedException(); + } - if (!properties.ContainsKey(key)) - { - throw new Exception($"Failed to expand key: {key}."); - } + internal static string ExpandProperties([NotNull] this string value, [NotNull] Dictionary properties) + { + if (value == null) throw new ArgumentNullException(nameof(value)); + if (properties == null) throw new ArgumentNullException(nameof(properties)); + if (properties.Count == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(properties)); - value = value.Replace(match.Value, properties[key]); + // ReSharper disable once RedundantEnumerableCastCall + foreach (var match in ExpansionRegex.Matches(value).Cast()) + { + var key = match.Value.Replace("$", string.Empty); + + if (!properties.ContainsKey(key)) + { + throw new Exception($"Failed to expand key: {key}."); } - return value; + value = value.Replace(match.Value, properties[key]); } - internal static bool IsNetAppSafe(this string framework) - { - return framework != null && NetAppRegex.IsMatch(framework); - } + return value; + } - internal static bool IsNetFrameworkValidSafe(this string framework) - { - return framework.IsNetAppSafe(); - } + internal static bool IsNetAppSafe(this string framework) + { + return framework != null && NetAppRegex.IsMatch(framework); + } - internal static bool IsRuntimeIdentifierValidSafe(this string runtimeIdentifier) - { - return runtimeIdentifier != null && ( - runtimeIdentifier == "win-x86" - || runtimeIdentifier == "win-x64" - || runtimeIdentifier == "linux-x64" - || runtimeIdentifier == "linux-arm64"); - } + internal static bool IsNetFrameworkValidSafe(this string framework) + { + return framework.IsNetAppSafe(); + } + + internal static bool IsRuntimeIdentifierValidSafe(this string runtimeIdentifier) + { + return runtimeIdentifier != null && ( + runtimeIdentifier == "win-x86" + || runtimeIdentifier == "win-x64" + || runtimeIdentifier == "linux-x64" + || runtimeIdentifier == "linux-arm64"); + } - internal static SnapChannel GetDefaultChannelOrThrow([NotNull] this SnapApp snapApp) + internal static SnapChannel GetDefaultChannelOrThrow([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + var channel = snapApp.Channels.FirstOrDefault(); + if (channel == null) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var channel = snapApp.Channels.FirstOrDefault(); - if (channel == null) - { - throw new Exception($"Default channel not found. Snap id: {snapApp.Id}"); - } - return channel; + throw new Exception($"Default channel not found. Snap id: {snapApp.Id}"); } + return channel; + } - internal static SnapChannel GetCurrentChannelOrThrow([NotNull] this SnapApp snapApp) + internal static SnapChannel GetCurrentChannelOrThrow([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + var channel = snapApp.Channels.SingleOrDefault(x => x.Current); + if (channel == null) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var channel = snapApp.Channels.SingleOrDefault(x => x.Current); - if (channel == null) - { - throw new Exception($"Current channel not found. Snap id: {snapApp.Id}"); - } - return channel; + throw new Exception($"Current channel not found. Snap id: {snapApp.Id}"); } + return channel; + } - internal static SnapChannel GetNextChannel([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + internal static SnapChannel GetNextChannel([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var channelsCount = snapApp.Channels.Count; + var channelsCount = snapApp.Channels.Count; - for (var index = 0; index < channelsCount; index++) + for (var index = 0; index < channelsCount; index++) + { + var currentSnapChannel = snapApp.Channels[index]; + var nextSnapChannel = index + 1 < channelsCount ? snapApp.Channels[index + 1] : null; + if (currentSnapChannel.Current && nextSnapChannel != null) { - var currentSnapChannel = snapApp.Channels[index]; - var nextSnapChannel = index + 1 < channelsCount ? snapApp.Channels[index + 1] : null; - if (currentSnapChannel.Current && nextSnapChannel != null) - { - return nextSnapChannel; - } + return nextSnapChannel; } - - return null; } + + return null; + } - internal static bool IsValidAppId([NotNull] this string value) - { - return AppIdRegex.IsMatch(value); - } + internal static bool IsValidAppId([NotNull] this string value) + { + return AppIdRegex.IsMatch(value); + } - internal static bool IsValidChannelName([NotNull] this string value) - { - return ChannelNameRegex.IsMatch(value); - } + internal static bool IsValidChannelName([NotNull] this string value) + { + return ChannelNameRegex.IsMatch(value); + } - internal static bool IsValidAppId([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return snapApp.Id.IsValidAppId(); - } + internal static bool IsValidAppId([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return snapApp.Id.IsValidAppId(); + } - internal static bool IsValidChannelName([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var channel = snapApp.GetCurrentChannelOrThrow(); - return channel.Name.IsValidChannelName(); - } + internal static bool IsValidChannelName([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + var channel = snapApp.GetCurrentChannelOrThrow(); + return channel.Name.IsValidChannelName(); + } - internal static string BuildNugetUpstreamId([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return snapApp.IsFull ? snapApp.BuildNugetFullUpstreamId() : snapApp.BuildNugetDeltaUpstreamId(); - } + internal static string BuildNugetUpstreamId([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return snapApp.IsFull ? snapApp.BuildNugetFullUpstreamId() : snapApp.BuildNugetDeltaUpstreamId(); + } - internal static string BuildNugetFullUpstreamId([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return $"{snapApp.Id}_full_{snapApp.Target.Rid}_snapx".ToLowerInvariant(); - } + internal static string BuildNugetFullUpstreamId([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return $"{snapApp.Id}_full_{snapApp.Target.Rid}_snapx".ToLowerInvariant(); + } - internal static string BuildNugetDeltaUpstreamId([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return $"{snapApp.Id}_delta_{snapApp.Target.Rid}_snapx".ToLowerInvariant(); - } + internal static string BuildNugetDeltaUpstreamId([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return $"{snapApp.Id}_delta_{snapApp.Target.Rid}_snapx".ToLowerInvariant(); + } - internal static string BuildNugetUpstreamId([NotNull] this SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - return snapRelease.IsFull ? snapRelease.BuildNugetFullUpstreamId() : snapRelease.BuildNugetDeltaUpstreamId(); - } + internal static string BuildNugetUpstreamId([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + return snapRelease.IsFull ? snapRelease.BuildNugetFullUpstreamId() : snapRelease.BuildNugetDeltaUpstreamId(); + } - internal static string BuildNugetFullUpstreamId([NotNull] this SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - return $"{snapRelease.Id}_full_{snapRelease.Target.Rid}_snapx".ToLowerInvariant(); - } + internal static string BuildNugetFullUpstreamId([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + return $"{snapRelease.Id}_full_{snapRelease.Target.Rid}_snapx".ToLowerInvariant(); + } - internal static string BuildNugetDeltaUpstreamId([NotNull] this SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - return $"{snapRelease.Id}_delta_{snapRelease.Target.Rid}_snapx".ToLowerInvariant(); - } + internal static string BuildNugetDeltaUpstreamId([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + return $"{snapRelease.Id}_delta_{snapRelease.Target.Rid}_snapx".ToLowerInvariant(); + } - internal static string BuildNugetFilename([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return snapApp.IsFull ? snapApp.BuildNugetFullFilename() : snapApp.BuildNugetDeltaFilename(); - } + internal static string BuildNugetFilename([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return snapApp.IsFull ? snapApp.BuildNugetFullFilename() : snapApp.BuildNugetDeltaFilename(); + } - internal static string BuildNugetFullFilename([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return $"{snapApp.Id}_full_{snapApp.Target.Rid}_snapx.{snapApp.Version.ToNuGetVersion()}.nupkg".ToLowerInvariant(); - } + internal static string BuildNugetFullFilename([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return $"{snapApp.Id}_full_{snapApp.Target.Rid}_snapx.{snapApp.Version.ToNuGetVersion()}.nupkg".ToLowerInvariant(); + } - internal static string BuildNugetDeltaFilename([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return $"{snapApp.Id}_delta_{snapApp.Target.Rid}_snapx.{snapApp.Version.ToNuGetVersion()}.nupkg".ToLowerInvariant(); - } + internal static string BuildNugetDeltaFilename([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return $"{snapApp.Id}_delta_{snapApp.Target.Rid}_snapx.{snapApp.Version.ToNuGetVersion()}.nupkg".ToLowerInvariant(); + } - internal static string BuildNugetFilename([NotNull] this SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - return snapRelease.IsFull ? snapRelease.BuildNugetFullFilename() : snapRelease.BuildNugetDeltaFilename(); - } + internal static string BuildNugetFilename([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + return snapRelease.IsFull ? snapRelease.BuildNugetFullFilename() : snapRelease.BuildNugetDeltaFilename(); + } - internal static string BuildNugetFullFilename([NotNull] this SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - return $"{snapRelease.Id}_full_{snapRelease.Target.Rid}_snapx.{snapRelease.Version.ToNuGetVersion()}.nupkg".ToLowerInvariant(); - } + internal static string BuildNugetFullFilename([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + return $"{snapRelease.Id}_full_{snapRelease.Target.Rid}_snapx.{snapRelease.Version.ToNuGetVersion()}.nupkg".ToLowerInvariant(); + } - internal static string BuildNugetDeltaFilename([NotNull] this SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - return $"{snapRelease.Id}_delta_{snapRelease.Target.Rid}_snapx.{snapRelease.Version.ToNuGetVersion()}.nupkg".ToLowerInvariant(); - } + internal static string BuildNugetDeltaFilename([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + return $"{snapRelease.Id}_delta_{snapRelease.Target.Rid}_snapx.{snapRelease.Version.ToNuGetVersion()}.nupkg".ToLowerInvariant(); + } - internal static string BuildNugetReleasesUpstreamId([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return $"{snapApp.Id.ToLowerInvariant()}_snapx"; - } + internal static string BuildNugetReleasesUpstreamId([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return $"{snapApp.Id.ToLowerInvariant()}_snapx"; + } - internal static PackageIdentity BuildNugetReleasesUpstreamPackageIdentityId([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return new PackageIdentity(snapApp.BuildNugetReleasesUpstreamId(), snapApp.Version.ToNuGetVersion()); - } + internal static PackageIdentity BuildNugetReleasesUpstreamPackageIdentityId([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return new PackageIdentity(snapApp.BuildNugetReleasesUpstreamId(), snapApp.Version.ToNuGetVersion()); + } - internal static string BuildNugetReleasesUpstreamId([NotNull] this SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - return $"{snapRelease.Id.ToLowerInvariant()}_snapx"; - } + internal static string BuildNugetReleasesUpstreamId([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + return $"{snapRelease.Id.ToLowerInvariant()}_snapx"; + } - internal static string BuildNugetReleasesFilename([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - return $"{snapApp.BuildNugetReleasesUpstreamId()}.nupkg".ToLowerInvariant(); - } + internal static string BuildNugetReleasesFilename([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + return $"{snapApp.BuildNugetReleasesUpstreamId()}.nupkg".ToLowerInvariant(); + } - public static SnapApp AsFullSnapApp([NotNull] this SnapApp snapApp, bool isGenesis) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + public static SnapApp AsFullSnapApp([NotNull] this SnapApp snapApp, bool isGenesis) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var fullSnapApp = new SnapApp(snapApp) - { - IsFull = true, - IsGenesis = isGenesis - }; + var fullSnapApp = new SnapApp(snapApp) + { + IsFull = true, + IsGenesis = isGenesis + }; - return fullSnapApp; - } + return fullSnapApp; + } - public static SnapApp AsDeltaSnapApp([NotNull] this SnapApp snapApp) - { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + public static SnapApp AsDeltaSnapApp([NotNull] this SnapApp snapApp) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - var fullSnapApp = new SnapApp(snapApp) - { - IsFull = false, - IsGenesis = false - }; + var fullSnapApp = new SnapApp(snapApp) + { + IsFull = false, + IsGenesis = false + }; - return fullSnapApp; - } + return fullSnapApp; + } - public static SnapRelease AsFullRelease([NotNull] this SnapRelease snapRelease, bool isGenesis) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + public static SnapRelease AsFullRelease([NotNull] this SnapRelease snapRelease, bool isGenesis) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - var fullSnapRelease = new SnapRelease(snapRelease) - { - Filename = snapRelease.BuildNugetFullFilename(), - UpstreamId = snapRelease.BuildNugetFullUpstreamId(), - IsGenesis = isGenesis, - IsFull = true, - Gc = isGenesis && snapRelease.Gc - }; - - fullSnapRelease.Files.Clear(); - fullSnapRelease.New.Clear(); - fullSnapRelease.Modified.Clear(); - fullSnapRelease.Unmodified.Clear(); - fullSnapRelease.Deleted.Clear(); + var fullSnapRelease = new SnapRelease(snapRelease) + { + Filename = snapRelease.BuildNugetFullFilename(), + UpstreamId = snapRelease.BuildNugetFullUpstreamId(), + IsGenesis = isGenesis, + IsFull = true, + Gc = isGenesis && snapRelease.Gc + }; + + fullSnapRelease.Files.Clear(); + fullSnapRelease.New.Clear(); + fullSnapRelease.Modified.Clear(); + fullSnapRelease.Unmodified.Clear(); + fullSnapRelease.Deleted.Clear(); - fullSnapRelease.Files.AddRange(snapRelease.Files.Select(x => new SnapReleaseChecksum(x))); + fullSnapRelease.Files.AddRange(snapRelease.Files.Select(x => new SnapReleaseChecksum(x))); - return fullSnapRelease; - } + return fullSnapRelease; + } - public static SnapRelease AsDeltaRelease([NotNull] this SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + public static SnapRelease AsDeltaRelease([NotNull] this SnapRelease snapRelease) + { + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - var deltaSnapRelease = new SnapRelease(snapRelease).AsFullRelease(false); - deltaSnapRelease.IsFull = false; - deltaSnapRelease.Filename = deltaSnapRelease.BuildNugetDeltaFilename(); - deltaSnapRelease.UpstreamId = deltaSnapRelease.BuildNugetDeltaUpstreamId(); + var deltaSnapRelease = new SnapRelease(snapRelease).AsFullRelease(false); + deltaSnapRelease.IsFull = false; + deltaSnapRelease.Filename = deltaSnapRelease.BuildNugetDeltaFilename(); + deltaSnapRelease.UpstreamId = deltaSnapRelease.BuildNugetDeltaUpstreamId(); - return deltaSnapRelease; - } + return deltaSnapRelease; + } - internal static PackageSource BuildPackageSource([NotNull] this SnapNugetFeed snapFeed, [NotNull] ISettings settings) - { - if (snapFeed == null) throw new ArgumentNullException(nameof(snapFeed)); - if (settings == null) throw new ArgumentNullException(nameof(settings)); + internal static PackageSource BuildPackageSource([NotNull] this SnapNugetFeed snapFeed, [NotNull] ISettings settings) + { + if (snapFeed == null) throw new ArgumentNullException(nameof(snapFeed)); + if (settings == null) throw new ArgumentNullException(nameof(settings)); - var packageSource = new PackageSource(snapFeed.Source.ToString(), snapFeed.Name, true, true, false); + var packageSource = new PackageSource(snapFeed.Source.ToString(), snapFeed.Name, true, true, false); - var storePasswordInClearText = !settings.IsPasswordEncryptionSupported(); + var storePasswordInClearText = !settings.IsPasswordEncryptionSupported(); - if (snapFeed.Username != null && snapFeed.Password != null) - { - snapFeed.Password = storePasswordInClearText ? snapFeed.Password : EncryptionUtility.EncryptString(snapFeed.Password); + if (snapFeed.Username != null && snapFeed.Password != null) + { + snapFeed.Password = storePasswordInClearText ? snapFeed.Password : EncryptionUtility.EncryptString(snapFeed.Password); - // Comma-delimited list of authentication types the credential is valid for as stored in the config file. - // If null or empty, all authentication types are valid. Example: 'basic,negotiate' - string validAuthenticationTypesText = null; + // Comma-delimited list of authentication types the credential is valid for as stored in the config file. + // If null or empty, all authentication types are valid. Example: 'basic,negotiate' + string validAuthenticationTypesText = null; - packageSource.Credentials = new PackageSourceCredential(packageSource.Name, - // ReSharper disable once ExpressionIsAlwaysNull - snapFeed.Username, snapFeed.Password, storePasswordInClearText, validAuthenticationTypesText); + packageSource.Credentials = new PackageSourceCredential(packageSource.Name, + // ReSharper disable once ExpressionIsAlwaysNull + snapFeed.Username, snapFeed.Password, storePasswordInClearText, validAuthenticationTypesText); - settings.AddOrUpdate(ConfigurationConstants.CredentialsSectionName, packageSource.Credentials.AsCredentialsItem()); - } + settings.AddOrUpdate(ConfigurationConstants.CredentialsSectionName, packageSource.Credentials.AsCredentialsItem()); + } - if (snapFeed.ApiKey != null) + if (snapFeed.ApiKey != null) + { + if (storePasswordInClearText) { - if (storePasswordInClearText) - { - settings.AddOrUpdate(ConfigurationConstants.ApiKeys, new AddItem(packageSource.Source, snapFeed.ApiKey)); - } - else - { - SettingsUtility.SetEncryptedValueForAddItem(settings, ConfigurationConstants.ApiKeys, packageSource.Source, snapFeed.ApiKey); - } + settings.AddOrUpdate(ConfigurationConstants.ApiKeys, new AddItem(packageSource.Source, snapFeed.ApiKey)); + } + else + { + SettingsUtility.SetEncryptedValueForAddItem(settings, ConfigurationConstants.ApiKeys, packageSource.Source, snapFeed.ApiKey); } + } - packageSource.ProtocolVersion = (int)snapFeed.ProtocolVersion; + packageSource.ProtocolVersion = (int)snapFeed.ProtocolVersion; - return packageSource; - } + return packageSource; + } - internal static string GetDecryptedValue([NotNull] this PackageSource packageSource, [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] string sectionName) - { - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - if (sectionName == null) throw new ArgumentNullException(nameof(sectionName)); + internal static string GetDecryptedValue([NotNull] this PackageSource packageSource, [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] string sectionName) + { + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + if (sectionName == null) throw new ArgumentNullException(nameof(sectionName)); - var nuGetSupportsEncryption = nuGetPackageSources.IsPasswordEncryptionSupported(); + var nuGetSupportsEncryption = nuGetPackageSources.IsPasswordEncryptionSupported(); - var decryptedValue = nuGetSupportsEncryption ? - SettingsUtility.GetDecryptedValueForAddItem(nuGetPackageSources.Settings, sectionName, packageSource.Source) : - SettingsUtility.GetValueForAddItem(nuGetPackageSources.Settings, sectionName, packageSource.Source); + var decryptedValue = nuGetSupportsEncryption ? + SettingsUtility.GetDecryptedValueForAddItem(nuGetPackageSources.Settings, sectionName, packageSource.Source) : + SettingsUtility.GetValueForAddItem(nuGetPackageSources.Settings, sectionName, packageSource.Source); - return string.IsNullOrWhiteSpace(decryptedValue) ? null : decryptedValue; - } + return string.IsNullOrWhiteSpace(decryptedValue) ? null : decryptedValue; + } - internal static SnapNugetFeed BuildSnapNugetFeed([NotNull] this PackageSource packageSource, [NotNull] INuGetPackageSources nuGetPackageSources) - { - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + internal static SnapNugetFeed BuildSnapNugetFeed([NotNull] this PackageSource packageSource, [NotNull] INuGetPackageSources nuGetPackageSources) + { + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - var apiKey = packageSource.GetDecryptedValue(nuGetPackageSources, ConfigurationConstants.ApiKeys); + var apiKey = packageSource.GetDecryptedValue(nuGetPackageSources, ConfigurationConstants.ApiKeys); - var snapFeed = new SnapNugetFeed - { - Name = packageSource.Name, - Source = packageSource.SourceUri, - ProtocolVersion = (NuGetProtocolVersion)packageSource.ProtocolVersion, - Username = packageSource.Credentials?.Username, - Password = packageSource.Credentials?.Password, - ApiKey = apiKey - }; + var snapFeed = new SnapNugetFeed + { + Name = packageSource.Name, + Source = packageSource.SourceUri, + ProtocolVersion = (NuGetProtocolVersion)packageSource.ProtocolVersion, + Username = packageSource.Credentials?.Username, + Password = packageSource.Credentials?.Password, + ApiKey = apiKey + }; - return snapFeed; - } + return snapFeed; + } - internal static IEnumerable BuildSnapApps([NotNull] this SnapApps snapApps, - [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] ISnapFilesystem snapFilesystem, - bool requireUpdateFeed = true, bool requirePushFeed = true) + internal static IEnumerable BuildSnapApps([NotNull] this SnapApps snapApps, + [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] ISnapFilesystem snapFilesystem, + bool requireUpdateFeed = true, bool requirePushFeed = true) + { + return snapApps.Apps.Select(snapsApp => snapApps.BuildSnapApp(snapsApp.Id, snapsApp.Target.Rid, + nuGetPackageSources, snapFilesystem, requireUpdateFeed, requirePushFeed)); + } + + internal static SnapApp BuildSnapApp([NotNull] this SnapApps snapApps, string id, [NotNull] string rid, + [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] ISnapFilesystem snapFilesystem, + bool requireUpdateFeed = true, bool requirePushFeed = true) + { + if (snapApps == null) throw new ArgumentNullException(nameof(snapApps)); + if (rid == null) throw new ArgumentNullException(nameof(rid)); + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); + + var snapApp = snapApps.Apps.SingleOrDefault(x => string.Equals(x.Id, id, StringComparison.OrdinalIgnoreCase)); + if (snapApp == null) { - return snapApps.Apps.Select(snapsApp => snapApps.BuildSnapApp(snapsApp.Id, snapsApp.Target.Rid, - nuGetPackageSources, snapFilesystem, requireUpdateFeed, requirePushFeed)); + throw new Exception($"Unable to find snap with id: {id}"); } - internal static SnapApp BuildSnapApp([NotNull] this SnapApps snapApps, string id, [NotNull] string rid, - [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] ISnapFilesystem snapFilesystem, - bool requireUpdateFeed = true, bool requirePushFeed = true) + if (!Guid.TryParse(snapApp.SuperVisorId, out var superVisorId)) { - if (snapApps == null) throw new ArgumentNullException(nameof(snapApps)); - if (rid == null) throw new ArgumentNullException(nameof(rid)); - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); - - var snapApp = snapApps.Apps.SingleOrDefault(x => string.Equals(x.Id, id, StringComparison.OrdinalIgnoreCase)); - if (snapApp == null) - { - throw new Exception($"Unable to find snap with id: {id}"); - } - - if (!Guid.TryParse(snapApp.SuperVisorId, out var superVisorId)) - { - throw new Exception("Unable to parse supervisor id. Please use a unique guid to identify your application."); - } + throw new Exception("Unable to parse supervisor id. Please use a unique guid to identify your application."); + } - if (superVisorId == Guid.Empty) - { - throw new Exception("Supervisor id cannot be an empty guid."); - } + if (superVisorId == Guid.Empty) + { + throw new Exception("Supervisor id cannot be an empty guid."); + } - if (snapApp.MainExe != null && string.IsNullOrWhiteSpace(snapApp.MainExe)) - { - throw new Exception($"{nameof(snapApp.MainExe)} property cannot be null or whitespace."); - } + if (snapApp.MainExe != null && string.IsNullOrWhiteSpace(snapApp.MainExe)) + { + throw new Exception($"{nameof(snapApp.MainExe)} property cannot be null or whitespace."); + } - if (snapApp.InstallDirectoryName != null && string.IsNullOrWhiteSpace(snapApp.InstallDirectoryName)) - { - throw new Exception($"{nameof(snapApp.InstallDirectoryName)} property cannot be null or whitespace."); - } + if (snapApp.InstallDirectoryName != null && string.IsNullOrWhiteSpace(snapApp.InstallDirectoryName)) + { + throw new Exception($"{nameof(snapApp.InstallDirectoryName)} property cannot be null or whitespace."); + } - snapApp.Target.Installers = snapApp.Target.Installers.Distinct().ToList(); - snapApp.Target.Rid = snapApp.Target.Rid?.ToLowerInvariant(); + snapApp.Target.Installers = snapApp.Target.Installers.Distinct().ToList(); + snapApp.Target.Rid = snapApp.Target.Rid?.ToLowerInvariant(); - if (!snapApp.Target.Rid.IsRuntimeIdentifierValidSafe()) - { - throw new Exception($"Unsupported rid: {rid}. Snap id: {snapApp.Id}"); - } + if (!snapApp.Target.Rid.IsRuntimeIdentifierValidSafe()) + { + throw new Exception($"Unsupported rid: {rid}. Snap id: {snapApp.Id}"); + } - if (!snapApp.Target.Framework.IsNetFrameworkValidSafe()) - { - throw new Exception($"Unknown .NET framework: {snapApp.Target.Framework}"); - } + if (!snapApp.Target.Framework.IsNetFrameworkValidSafe()) + { + throw new Exception($"Unknown .NET framework: {snapApp.Target.Framework}"); + } - var snapAppTargetUniqueShortcuts = snapApp.Target.Shortcuts.Select(x => x).ToList(); - if (snapAppTargetUniqueShortcuts.Distinct().Count() != snapApp.Target.Shortcuts.Count) - { - throw new Exception($"Target shortcut locations must be unique: {string.Join(", ", snapAppTargetUniqueShortcuts)}. Snap id: {snapApp.Id}"); - } + var snapAppTargetUniqueShortcuts = snapApp.Target.Shortcuts.Select(x => x).ToList(); + if (snapAppTargetUniqueShortcuts.Distinct().Count() != snapApp.Target.Shortcuts.Count) + { + throw new Exception($"Target shortcut locations must be unique: {string.Join(", ", snapAppTargetUniqueShortcuts)}. Snap id: {snapApp.Id}"); + } - var snapAppTargetUniqueInstallers = snapApp.Target.Installers.Select(x => x).ToList(); - if (snapAppTargetUniqueInstallers.Distinct().Count() != snapApp.Target.Installers.Count) - { - throw new Exception($"Target installer types must be unique: {string.Join(", ", snapAppTargetUniqueInstallers)}. Snap id: {snapApp.Id}"); - } + var snapAppTargetUniqueInstallers = snapApp.Target.Installers.Select(x => x).ToList(); + if (snapAppTargetUniqueInstallers.Distinct().Count() != snapApp.Target.Installers.Count) + { + throw new Exception($"Target installer types must be unique: {string.Join(", ", snapAppTargetUniqueInstallers)}. Snap id: {snapApp.Id}"); + } - if (snapApp.Target.Icon != null) - { - snapApp.Target.Icon = snapFilesystem.PathGetFullPath(snapApp.Target.Icon); + if (snapApp.Target.Icon != null) + { + snapApp.Target.Icon = snapFilesystem.PathGetFullPath(snapApp.Target.Icon); - if (!snapFilesystem.FileExists(snapApp.Target.Icon)) - { - throw new Exception($"Unable to find icon: {snapApp.Target.Icon}."); - } + if (!snapFilesystem.FileExists(snapApp.Target.Icon)) + { + throw new Exception($"Unable to find icon: {snapApp.Target.Icon}."); } + } - var snapAppUniqueChannels = snapApp.Channels.Distinct().ToList(); - if (snapAppUniqueChannels.Count != snapApp.Channels.Count) - { - throw new Exception($"Channel list must be unique: {string.Join(",", snapApp.Channels)}. Snap id: {snapApp.Id}"); - } + var snapAppUniqueChannels = snapApp.Channels.Distinct().ToList(); + if (snapAppUniqueChannels.Count != snapApp.Channels.Count) + { + throw new Exception($"Channel list must be unique: {string.Join(",", snapApp.Channels)}. Snap id: {snapApp.Id}"); + } - var snapAppsDefaultChannel = snapApps.Channels.First(); - var snapAppDefaultChannel = snapApp.Channels.First(); + var snapAppsDefaultChannel = snapApps.Channels.First(); + var snapAppDefaultChannel = snapApp.Channels.First(); - if (!string.Equals(snapAppsDefaultChannel.Name, snapAppDefaultChannel, StringComparison.Ordinal)) - { - throw new Exception($"Default channel must be {snapAppsDefaultChannel.Name}. Snap id: {snapApp.Id}"); - } + if (!string.Equals(snapAppsDefaultChannel.Name, snapAppDefaultChannel, StringComparison.Ordinal)) + { + throw new Exception($"Default channel must be {snapAppsDefaultChannel.Name}. Snap id: {snapApp.Id}"); + } - var snapAppAvailableChannels = snapApps.Channels.Where(rhs => snapApp.Channels.Any(lhs => lhs.Equals(rhs.Name, StringComparison.OrdinalIgnoreCase))).ToList(); - if (!snapAppAvailableChannels.Any()) - { - throw new Exception($"Could not find any global channels. Channel list: {string.Join(",", snapAppUniqueChannels)}. Snap id: {snapApp.Id}"); - } + var snapAppAvailableChannels = snapApps.Channels.Where(rhs => snapApp.Channels.Any(lhs => lhs.Equals(rhs.Name, StringComparison.OrdinalIgnoreCase))).ToList(); + if (!snapAppAvailableChannels.Any()) + { + throw new Exception($"Could not find any global channels. Channel list: {string.Join(",", snapAppUniqueChannels)}. Snap id: {snapApp.Id}"); + } - var snapFeeds = new List(); - snapFeeds.AddRange(nuGetPackageSources.BuildSnapFeeds()); - snapFeeds.AddRange(snapApps.Channels.Select(x => x.UpdateFeed).OfType().DistinctBy(x => x.Source).Select(x => new SnapHttpFeed(x))); + var snapFeeds = new List(); + snapFeeds.AddRange(nuGetPackageSources.BuildSnapFeeds()); + snapFeeds.AddRange(snapApps.Channels.Select(x => x.UpdateFeed).OfType().DistinctBy(x => x.Source).Select(x => new SnapHttpFeed(x))); - var snapNugetFeeds = snapFeeds.Where(x => x is SnapNugetFeed).Cast().ToList(); + var snapNugetFeeds = snapFeeds.Where(x => x is SnapNugetFeed).Cast().ToList(); - var snapHttpFeeds = snapFeeds.Where(x => x is SnapHttpFeed).Cast().ToList(); - var snapAppChannels = new List(); - - for (var i = 0; i < snapAppAvailableChannels.Count; i++) - { - var snapsChannel = snapAppAvailableChannels[i]; - var pushFeed = snapNugetFeeds.SingleOrDefault(x => x.Name == snapsChannel.PushFeed.Name); - - SnapFeed updateFeed = snapsChannel.UpdateFeed switch - { - SnapsNugetFeed snapsNugetFeed => snapNugetFeeds.SingleOrDefault(x => x.Name == snapsNugetFeed.Name), - SnapsHttpFeed snapsHttpFeed => snapHttpFeeds.SingleOrDefault(x => x.Source == snapsHttpFeed.Source), - _ => null - }; + var snapHttpFeeds = snapFeeds.Where(x => x is SnapHttpFeed).Cast().ToList(); + var snapAppChannels = new List(); - if (requirePushFeed && pushFeed == null) - { - throw new Exception($"Unable to resolve push feed: {snapsChannel.PushFeed.Name}. Channel: {snapsChannel.Name}. Application id: {snapApp.Id}"); - } - - if (!requirePushFeed && pushFeed == null) - { - pushFeed = new SnapNugetFeed(); - } - - // Todo: Use actual feed name instead of type when throwing exception. - if (requireUpdateFeed && updateFeed == null) - { - throw new Exception($"Unable to resolve update feed type: {snapsChannel.UpdateFeed?.GetType().Name}. Channel: {snapsChannel.Name}. Application id: {snapApp.Id}"); - } + for (var i = 0; i < snapAppAvailableChannels.Count; i++) + { + var snapsChannel = snapAppAvailableChannels[i]; + var pushFeed = snapNugetFeeds.SingleOrDefault(x => x.Name == snapsChannel.PushFeed.Name); - if (!requireUpdateFeed && updateFeed == null) - { - updateFeed = new SnapNugetFeed(); - } + SnapFeed updateFeed = snapsChannel.UpdateFeed switch + { + SnapsNugetFeed snapsNugetFeed => snapNugetFeeds.SingleOrDefault(x => x.Name == snapsNugetFeed.Name), + SnapsHttpFeed snapsHttpFeed => snapHttpFeeds.SingleOrDefault(x => x.Source == snapsHttpFeed.Source), + _ => null + }; - var currentChannel = i == 0; // Default snap channel is always the first one defined. - snapAppChannels.Add(new SnapChannel(snapsChannel.Name, currentChannel, pushFeed, updateFeed)); + if (requirePushFeed && pushFeed == null) + { + throw new Exception($"Unable to resolve push feed: {snapsChannel.PushFeed.Name}. Channel: {snapsChannel.Name}. Application id: {snapApp.Id}"); } - var snapChannelNugetFeedNames = snapAppChannels.Select(x => x.PushFeed.Name).Distinct().ToList(); - if (snapChannelNugetFeedNames.Count > 1) + if (!requirePushFeed && pushFeed == null) { - throw new Exception($"Multiple nuget push feeds is not supported: {string.Join(",", snapChannelNugetFeedNames)}. Application id: {snapApp.Id}"); + pushFeed = new SnapNugetFeed(); } - if (snapApp.Target.PersistentAssets.Any(x => x.StartsWith("app-", StringComparison.OrdinalIgnoreCase))) + // Todo: Use actual feed name instead of type when throwing exception. + if (requireUpdateFeed && updateFeed == null) { - throw new Exception("Fatal error! A persistent asset starting with 'app-' was detected in manifest. This is a reserved keyword."); + throw new Exception($"Unable to resolve update feed type: {snapsChannel.UpdateFeed?.GetType().Name}. Channel: {snapsChannel.Name}. Application id: {snapApp.Id}"); } - return new SnapApp + if (!requireUpdateFeed && updateFeed == null) { - Id = snapApp.Id, - InstallDirectoryName = snapApp.InstallDirectoryName, - MainExe = snapApp.MainExe, - SuperVisorId = superVisorId.ToString(), - Channels = snapAppChannels, - Target = new SnapTarget(snapApp.Target), - Authors = snapApp.Nuspec.Authors, - Description = snapApp.Nuspec.Description, - ReleaseNotes = snapApp.Nuspec.ReleaseNotes, - RepositoryUrl = snapApp.Nuspec.RepositoryUrl, - RepositoryType = snapApp.Nuspec.RepositoryType - }; + updateFeed = new SnapNugetFeed(); + } + + var currentChannel = i == 0; // Default snap channel is always the first one defined. + snapAppChannels.Add(new SnapChannel(snapsChannel.Name, currentChannel, pushFeed, updateFeed)); } - internal static INuGetPackageSources BuildNugetSources([NotNull] this SnapApp snapApp, [NotNull] string tempDirectory) + var snapChannelNugetFeedNames = snapAppChannels.Select(x => x.PushFeed.Name).Distinct().ToList(); + if (snapChannelNugetFeedNames.Count > 1) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (tempDirectory == null) throw new ArgumentNullException(nameof(tempDirectory)); - var inMemorySettings = new NugetInMemorySettings(tempDirectory); - - var nugetFeeds = snapApp.Channels - .SelectMany(x => new List { x.PushFeed, x.UpdateFeed as SnapNugetFeed }) - .Where(x => x?.Name != null && x.Source != null) - .DistinctBy(x => x.Name) - .Select(x => x.BuildPackageSource(inMemorySettings)) - .ToList(); + throw new Exception($"Multiple nuget push feeds is not supported: {string.Join(",", snapChannelNugetFeedNames)}. Application id: {snapApp.Id}"); + } - return new NuGetPackageSources(inMemorySettings, nugetFeeds); + if (snapApp.Target.PersistentAssets.Any(x => x.StartsWith("app-", StringComparison.OrdinalIgnoreCase))) + { + throw new Exception("Fatal error! A persistent asset starting with 'app-' was detected in manifest. This is a reserved keyword."); } - internal static INuGetPackageSources BuildNugetSources([NotNull] this SnapApps snapApps, INuGetPackageSources nuGetPackageSources) + return new SnapApp { - var allPackageSources = snapApps.Channels - .SelectMany(x => + Id = snapApp.Id, + InstallDirectoryName = snapApp.InstallDirectoryName, + MainExe = snapApp.MainExe, + SuperVisorId = superVisorId.ToString(), + Channels = snapAppChannels, + Target = new SnapTarget(snapApp.Target), + Authors = snapApp.Nuspec.Authors, + Description = snapApp.Nuspec.Description, + ReleaseNotes = snapApp.Nuspec.ReleaseNotes, + RepositoryUrl = snapApp.Nuspec.RepositoryUrl, + RepositoryType = snapApp.Nuspec.RepositoryType + }; + } + + internal static INuGetPackageSources BuildNugetSources([NotNull] this SnapApp snapApp, [NotNull] string tempDirectory) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (tempDirectory == null) throw new ArgumentNullException(nameof(tempDirectory)); + var inMemorySettings = new NugetInMemorySettings(tempDirectory); + + var nugetFeeds = snapApp.Channels + .SelectMany(x => new List { x.PushFeed, x.UpdateFeed as SnapNugetFeed }) + .Where(x => x?.Name != null && x.Source != null) + .DistinctBy(x => x.Name) + .Select(x => x.BuildPackageSource(inMemorySettings)) + .ToList(); + + return new NuGetPackageSources(inMemorySettings, nugetFeeds); + } + + internal static INuGetPackageSources BuildNugetSources([NotNull] this SnapApps snapApps, INuGetPackageSources nuGetPackageSources) + { + var allPackageSources = snapApps.Channels + .SelectMany(x => + { + var packageSources = new List(); + + var pushFeed = nuGetPackageSources.Items.SingleOrDefault(packageSource => packageSource.Name == x.PushFeed.Name); + if (pushFeed != null) { - var packageSources = new List(); + packageSources.Add(pushFeed); + } - var pushFeed = nuGetPackageSources.Items.SingleOrDefault(packageSource => packageSource.Name == x.PushFeed.Name); - if (pushFeed != null) + if (x.UpdateFeed is SnapsNugetFeed snapsNugetFeed) + { + var updateFeed = nuGetPackageSources.Items.SingleOrDefault(packageSource => packageSource.Name == snapsNugetFeed.Name); + if (updateFeed != null) { - packageSources.Add(pushFeed); + packageSources.Add(updateFeed); } + } - if (x.UpdateFeed is SnapsNugetFeed snapsNugetFeed) - { - var updateFeed = nuGetPackageSources.Items.SingleOrDefault(packageSource => packageSource.Name == snapsNugetFeed.Name); - if (updateFeed != null) - { - packageSources.Add(updateFeed); - } - } + return packageSources; + }) + .DistinctBy(x => x.Name) + .ToList(); - return packageSources; - }) - .DistinctBy(x => x.Name) - .ToList(); + return !allPackageSources.Any() ? NuGetPackageSources.Empty : new NuGetPackageSources(nuGetPackageSources.Settings, allPackageSources); + } - return !allPackageSources.Any() ? NuGetPackageSources.Empty : new NuGetPackageSources(nuGetPackageSources.Settings, allPackageSources); - } + internal static List BuildSnapFeeds([NotNull] this INuGetPackageSources nuGetPackageSources) + { + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + var snapFeeds = nuGetPackageSources.Items.Select(x => x.BuildSnapNugetFeed(nuGetPackageSources)).ToList(); + return snapFeeds; + } + + internal static SnapApp GetSnapApp([NotNull] this AssemblyDefinition assemblyDefinition, [NotNull] ISnapAppReader snapAppReader) + { + if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + + var assemblyReflector = new CecilAssemblyReflector(assemblyDefinition); - internal static List BuildSnapFeeds([NotNull] this INuGetPackageSources nuGetPackageSources) + var snapReleaseDetailsAttribute = assemblyReflector.GetAttribute(); + if (snapReleaseDetailsAttribute == null) { - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - var snapFeeds = nuGetPackageSources.Items.Select(x => x.BuildSnapNugetFeed(nuGetPackageSources)).ToList(); - return snapFeeds; + throw new Exception($"Unable to find {nameof(SnapAppReleaseDetailsAttribute)} in assembly {assemblyReflector.FullName}"); } - internal static SnapApp GetSnapApp([NotNull] this AssemblyDefinition assemblyDefinition, [NotNull] ISnapAppReader snapAppReader) + var snapSpecResource = assemblyReflector.MainModule.Resources.SingleOrDefault(x => x.Name == SnapConstants.SnapAppLibraryName); + if (!(snapSpecResource is EmbeddedResource snapSpecEmbeddedResource)) { - if (assemblyDefinition == null) throw new ArgumentNullException(nameof(assemblyDefinition)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - - var assemblyReflector = new CecilAssemblyReflector(assemblyDefinition); - - var snapReleaseDetailsAttribute = assemblyReflector.GetAttribute(); - if (snapReleaseDetailsAttribute == null) - { - throw new Exception($"Unable to find {nameof(SnapAppReleaseDetailsAttribute)} in assembly {assemblyReflector.FullName}"); - } + throw new Exception($"Unable to find resource {SnapConstants.SnapAppLibraryName} in assembly {assemblyReflector.FullName}"); + } - var snapSpecResource = assemblyReflector.MainModule.Resources.SingleOrDefault(x => x.Name == SnapConstants.SnapAppLibraryName); - if (!(snapSpecResource is EmbeddedResource snapSpecEmbeddedResource)) - { - throw new Exception($"Unable to find resource {SnapConstants.SnapAppLibraryName} in assembly {assemblyReflector.FullName}"); - } + using var resourceStream = snapSpecEmbeddedResource.GetResourceStream(); + using var snapAppMemoryStream = new MemoryStream(); + resourceStream.CopyTo(snapAppMemoryStream); + snapAppMemoryStream.Seek(0, SeekOrigin.Begin); - using var resourceStream = snapSpecEmbeddedResource.GetResourceStream(); - using var snapAppMemoryStream = new MemoryStream(); - resourceStream.CopyTo(snapAppMemoryStream); - snapAppMemoryStream.Seek(0, SeekOrigin.Begin); + return snapAppReader.BuildSnapAppFromStream(snapAppMemoryStream); + } - return snapAppReader.BuildSnapAppFromStream(snapAppMemoryStream); - } + internal static SnapApp GetSnapAppFromDirectory([NotNull] this string workingDirectory, [NotNull] ISnapFilesystem filesystem, [NotNull] ISnapAppReader snapAppReader) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - internal static SnapApp GetSnapAppFromDirectory([NotNull] this string workingDirectory, [NotNull] ISnapFilesystem filesystem, [NotNull] ISnapAppReader snapAppReader) + var snapAppDll = filesystem.PathCombine(workingDirectory, SnapConstants.SnapAppDllFilename); + if (!filesystem.FileExists(snapAppDll)) { - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + throw new FileNotFoundException(snapAppDll); + } - var snapAppDll = filesystem.PathCombine(workingDirectory, SnapConstants.SnapAppDllFilename); - if (!filesystem.FileExists(snapAppDll)) - { - throw new FileNotFoundException(snapAppDll); - } + using var snapAppDllFileStream = new FileStream(snapAppDll, FileMode.Open, FileAccess.Read, FileShare.Read); + using var snapAppDllAssemblyDefinition = AssemblyDefinition.ReadAssembly(snapAppDllFileStream); + return snapAppDllAssemblyDefinition.GetSnapApp(snapAppReader); + } - using var snapAppDllFileStream = new FileStream(snapAppDll, FileMode.Open, FileAccess.Read, FileShare.Read); - using var snapAppDllAssemblyDefinition = AssemblyDefinition.ReadAssembly(snapAppDllFileStream); - return snapAppDllAssemblyDefinition.GetSnapApp(snapAppReader); - } + internal static void GetCoreRunExecutableFullPath([NotNull] this SnapApp snapApp, [NotNull] ISnapFilesystem snapFilesystem, [NotNull] string baseDirectory, out string coreRunFullPath) + { + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); + if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - internal static void GetCoreRunExecutableFullPath([NotNull] this SnapApp snapApp, [NotNull] ISnapFilesystem snapFilesystem, [NotNull] string baseDirectory, out string coreRunFullPath) + var exeName = snapApp.MainExe ?? snapApp.Id; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); - if (baseDirectory == null) throw new ArgumentNullException(nameof(baseDirectory)); - - var exeName = snapApp.MainExe ?? snapApp.Id; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - exeName += ".exe"; - } - - coreRunFullPath = snapFilesystem.PathCombine(baseDirectory, exeName); + exeName += ".exe"; } + + coreRunFullPath = snapFilesystem.PathCombine(baseDirectory, exeName); + } - internal static void GetCoreRunExecutableFullPath([NotNull] this Assembly assembly, [NotNull] ISnapFilesystem snapFilesystem, - [NotNull] ISnapAppReader snapAppReader, out string coreRunFullPath) - { - if (assembly == null) throw new ArgumentNullException(nameof(assembly)); - if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + internal static void GetCoreRunExecutableFullPath([NotNull] this Assembly assembly, [NotNull] ISnapFilesystem snapFilesystem, + [NotNull] ISnapAppReader snapAppReader, out string coreRunFullPath) + { + if (assembly == null) throw new ArgumentNullException(nameof(assembly)); + if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - var assemblyLocationDirectory = snapFilesystem.PathGetDirectoryName(assembly.Location); - var snapApp = assemblyLocationDirectory.GetSnapAppFromDirectory(snapFilesystem, snapAppReader); - var parentDirectory = snapFilesystem.DirectoryGetParent(assemblyLocationDirectory); + var assemblyLocationDirectory = snapFilesystem.PathGetDirectoryName(assembly.Location); + var snapApp = assemblyLocationDirectory.GetSnapAppFromDirectory(snapFilesystem, snapAppReader); + var parentDirectory = snapFilesystem.DirectoryGetParent(assemblyLocationDirectory); - snapApp.GetCoreRunExecutableFullPath(snapFilesystem, parentDirectory, out coreRunFullPath); - } + snapApp.GetCoreRunExecutableFullPath(snapFilesystem, parentDirectory, out coreRunFullPath); + } - internal static bool IsSupportedOsVersion(this OSPlatform oSPlatform) - { - return oSPlatform == OSPlatform.Linux || oSPlatform == OSPlatform.Windows; - } + internal static bool IsSupportedOsVersion(this OSPlatform oSPlatform) + { + return oSPlatform == OSPlatform.Linux || oSPlatform == OSPlatform.Windows; } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/StreamExtensions.cs b/src/Snap/Extensions/StreamExtensions.cs index 4529a0a2..9dfd2f7c 100644 --- a/src/Snap/Extensions/StreamExtensions.cs +++ b/src/Snap/Extensions/StreamExtensions.cs @@ -4,27 +4,26 @@ using System.Threading.Tasks; using JetBrains.Annotations; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class StreamExtensions { - internal static class StreamExtensions + public static async Task ReadToEndAsync([NotNull] this Task srcStreamTask, bool leaveSrcStreamOpen = true, CancellationToken cancellationToken = default) { - public static async Task ReadToEndAsync([NotNull] this Task srcStreamTask, bool leaveSrcStreamOpen = true, CancellationToken cancellationToken = default) - { - var srcStream = await srcStreamTask; - return await srcStream.ReadToEndAsync(leaveSrcStreamOpen, cancellationToken); - } + var srcStream = await srcStreamTask; + return await srcStream.ReadToEndAsync(leaveSrcStreamOpen, cancellationToken); + } - public static async Task ReadToEndAsync([NotNull] this Stream srcStream, bool leaveSrcStreamOpen = true, CancellationToken cancellationToken = default) + public static async Task ReadToEndAsync([NotNull] this Stream srcStream, bool leaveSrcStreamOpen = true, CancellationToken cancellationToken = default) + { + if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); + var outputStream = new MemoryStream(); + await srcStream.CopyToAsync(outputStream, cancellationToken); + outputStream.Seek(0, SeekOrigin.Begin); + if (!leaveSrcStreamOpen) { - if (srcStream == null) throw new ArgumentNullException(nameof(srcStream)); - var outputStream = new MemoryStream(); - await srcStream.CopyToAsync(outputStream, cancellationToken); - outputStream.Seek(0, SeekOrigin.Begin); - if (!leaveSrcStreamOpen) - { - await srcStream.DisposeAsync(); - } - return outputStream; + await srcStream.DisposeAsync(); } + return outputStream; } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/StringExtensions.cs b/src/Snap/Extensions/StringExtensions.cs index 28764656..8f48f5a2 100644 --- a/src/Snap/Extensions/StringExtensions.cs +++ b/src/Snap/Extensions/StringExtensions.cs @@ -1,48 +1,70 @@ -using System; + +using System; using System.Linq; +using Snap.Core; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class StringExtensions { - internal static class StringExtensions + public static ISnapNetworkTimeProvider BuildNtpProvider(this string value) { - public static bool IsTrue(this string value) + if (string.IsNullOrWhiteSpace(value)) { - if (string.IsNullOrWhiteSpace(value)) - { - return false; - } - - value = value.Trim(); - return value == "1" || value.ToLowerInvariant() == "true"; + return null; } - public static string ForwardSlashesSafe(this string value) + var segments = value.Split(":", StringSplitOptions.RemoveEmptyEntries).ToList(); + if (segments.Count != 2) { - return value?.Replace('\\', '/'); + return null; } - public static string TrailingSlashesSafe(this string value) + if (!int.TryParse(segments[1], out var port) || port <= 0) { - return value?.Replace('/', '\\'); + return null; } - public static int ToIntSafe(this string value) + return new SnapNetworkTimeProvider(segments[0], port); + } + + public static bool IsTrue(this string value) + { + if (string.IsNullOrWhiteSpace(value)) { - if (value == null) - { - return 0; - } - - var digits = new string(value.Where(char.IsDigit).ToArray()); - int.TryParse(digits, out var number); - return number; + return false; } - public static string Repeat(this char chr, int times) + value = value.Trim(); + return value == "1" || value.ToLowerInvariant() == "true"; + } + + public static string ForwardSlashesSafe(this string value) + { + return value?.Replace('\\', '/'); + } + + public static string TrailingSlashesSafe(this string value) + { + return value?.Replace('/', '\\'); + } + + public static int ToIntSafe(this string value) + { + if (value == null) { - if (chr <= 0) throw new ArgumentOutOfRangeException(nameof(chr)); - if (times <= 0) throw new ArgumentOutOfRangeException(nameof(times)); - return new string(chr, times); + return 0; } + + var digits = new string(value.Where(char.IsDigit).ToArray()); + int.TryParse(digits, out var number); + return number; + } + + public static string Repeat(this char chr, int times) + { + if (chr <= 0) throw new ArgumentOutOfRangeException(nameof(chr)); + if (times <= 0) throw new ArgumentOutOfRangeException(nameof(times)); + return new string(chr, times); } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/TplExtensions.cs b/src/Snap/Extensions/TplExtensions.cs index 8614fe32..715425d3 100644 --- a/src/Snap/Extensions/TplExtensions.cs +++ b/src/Snap/Extensions/TplExtensions.cs @@ -3,78 +3,77 @@ using System.Threading.Tasks; using JetBrains.Annotations; -namespace Snap.Extensions +namespace Snap.Extensions; + +// https://stackoverflow.com/a/25097498 +internal static class TplHelper { - // https://stackoverflow.com/a/25097498 - internal static class TplHelper - { - static readonly TaskFactory MyTaskFactory = new(CancellationToken.None, - TaskCreationOptions.None, - TaskContinuationOptions.None, - TaskScheduler.Default); + static readonly TaskFactory MyTaskFactory = new(CancellationToken.None, + TaskCreationOptions.None, + TaskContinuationOptions.None, + TaskScheduler.Default); - public static TResult RunSync(Func> func) - { - return MyTaskFactory - .StartNew(func) - .Unwrap() - .GetAwaiter() - .GetResult(); - } + public static TResult RunSync(Func> func) + { + return MyTaskFactory + .StartNew(func) + .Unwrap() + .GetAwaiter() + .GetResult(); + } - public static void RunSync(Func func) - { - MyTaskFactory - .StartNew(func) - .Unwrap() - .GetAwaiter() - .GetResult(); - } + public static void RunSync(Func func) + { + MyTaskFactory + .StartNew(func) + .Unwrap() + .GetAwaiter() + .GetResult(); } +} - internal static class TplExtensions +internal static class TplExtensions +{ + // https://blogs.msdn.microsoft.com/pfxteam/2012/10/05/how-do-i-cancel-non-cancelable-async-operations/ + public static async Task WithCancellation([NotNull] this Task task, CancellationToken cancellationToken) { - // https://blogs.msdn.microsoft.com/pfxteam/2012/10/05/how-do-i-cancel-non-cancelable-async-operations/ - public static async Task WithCancellation([NotNull] this Task task, CancellationToken cancellationToken) - { - if (task == null) throw new ArgumentNullException(nameof(task)); + if (task == null) throw new ArgumentNullException(nameof(task)); - // The tasck completion source. - var tcs = new TaskCompletionSource(); + // The tasck completion source. + var tcs = new TaskCompletionSource(); - // Register with the cancellation token. - await using (cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(true), tcs)) + // Register with the cancellation token. + await using (cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(true), tcs)) + { + // If the task waited on is the cancellation token... + if (task != await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)) { - // If the task waited on is the cancellation token... - if (task != await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)) - { - throw new OperationCanceledException(cancellationToken); - } + throw new OperationCanceledException(cancellationToken); } - - // Wait for one or the other to complete. - return await task; } - public static async Task WithCancellation([NotNull] this Task task, CancellationToken cancellationToken) - { - if (task == null) throw new ArgumentNullException(nameof(task)); + // Wait for one or the other to complete. + return await task; + } + + public static async Task WithCancellation([NotNull] this Task task, CancellationToken cancellationToken) + { + if (task == null) throw new ArgumentNullException(nameof(task)); - // The tasck completion source. - var tcs = new TaskCompletionSource(); + // The tasck completion source. + var tcs = new TaskCompletionSource(); - // Register with the cancellation token. - await using (cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(true), tcs)) + // Register with the cancellation token. + await using (cancellationToken.Register(state => ((TaskCompletionSource)state).TrySetResult(true), tcs)) + { + // If the task waited on is the cancellation token... + if (task != await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)) { - // If the task waited on is the cancellation token... - if (task != await Task.WhenAny(task, tcs.Task).ConfigureAwait(false)) - { - throw new OperationCanceledException(cancellationToken); - } + throw new OperationCanceledException(cancellationToken); } - - // Wait for one or the other to complete. - await task; } + + // Wait for one or the other to complete. + await task; } -} +} \ No newline at end of file diff --git a/src/Snap/Extensions/VersionExtensions.cs b/src/Snap/Extensions/VersionExtensions.cs index 306806ed..1ab28960 100644 --- a/src/Snap/Extensions/VersionExtensions.cs +++ b/src/Snap/Extensions/VersionExtensions.cs @@ -2,21 +2,20 @@ using JetBrains.Annotations; using NuGet.Versioning; -namespace Snap.Extensions +namespace Snap.Extensions; + +internal static class VersionExtensions { - internal static class VersionExtensions + public static SemanticVersion BumpMajor(this SemanticVersion version, int inc = 1) { - public static SemanticVersion BumpMajor(this SemanticVersion version, int inc = 1) - { - if (inc <= 0) throw new ArgumentOutOfRangeException(nameof(inc)); - return new SemanticVersion(version.Major + inc, version.Minor, version.Patch, version.ReleaseLabels, - version.Metadata); - } + if (inc <= 0) throw new ArgumentOutOfRangeException(nameof(inc)); + return new SemanticVersion(version.Major + inc, version.Minor, version.Patch, version.ReleaseLabels, + version.Metadata); + } - public static NuGetVersion ToNuGetVersion([NotNull] this SemanticVersion version) - { - if (version == null) throw new ArgumentNullException(nameof(version)); - return NuGetVersion.Parse(version.ToNormalizedString()); - } + public static NuGetVersion ToNuGetVersion([NotNull] this SemanticVersion version) + { + if (version == null) throw new ArgumentNullException(nameof(version)); + return NuGetVersion.Parse(version.ToNormalizedString()); } -} +} \ No newline at end of file diff --git a/src/Snap/NuGet/InMemoryPackageFile.cs b/src/Snap/NuGet/InMemoryPackageFile.cs index e9fba94a..282149fb 100644 --- a/src/Snap/NuGet/InMemoryPackageFile.cs +++ b/src/Snap/NuGet/InMemoryPackageFile.cs @@ -5,35 +5,34 @@ using NuGet.Frameworks; using NuGet.Packaging; -namespace Snap.NuGet +namespace Snap.NuGet; + +internal sealed class InMemoryPackageFile : IPackageFile { - internal sealed class InMemoryPackageFile : IPackageFile - { - readonly Stream _memoryStream; + readonly Stream _memoryStream; - public string Path { get; } - public string EffectivePath { get; } - public FrameworkName TargetFramework { get; } - public NuGetFramework NuGetFramework { get; } - public DateTimeOffset LastWriteTime { get; } - public string Filename { get; } + public string Path { get; } + public string EffectivePath { get; } + public FrameworkName TargetFramework { get; } + public NuGetFramework NuGetFramework { get; } + public DateTimeOffset LastWriteTime { get; } + public string Filename { get; } - public InMemoryPackageFile([NotNull] Stream memoryStream, [NotNull] NuGetFramework nuGetFramework, [NotNull] string targetPath, [NotNull] string filename) - { - if (nuGetFramework == null) throw new ArgumentNullException(nameof(nuGetFramework)); - if (targetPath == null) throw new ArgumentNullException(nameof(targetPath)); - _memoryStream = memoryStream ?? throw new ArgumentNullException(nameof(memoryStream)); + public InMemoryPackageFile([NotNull] Stream memoryStream, [NotNull] NuGetFramework nuGetFramework, [NotNull] string targetPath, [NotNull] string filename) + { + if (nuGetFramework == null) throw new ArgumentNullException(nameof(nuGetFramework)); + if (targetPath == null) throw new ArgumentNullException(nameof(targetPath)); + _memoryStream = memoryStream ?? throw new ArgumentNullException(nameof(memoryStream)); - TargetFramework = new FrameworkName(nuGetFramework.DotNetFrameworkName); - NuGetFramework = nuGetFramework; - Path = EffectivePath = targetPath ?? throw new ArgumentNullException(nameof(targetPath)); - Filename = filename ?? throw new ArgumentNullException(nameof(filename)); - LastWriteTime = DateTimeOffset.Now; - } + TargetFramework = new FrameworkName(nuGetFramework.DotNetFrameworkName); + NuGetFramework = nuGetFramework; + Path = EffectivePath = targetPath ?? throw new ArgumentNullException(nameof(targetPath)); + Filename = filename ?? throw new ArgumentNullException(nameof(filename)); + LastWriteTime = DateTimeOffset.Now; + } - public Stream GetStream() - { - return _memoryStream; - } + public Stream GetStream() + { + return _memoryStream; } } diff --git a/src/Snap/NuGet/NuGetConfigFileReader.cs b/src/Snap/NuGet/NuGetConfigFileReader.cs index 403e30d3..8eec3a6f 100644 --- a/src/Snap/NuGet/NuGetConfigFileReader.cs +++ b/src/Snap/NuGet/NuGetConfigFileReader.cs @@ -5,36 +5,35 @@ using NuGet.Configuration; using Snap.Logging; -namespace Snap.NuGet -{ - internal interface INugetConfigFileReader - { - } - - internal class NuGetConfigFileReader : INugetConfigFileReader - { - static readonly ILog Logger = LogProvider.For(); +namespace Snap.NuGet; - public NuGetPackageSources ReadNugetSources([NotNull] string workingDirectory) - { - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); +internal interface INugetConfigFileReader +{ +} - var settings = Settings.LoadDefaultSettings(workingDirectory); +internal class NuGetConfigFileReader : INugetConfigFileReader +{ + static readonly ILog Logger = LogProvider.For(); - foreach (var file in settings.GetConfigFilePaths()) - { - Logger.Info($"Adding package sources from {file}"); - } + public NuGetPackageSources ReadNugetSources([NotNull] string workingDirectory) + { + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - var packageSources = PackageSourceProvider.LoadPackageSources(settings).ToList(); - return ReadFromFile(settings, packageSources); - } + var settings = Settings.LoadDefaultSettings(workingDirectory); - static NuGetPackageSources ReadFromFile([NotNull] ISettings settings, [NotNull] IReadOnlyCollection sources) + foreach (var file in settings.GetConfigFilePaths()) { - if (settings == null) throw new ArgumentNullException(nameof(settings)); - if (sources == null) throw new ArgumentNullException(nameof(sources)); - return new NuGetPackageSources(settings, sources); + Logger.Info($"Adding package sources from {file}"); } + + var packageSources = PackageSourceProvider.LoadPackageSources(settings).ToList(); + return ReadFromFile(settings, packageSources); } -} + + static NuGetPackageSources ReadFromFile([NotNull] ISettings settings, [NotNull] IReadOnlyCollection sources) + { + if (settings == null) throw new ArgumentNullException(nameof(settings)); + if (sources == null) throw new ArgumentNullException(nameof(sources)); + return new NuGetPackageSources(settings, sources); + } +} \ No newline at end of file diff --git a/src/Snap/NuGet/NuGetPackageSources.cs b/src/Snap/NuGet/NuGetPackageSources.cs index 035639f4..45a9851a 100644 --- a/src/Snap/NuGet/NuGetPackageSources.cs +++ b/src/Snap/NuGet/NuGetPackageSources.cs @@ -7,154 +7,153 @@ using Snap.Core; using Snap.Logging; -namespace Snap.NuGet +namespace Snap.NuGet; + +internal class NuGetMachineWideSettings : IMachineWideSettings { - internal class NuGetMachineWideSettings : IMachineWideSettings - { - readonly Lazy _settings; + readonly Lazy _settings; - public ISettings Settings => _settings.Value; + public ISettings Settings => _settings.Value; - public NuGetMachineWideSettings([NotNull] ISnapFilesystem filesystem, [NotNull] string workingDirectory, ILog logger = null) - { - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + public NuGetMachineWideSettings([NotNull] ISnapFilesystem filesystem, [NotNull] string workingDirectory, ILog logger = null) + { + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - logger ??= LogProvider.For(); + logger ??= LogProvider.For(); - // https://github.com/NuGet/NuGet.Client/blob/8cb7886a7e9052308cfa51308f6f901c7caf5004/src/NuGet.Core/NuGet.Commands/SourcesCommands/SourceRunners.cs#L102 + // https://github.com/NuGet/NuGet.Client/blob/8cb7886a7e9052308cfa51308f6f901c7caf5004/src/NuGet.Core/NuGet.Commands/SourcesCommands/SourceRunners.cs#L102 - _settings = new Lazy(() => + _settings = new Lazy(() => + { + ISettings settings; + try { - ISettings settings; - try - { - settings = global::NuGet.Configuration.Settings.LoadDefaultSettings(workingDirectory, - configFileName: null, - machineWideSettings: new XPlatMachineWideSetting()); - } - catch (NuGetConfigurationException ex) when (ex.InnerException is UnauthorizedAccessException) - { - logger.ErrorException("Error loading machine wide settings", ex.InnerException ?? ex); - return new NullSettings(); - } - - return settings; - }); - } - } - - internal sealed class NugetOrgOfficialV2PackageSources : NuGetPackageSources - { - static readonly PackageSource PackageSourceV2 = - new(NuGetConstants.V2FeedUrl, "nuget.org", true, true, false) + settings = global::NuGet.Configuration.Settings.LoadDefaultSettings(workingDirectory, + configFileName: null, + machineWideSettings: new XPlatMachineWideSetting()); + } + catch (NuGetConfigurationException ex) when (ex.InnerException is UnauthorizedAccessException) { - ProtocolVersion = (int) NuGetProtocolVersion.V2, - IsMachineWide = true - }; + logger.ErrorException("Error loading machine wide settings", ex.InnerException ?? ex); + return new NullSettings(); + } - public NugetOrgOfficialV2PackageSources() : base(new NullSettings(), new List {PackageSourceV2}) - { - } + return settings; + }); } +} - internal sealed class NugetOrgOfficialV3PackageSources : NuGetPackageSources +internal sealed class NugetOrgOfficialV2PackageSources : NuGetPackageSources +{ + static readonly PackageSource PackageSourceV2 = + new(NuGetConstants.V2FeedUrl, "nuget.org", true, true, false) + { + ProtocolVersion = (int) NuGetProtocolVersion.V2, + IsMachineWide = true + }; + + public NugetOrgOfficialV2PackageSources() : base(new NullSettings(), new List {PackageSourceV2}) { - static readonly PackageSource PackageSourceV3 = - new(NuGetConstants.V3FeedUrl, "nuget.org", true, true, false) - { - ProtocolVersion = (int) NuGetProtocolVersion.V3, - IsMachineWide = true - }; + } +} - public NugetOrgOfficialV3PackageSources() : base(new NullSettings(), new List {PackageSourceV3}) +internal sealed class NugetOrgOfficialV3PackageSources : NuGetPackageSources +{ + static readonly PackageSource PackageSourceV3 = + new(NuGetConstants.V3FeedUrl, "nuget.org", true, true, false) { - } + ProtocolVersion = (int) NuGetProtocolVersion.V3, + IsMachineWide = true + }; + + public NugetOrgOfficialV3PackageSources() : base(new NullSettings(), new List {PackageSourceV3}) + { } +} - internal class NuGetMachineWidePackageSources : NuGetPackageSources +internal class NuGetMachineWidePackageSources : NuGetPackageSources +{ + public NuGetMachineWidePackageSources([NotNull] ISnapFilesystem filesystem, [NotNull] string workingDirectory) { - public NuGetMachineWidePackageSources([NotNull] ISnapFilesystem filesystem, [NotNull] string workingDirectory) - { - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - var nugetMachineWideSettings = new NuGetMachineWideSettings(filesystem, workingDirectory); - var packageSources = new List(); + var nugetMachineWideSettings = new NuGetMachineWideSettings(filesystem, workingDirectory); + var packageSources = new List(); - var nugetConfigReader = new NuGetConfigFileReader(); - foreach (var packageSource in nugetConfigReader.ReadNugetSources(workingDirectory).Where(x => x.IsEnabled)) + var nugetConfigReader = new NuGetConfigFileReader(); + foreach (var packageSource in nugetConfigReader.ReadNugetSources(workingDirectory).Where(x => x.IsEnabled)) + { + if (!packageSources.Contains(packageSource)) { - if (!packageSources.Contains(packageSource)) - { - packageSources.Add(packageSource); - } + packageSources.Add(packageSource); } - - Items = packageSources; - Settings = nugetMachineWideSettings.Settings; } + + Items = packageSources; + Settings = nugetMachineWideSettings.Settings; } +} - internal class NuGetInMemoryPackageSources : NuGetPackageSources +internal class NuGetInMemoryPackageSources : NuGetPackageSources +{ + public NuGetInMemoryPackageSources(string tempDirectory, IReadOnlyCollection packageSources) : base( + new NugetInMemorySettings(tempDirectory), packageSources) { - public NuGetInMemoryPackageSources(string tempDirectory, IReadOnlyCollection packageSources) : base( - new NugetInMemorySettings(tempDirectory), packageSources) - { - - } - public NuGetInMemoryPackageSources(string tempDirectory, PackageSource packageSource) : this(tempDirectory, new List { packageSource }) - { - - } } - internal interface INuGetPackageSources : IEnumerable + public NuGetInMemoryPackageSources(string tempDirectory, PackageSource packageSource) : this(tempDirectory, new List { packageSource }) { - ISettings Settings { get; } - IReadOnlyCollection Items { get; } + } +} - internal class NuGetPackageSources : INuGetPackageSources - { - public ISettings Settings { get; protected set; } - public IReadOnlyCollection Items { get; protected set; } +internal interface INuGetPackageSources : IEnumerable +{ + ISettings Settings { get; } + IReadOnlyCollection Items { get; } +} - public static NuGetPackageSources Empty => new(); +internal class NuGetPackageSources : INuGetPackageSources +{ + public ISettings Settings { get; protected set; } + public IReadOnlyCollection Items { get; protected set; } - protected NuGetPackageSources() - { - Items = new List(); - Settings = new NullSettings(); - } + public static NuGetPackageSources Empty => new(); - [UsedImplicitly] - public NuGetPackageSources([NotNull] ISettings settings) : this(settings, - settings.GetConfigFilePaths().Select(x => new PackageSource(x)).ToList()) - { - if (settings == null) throw new ArgumentNullException(nameof(settings)); - } + protected NuGetPackageSources() + { + Items = new List(); + Settings = new NullSettings(); + } - public NuGetPackageSources([NotNull] ISettings settings, [NotNull] IReadOnlyCollection sources) - { - Items = sources ?? throw new ArgumentNullException(nameof(sources)); - Settings = settings ?? throw new ArgumentNullException(nameof(settings)); - } + [UsedImplicitly] + public NuGetPackageSources([NotNull] ISettings settings) : this(settings, + settings.GetConfigFilePaths().Select(x => new PackageSource(x)).ToList()) + { + if (settings == null) throw new ArgumentNullException(nameof(settings)); + } - public IEnumerator GetEnumerator() - { - return Items.GetEnumerator(); - } + public NuGetPackageSources([NotNull] ISettings settings, [NotNull] IReadOnlyCollection sources) + { + Items = sources ?? throw new ArgumentNullException(nameof(sources)); + Settings = settings ?? throw new ArgumentNullException(nameof(settings)); + } - public override string ToString() - { - return string.Join(",", Items.Select(s => s.SourceUri.ToString())); - } + public IEnumerator GetEnumerator() + { + return Items.GetEnumerator(); + } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + public override string ToString() + { + return string.Join(",", Items.Select(s => s.SourceUri.ToString())); } -} + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } +} \ No newline at end of file diff --git a/src/Snap/NuGet/NuGetProtocolVersion.cs b/src/Snap/NuGet/NuGetProtocolVersion.cs index 0398ea69..d77305f6 100644 --- a/src/Snap/NuGet/NuGetProtocolVersion.cs +++ b/src/Snap/NuGet/NuGetProtocolVersion.cs @@ -1,11 +1,10 @@ using JetBrains.Annotations; -namespace Snap.NuGet +namespace Snap.NuGet; + +public enum NuGetProtocolVersion { - public enum NuGetProtocolVersion - { - [UsedImplicitly] NotSupported = 0, - V2 = 1, - V3 = 2 - } -} + [UsedImplicitly] NotSupported = 0, + V2 = 1, + V3 = 2 +} \ No newline at end of file diff --git a/src/Snap/NuGet/NugetConcurrentSourceRepositoryCache.cs b/src/Snap/NuGet/NugetConcurrentSourceRepositoryCache.cs index 7ad61745..faf0190a 100644 --- a/src/Snap/NuGet/NugetConcurrentSourceRepositoryCache.cs +++ b/src/Snap/NuGet/NugetConcurrentSourceRepositoryCache.cs @@ -4,22 +4,21 @@ using NuGet.Configuration; using NuGet.Protocol.Core.Types; -namespace Snap.NuGet +namespace Snap.NuGet; + +internal class NugetConcurrentSourceRepositoryCache { - internal class NugetConcurrentSourceRepositoryCache - { - readonly ConcurrentDictionary _packageSources = new(); + readonly ConcurrentDictionary _packageSources = new(); - public SourceRepository GetOrAdd([NotNull] PackageSource source) - { - if (source == null) throw new ArgumentNullException(nameof(source)); - return _packageSources.GetOrAdd(source, CreateSourceRepository); - } + public SourceRepository GetOrAdd([NotNull] PackageSource source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + return _packageSources.GetOrAdd(source, CreateSourceRepository); + } - static SourceRepository CreateSourceRepository([NotNull] PackageSource packageSource) - { - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - return new SourceRepository(packageSource, Repository.Provider.GetCoreV3()); - } + static SourceRepository CreateSourceRepository([NotNull] PackageSource packageSource) + { + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + return new SourceRepository(packageSource, Repository.Provider.GetCoreV3()); } -} +} \ No newline at end of file diff --git a/src/Snap/NuGet/NugetInMemorySettings.cs b/src/Snap/NuGet/NugetInMemorySettings.cs index 00488272..2fe13ffb 100644 --- a/src/Snap/NuGet/NugetInMemorySettings.cs +++ b/src/Snap/NuGet/NugetInMemorySettings.cs @@ -3,50 +3,49 @@ using JetBrains.Annotations; using NuGet.Configuration; -namespace Snap.NuGet +namespace Snap.NuGet; + +internal sealed class NugetInMemorySettings : ISettings { - internal sealed class NugetInMemorySettings : ISettings + readonly Settings _settings; + + public NugetInMemorySettings([NotNull] string tempDirectory) { - readonly Settings _settings; - - public NugetInMemorySettings([NotNull] string tempDirectory) - { - if (tempDirectory == null) throw new ArgumentNullException(nameof(tempDirectory)); - _settings = new Settings(tempDirectory); - } - - public SettingSection GetSection(string sectionName) - { - return _settings.GetSection(sectionName); - } - - public void AddOrUpdate(string sectionName, SettingItem item) - { - _settings.AddOrUpdate(sectionName, item); - } - - public void Remove(string sectionName, SettingItem item) - { - _settings.Remove(sectionName, item); - } - - public void SaveToDisk() - { - - } + if (tempDirectory == null) throw new ArgumentNullException(nameof(tempDirectory)); + _settings = new Settings(tempDirectory); + } + + public SettingSection GetSection(string sectionName) + { + return _settings.GetSection(sectionName); + } + + public void AddOrUpdate(string sectionName, SettingItem item) + { + _settings.AddOrUpdate(sectionName, item); + } - public IList GetConfigFilePaths() - { - return _settings.GetConfigFilePaths(); - } + public void Remove(string sectionName, SettingItem item) + { + _settings.Remove(sectionName, item); + } + + public void SaveToDisk() + { + + } - public IList GetConfigRoots() - { - return _settings.GetConfigRoots(); - } + public IList GetConfigFilePaths() + { + return _settings.GetConfigFilePaths(); + } - #pragma warning disable CS0067 - public event EventHandler SettingsChanged; - #pragma warning restore CS0067 + public IList GetConfigRoots() + { + return _settings.GetConfigRoots(); } -} + +#pragma warning disable CS0067 + public event EventHandler SettingsChanged; +#pragma warning restore CS0067 +} \ No newline at end of file diff --git a/src/Snap/NuGet/NugetLogger.cs b/src/Snap/NuGet/NugetLogger.cs index 86257535..99a23a27 100644 --- a/src/Snap/NuGet/NugetLogger.cs +++ b/src/Snap/NuGet/NugetLogger.cs @@ -4,57 +4,56 @@ using Snap.Logging; using LogLevel = NuGet.Common.LogLevel; -namespace Snap.NuGet +namespace Snap.NuGet; + +internal interface ISnapNugetLogger : ILogger { - internal interface ISnapNugetLogger : ILogger - { - } +} - internal class NugetLogger : LoggerBase, ISnapNugetLogger - { - static ILog _logger; +internal class NugetLogger : LoggerBase, ISnapNugetLogger +{ + static ILog _logger; - public NugetLogger(ILog logger) - { - _logger = logger ?? LogProvider.For(); - } + public NugetLogger(ILog logger) + { + _logger = logger ?? LogProvider.For(); + } - public override void Log(ILogMessage message) - { - message.Message = message.Message?.TrimStart(); - - switch (message.Level) - { - case LogLevel.Verbose: - _logger.Trace($"{message.Message}"); - break; - case LogLevel.Debug: - _logger.Debug($"{message.Message}"); - break; - case LogLevel.Information: - _logger.Info($"{message.Message}"); - break; - case LogLevel.Minimal: - _logger.Trace($"{message.Message}"); - break; - case LogLevel.Warning: - _logger.Warn($"{message.Message}"); - break; - case LogLevel.Error: - _logger.Error($"{message.Message}"); - break; - default: - throw new ArgumentOutOfRangeException($"Invalid log level: {message.Level}"); - } - } + public override void Log(ILogMessage message) + { + message.Message = message.Message?.TrimStart(); - public override Task LogAsync(ILogMessage message) + switch (message.Level) { - Log(message); - return CompletedTask; + case LogLevel.Verbose: + _logger.Trace($"{message.Message}"); + break; + case LogLevel.Debug: + _logger.Debug($"{message.Message}"); + break; + case LogLevel.Information: + _logger.Info($"{message.Message}"); + break; + case LogLevel.Minimal: + _logger.Trace($"{message.Message}"); + break; + case LogLevel.Warning: + _logger.Warn($"{message.Message}"); + break; + case LogLevel.Error: + _logger.Error($"{message.Message}"); + break; + default: + throw new ArgumentOutOfRangeException($"Invalid log level: {message.Level}"); } + } - static readonly Task CompletedTask = Task.FromResult(0); + public override Task LogAsync(ILogMessage message) + { + Log(message); + return CompletedTask; } -} + + static readonly Task CompletedTask = Task.FromResult(0); +} \ No newline at end of file diff --git a/src/Snap/NuGet/NugetPackageSearchMedatadata.cs b/src/Snap/NuGet/NugetPackageSearchMedatadata.cs index a025e15a..c809914a 100644 --- a/src/Snap/NuGet/NugetPackageSearchMedatadata.cs +++ b/src/Snap/NuGet/NugetPackageSearchMedatadata.cs @@ -4,25 +4,24 @@ using NuGet.Configuration; using NuGet.Packaging.Core; -namespace Snap.NuGet +namespace Snap.NuGet; + +internal class NuGetPackageSearchMedatadata { - internal class NuGetPackageSearchMedatadata - { - public PackageIdentity Identity { get; } - public PackageSource Source { get; } - public DateTimeOffset? Published { get; } - public IReadOnlyCollection Dependencies { get; } + public PackageIdentity Identity { get; } + public PackageSource Source { get; } + public DateTimeOffset? Published { get; } + public IReadOnlyCollection Dependencies { get; } - public NuGetPackageSearchMedatadata( - PackageIdentity identity, - PackageSource source, - DateTimeOffset? published, - IEnumerable dependencies) - { - Identity = identity ?? throw new ArgumentNullException(nameof(identity)); - Source = source ?? throw new ArgumentNullException(nameof(source)); - Published = published; - Dependencies = dependencies?.ToList() ?? new List(); - } + public NuGetPackageSearchMedatadata( + PackageIdentity identity, + PackageSource source, + DateTimeOffset? published, + IEnumerable dependencies) + { + Identity = identity ?? throw new ArgumentNullException(nameof(identity)); + Source = source ?? throw new ArgumentNullException(nameof(source)); + Published = published; + Dependencies = dependencies?.ToList() ?? new List(); } -} +} \ No newline at end of file diff --git a/src/Snap/NuGet/NugetService.cs b/src/Snap/NuGet/NugetService.cs index 307be037..e0285d56 100644 --- a/src/Snap/NuGet/NugetService.cs +++ b/src/Snap/NuGet/NugetService.cs @@ -18,436 +18,434 @@ using Snap.Logging; using Snap.Logging.LogProviders; -namespace Snap.NuGet -{ - internal sealed class FindLocalPackagesResource : global::NuGet.Protocol.FindLocalPackagesResource - { - readonly IEnumerable _localPackageInfos; +namespace Snap.NuGet; - public FindLocalPackagesResource(IEnumerable localPackageInfos) - { - _localPackageInfos = localPackageInfos; - } +internal sealed class FindLocalPackagesResource : global::NuGet.Protocol.FindLocalPackagesResource +{ + readonly IEnumerable _localPackageInfos; - public override LocalPackageInfo GetPackage(Uri path, ILogger logger, CancellationToken token) - { - throw new NotSupportedException(); - } + public FindLocalPackagesResource(IEnumerable localPackageInfos) + { + _localPackageInfos = localPackageInfos; + } - public override LocalPackageInfo GetPackage(PackageIdentity identity, ILogger logger, CancellationToken token) - { - return GetPackages(logger, token).FirstOrDefault(localPackageInfo => localPackageInfo.Identity.Equals(identity)); - } + public override LocalPackageInfo GetPackage(Uri path, ILogger logger, CancellationToken token) + { + throw new NotSupportedException(); + } - public override IEnumerable FindPackagesById(string id, ILogger logger, CancellationToken token) - { - return _localPackageInfos.Where(x => !token.IsCancellationRequested && x.Identity.Id.Equals(id, StringComparison.Ordinal)); - } + public override LocalPackageInfo GetPackage(PackageIdentity identity, ILogger logger, CancellationToken token) + { + return GetPackages(logger, token).FirstOrDefault(localPackageInfo => localPackageInfo.Identity.Equals(identity)); + } - public override IEnumerable GetPackages(ILogger logger, CancellationToken token) - { - return _localPackageInfos.TakeWhile(localPackageInfo => !token.IsCancellationRequested); - } + public override IEnumerable FindPackagesById(string id, ILogger logger, CancellationToken token) + { + return _localPackageInfos.Where(x => !token.IsCancellationRequested && x.Identity.Id.Equals(id, StringComparison.Ordinal)); } - internal sealed class DownloadContext + public override IEnumerable GetPackages(ILogger logger, CancellationToken token) { - public PackageIdentity PackageIdentity { get; set; } - public long PackageFileSize { get; set; } - public int MaxTries { get; set; } + return _localPackageInfos.TakeWhile(_ => !token.IsCancellationRequested); + } +} + +internal sealed class DownloadContext +{ + public PackageIdentity PackageIdentity { get; set; } + public long PackageFileSize { get; set; } + public int MaxTries { get; set; } - public DownloadContext() - { + public DownloadContext() + { - } - - public DownloadContext([NotNull] SnapRelease snapRelease) - { - if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); - PackageIdentity = snapRelease.BuildPackageIdentity(); - PackageFileSize = snapRelease.IsFull ? snapRelease.FullFilesize : snapRelease.DeltaFilesize; - } } - internal interface INugetServiceProgressSource + public DownloadContext([NotNull] SnapRelease snapRelease) { - Action<(int progressPercentage, long bytesRead, long totalBytesDownloaded, long totalBytesToDownload)> Progress { get; set; } - void Raise(int progressPercentage, long bytesRead, long totalBytesDownloaded, long totalBytesToDownload); + if (snapRelease == null) throw new ArgumentNullException(nameof(snapRelease)); + PackageIdentity = snapRelease.BuildPackageIdentity(); + PackageFileSize = snapRelease.IsFull ? snapRelease.FullFilesize : snapRelease.DeltaFilesize; } +} - internal sealed class NugetServiceProgressSource : INugetServiceProgressSource - { - public Action<(int progressPercentage, long bytesRead, long totalBytesDownloaded, long totalBytesToDownload)> Progress { get; set; } +internal interface INugetServiceProgressSource +{ + Action<(int progressPercentage, long bytesRead, long totalBytesDownloaded, long totalBytesToDownload)> Progress { get; set; } + void Raise(int progressPercentage, long bytesRead, long totalBytesDownloaded, long totalBytesToDownload); +} - public void Raise(int progressPercentage, long bytesRead, long totalBytesDownloaded, long totalBytesToDownload) - { - Progress?.Invoke((progressPercentage, bytesRead, totalBytesDownloaded, totalBytesToDownload)); - } - } +internal sealed class NugetServiceProgressSource : INugetServiceProgressSource +{ + public Action<(int progressPercentage, long bytesRead, long totalBytesDownloaded, long totalBytesToDownload)> Progress { get; set; } - internal interface INugetService + public void Raise(int progressPercentage, long bytesRead, long totalBytesDownloaded, long totalBytesToDownload) { - Task> GetMetadatasAsync(string packageId, INuGetPackageSources packageSources, - bool includePrerelease, bool noCache = false, CancellationToken cancellationToken = default); + Progress?.Invoke((progressPercentage, bytesRead, totalBytesDownloaded, totalBytesToDownload)); + } +} - Task GetLatestMetadataAsync(string packageId, PackageSource packageSource, - bool includePreRelease, bool noCache = false, CancellationToken cancellationToken = default); +internal interface INugetService +{ + Task> GetMetadatasAsync(string packageId, INuGetPackageSources packageSources, + bool includePrerelease, bool noCache = false, CancellationToken cancellationToken = default); + + Task GetLatestMetadataAsync(string packageId, PackageSource packageSource, + bool includePreRelease, bool noCache = false, CancellationToken cancellationToken = default); - Task PushAsync(string packagePath, INuGetPackageSources packageSources, PackageSource packageSource, - int timeoutInSeconds, ISnapNugetLogger nugetLogger = default, - CancellationToken cancellationToken = default); + Task PushAsync(string packagePath, INuGetPackageSources packageSources, PackageSource packageSource, + int timeoutInSeconds, ISnapNugetLogger nugetLogger = default, + CancellationToken cancellationToken = default); - Task DeleteAsync([NotNull] PackageIdentity packageIdentity, INuGetPackageSources packageSources, PackageSource packageSource, - ISnapNugetLogger nugetLogger = default, CancellationToken cancellationToken = default); + Task DeleteAsync([NotNull] PackageIdentity packageIdentity, INuGetPackageSources packageSources, PackageSource packageSource, + ISnapNugetLogger nugetLogger = default, CancellationToken cancellationToken = default); - Task DownloadLatestAsync(string packageId, - [NotNull] PackageSource packageSource, bool includePreRelease, bool noCache = false, CancellationToken cancellationToken = default); + Task DownloadLatestAsync(string packageId, + [NotNull] PackageSource packageSource, bool includePreRelease, bool noCache = false, CancellationToken cancellationToken = default); - Task DownloadAsync([NotNull] PackageSource packageSource, PackageIdentity packageIdentity, CancellationToken cancellationToken); + Task DownloadAsync([NotNull] PackageSource packageSource, PackageIdentity packageIdentity, CancellationToken cancellationToken); - Task DownloadAsyncWithProgressAsync([NotNull] PackageSource packageSource, [NotNull] DownloadContext downloadContext, - INugetServiceProgressSource progressSource, CancellationToken cancellationToken); - } + Task DownloadAsyncWithProgressAsync([NotNull] PackageSource packageSource, [NotNull] DownloadContext downloadContext, + INugetServiceProgressSource progressSource, CancellationToken cancellationToken); +} - internal class NugetService : INugetService - { - readonly ILog _logger = LogProvider.For(); - readonly ISnapNugetLogger _nugetLogger; - readonly ISnapFilesystem _snapFilesystem; +internal class NugetService : INugetService +{ + readonly ILog _logger = LogProvider.For(); + readonly ISnapNugetLogger _nugetLogger; + readonly ISnapFilesystem _snapFilesystem; - readonly NugetConcurrentSourceRepositoryCache _packageSources = new(); + readonly NugetConcurrentSourceRepositoryCache _packageSources = new(); - public NugetService([NotNull] ISnapFilesystem snapFilesystem, [NotNull] ISnapNugetLogger snapNugetLogger) - { - _snapFilesystem = snapFilesystem ?? throw new ArgumentNullException(nameof(snapFilesystem)); - _nugetLogger = snapNugetLogger ?? throw new ArgumentNullException(nameof(snapNugetLogger)); - } + public NugetService([NotNull] ISnapFilesystem snapFilesystem, [NotNull] ISnapNugetLogger snapNugetLogger) + { + _snapFilesystem = snapFilesystem ?? throw new ArgumentNullException(nameof(snapFilesystem)); + _nugetLogger = snapNugetLogger ?? throw new ArgumentNullException(nameof(snapNugetLogger)); + } - public async Task> GetMetadatasAsync([NotNull] string packageId, - [NotNull] INuGetPackageSources packageSources, bool includePrerelease, bool noCache = false, CancellationToken cancellationToken = default) - { - if (packageId == null) throw new ArgumentNullException(nameof(packageId)); - if (packageSources == null) throw new ArgumentNullException(nameof(packageSources)); + public async Task> GetMetadatasAsync([NotNull] string packageId, + [NotNull] INuGetPackageSources packageSources, bool includePrerelease, bool noCache = false, CancellationToken cancellationToken = default) + { + if (packageId == null) throw new ArgumentNullException(nameof(packageId)); + if (packageSources == null) throw new ArgumentNullException(nameof(packageSources)); - var tasks = packageSources.Items.Select(x => GetMetadatasAsync(packageId, x, includePrerelease, noCache, cancellationToken)); + var tasks = packageSources.Items.Select(x => GetMetadatasAsync(packageId, x, includePrerelease, noCache, cancellationToken)); - var results = await Task.WhenAll(tasks); + var results = await Task.WhenAll(tasks); - return results - .SelectMany(r => r) - .Where(p => p?.Identity?.Version != null) - .ToList(); - } + return results + .SelectMany(r => r) + .Where(p => p?.Identity?.Version != null) + .ToList(); + } + + public async Task GetLatestMetadataAsync(string packageId, PackageSource packageSource, + bool includePreRelease = true, bool noCache = false, CancellationToken cancellationToken = default) + { + var medatadatas = (await GetMetadatasAsync(packageId, packageSource, includePreRelease, noCache, cancellationToken)).ToList(); + return medatadatas.OrderByDescending(x => x.Identity.Version).FirstOrDefault(); + } - public async Task GetLatestMetadataAsync(string packageId, PackageSource packageSource, - bool includePreRelease = true, bool noCache = false, CancellationToken cancellationToken = default) + public async Task DownloadLatestAsync(string packageId, PackageSource packageSource, + bool includePreRelease, bool noCache = false, CancellationToken cancellationToken = default) + { + var metadata = await GetLatestMetadataAsync(packageId, packageSource, includePreRelease, noCache, cancellationToken); + if (metadata == null) { - var medatadatas = (await GetMetadatasAsync(packageId, packageSource, includePreRelease, noCache, cancellationToken)).ToList(); - return medatadatas.OrderByDescending(x => x.Identity.Version).FirstOrDefault(); + return new DownloadResourceResult(DownloadResourceResultStatus.NotFound); } - public async Task DownloadLatestAsync(string packageId, PackageSource packageSource, - bool includePreRelease, bool noCache = false, CancellationToken cancellationToken = default) - { - var metadata = await GetLatestMetadataAsync(packageId, packageSource, includePreRelease, noCache, cancellationToken); - if (metadata == null) - { - return new DownloadResourceResult(DownloadResourceResultStatus.NotFound); - } + return await DownloadAsync(metadata.Source, metadata.Identity, cancellationToken); + } - return await DownloadAsync(metadata.Source, metadata.Identity, cancellationToken); - } + public async Task PushAsync([NotNull] string packagePath, [NotNull] INuGetPackageSources packageSources, + [NotNull] PackageSource packageSource, + int timeOutInSeconds, ISnapNugetLogger nugetLogger = default, CancellationToken cancellationToken = default) + { + if (packagePath == null) throw new ArgumentNullException(nameof(packagePath)); + if (packageSources == null) throw new ArgumentNullException(nameof(packageSources)); + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - public async Task PushAsync([NotNull] string packagePath, [NotNull] INuGetPackageSources packageSources, - [NotNull] PackageSource packageSource, - int timeOutInSeconds, ISnapNugetLogger nugetLogger = default, CancellationToken cancellationToken = default) + if (packageSource.IsLocalOrUncPath()) { - if (packagePath == null) throw new ArgumentNullException(nameof(packagePath)); - if (packageSources == null) throw new ArgumentNullException(nameof(packageSources)); - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + var destinationDirectory = packageSource.SourceUri.AbsolutePath; + _snapFilesystem.DirectoryCreateIfNotExists(destinationDirectory); - if (packageSource.IsLocalOrUncPath()) - { - var destinationDirectory = packageSource.SourceUri.AbsolutePath; - _snapFilesystem.DirectoryCreateIfNotExists(destinationDirectory); + var destinationFilename = _snapFilesystem.PathCombine(destinationDirectory, _snapFilesystem.PathGetFileName(packagePath)); - var destinationFilename = _snapFilesystem.PathCombine(destinationDirectory, _snapFilesystem.PathGetFileName(packagePath)); + await _snapFilesystem.FileCopyAsync(packagePath, destinationFilename, cancellationToken, false); - await _snapFilesystem.FileCopyAsync(packagePath, destinationFilename, cancellationToken, false); + return; + } - return; - } + var sourceRepository = _packageSources.GetOrAdd(packageSource); + var packageUpdateResource = await sourceRepository.GetResourceAsync(cancellationToken); - var sourceRepository = _packageSources.GetOrAdd(packageSource); - var packageUpdateResource = await sourceRepository.GetResourceAsync(cancellationToken); + await packageUpdateResource.Push(new List { packagePath }, null, timeOutInSeconds, false, _ => BuildApiKey(packageSources, packageSource), _ => null, + false, false, null, nugetLogger ?? NullLogger.Instance); + } - await packageUpdateResource.Push(packagePath, null, timeOutInSeconds, false, _ => BuildApiKey(packageSources, packageSource), _ => null, - false, false, null, nugetLogger ?? NullLogger.Instance); - } + public async Task DeleteAsync(PackageIdentity packageIdentity, INuGetPackageSources packageSources, PackageSource packageSource, + ISnapNugetLogger nugetLogger = default, CancellationToken cancellationToken = default) + { + if (packageIdentity == null) throw new ArgumentNullException(nameof(packageIdentity)); + if (packageSources == null) throw new ArgumentNullException(nameof(packageSources)); + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - public async Task DeleteAsync(PackageIdentity packageIdentity, INuGetPackageSources packageSources, PackageSource packageSource, - ISnapNugetLogger nugetLogger = default, CancellationToken cancellationToken = default) + if (packageSource.IsLocalOrUncPath()) { - if (packageIdentity == null) throw new ArgumentNullException(nameof(packageIdentity)); - if (packageSources == null) throw new ArgumentNullException(nameof(packageSources)); - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + var sourceDirectory = packageSource.SourceUri.AbsolutePath; + _snapFilesystem.DirectoryExistsThrowIfNotExists(sourceDirectory); - if (packageSource.IsLocalOrUncPath()) - { - var sourceDirectory = packageSource.SourceUri.AbsolutePath; - _snapFilesystem.DirectoryExistsThrowIfNotExists(sourceDirectory); - - var nupkg = _snapFilesystem.PathCombine(sourceDirectory, $"{packageIdentity.Id}.{packageIdentity.Version}.nupkg"); - var snupkg = _snapFilesystem.PathCombine(sourceDirectory, $"{packageIdentity.Id}.{packageIdentity.Version}.snupkg"); - _snapFilesystem.FileDelete(nupkg); - _snapFilesystem.FileDeleteIfExists(snupkg); - return; - } + var nupkg = _snapFilesystem.PathCombine(sourceDirectory, $"{packageIdentity.Id}.{packageIdentity.Version}.nupkg"); + var snupkg = _snapFilesystem.PathCombine(sourceDirectory, $"{packageIdentity.Id}.{packageIdentity.Version}.snupkg"); + _snapFilesystem.FileDelete(nupkg); + _snapFilesystem.FileDeleteIfExists(snupkg); + return; + } - var sourceRepository = _packageSources.GetOrAdd(packageSource); - var packageUpdateResource = await sourceRepository.GetResourceAsync(cancellationToken); + var sourceRepository = _packageSources.GetOrAdd(packageSource); + var packageUpdateResource = await sourceRepository.GetResourceAsync(cancellationToken); - await packageUpdateResource.Delete(packageIdentity.Id, packageIdentity.Version.ToNormalizedString(), - _ => BuildApiKey(packageSources, packageSource), _ => true, false, nugetLogger ?? NullLogger.Instance); - } + await packageUpdateResource.Delete(packageIdentity.Id, packageIdentity.Version.ToNormalizedString(), + _ => BuildApiKey(packageSources, packageSource), _ => true, false, nugetLogger ?? NullLogger.Instance); + } - public Task DownloadAsync(PackageSource packageSource, [NotNull] PackageIdentity packageIdentity, CancellationToken cancellationToken) - { - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - if (packageIdentity == null) throw new ArgumentNullException(nameof(packageIdentity)); + public Task DownloadAsync(PackageSource packageSource, [NotNull] PackageIdentity packageIdentity, CancellationToken cancellationToken) + { + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + if (packageIdentity == null) throw new ArgumentNullException(nameof(packageIdentity)); - var downloadContext = new DownloadContext - { - PackageIdentity = packageIdentity, - PackageFileSize = 0 - }; + var downloadContext = new DownloadContext + { + PackageIdentity = packageIdentity, + PackageFileSize = 0 + }; - var progressSource = new NugetServiceProgressSource(); - return DownloadAsyncWithProgressAsync(packageSource, downloadContext, progressSource, cancellationToken); - } + var progressSource = new NugetServiceProgressSource(); + return DownloadAsyncWithProgressAsync(packageSource, downloadContext, progressSource, cancellationToken); + } - public async Task DownloadAsyncWithProgressAsync(PackageSource packageSource, DownloadContext downloadContext, - INugetServiceProgressSource progressSource, CancellationToken cancellationToken) - { - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - if (downloadContext == null) throw new ArgumentNullException(nameof(downloadContext)); + public async Task DownloadAsyncWithProgressAsync(PackageSource packageSource, DownloadContext downloadContext, + INugetServiceProgressSource progressSource, CancellationToken cancellationToken) + { + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + if (downloadContext == null) throw new ArgumentNullException(nameof(downloadContext)); - using var cacheContext = new SourceCacheContext {NoCache = true, DirectDownload = true}; + using var cacheContext = new SourceCacheContext {NoCache = true, DirectDownload = true}; - var tempPackagesDirectory = _snapFilesystem.PathCombine(cacheContext.GeneratedTempFolder, Guid.NewGuid().ToString()); - var redirectedPackagesDirectory = _snapFilesystem.PathCombine(tempPackagesDirectory, "nuget_install_dir"); - _snapFilesystem.DirectoryCreate(redirectedPackagesDirectory); + var tempPackagesDirectory = _snapFilesystem.PathCombine(cacheContext.GeneratedTempFolder, Guid.NewGuid().ToString()); + var redirectedPackagesDirectory = _snapFilesystem.PathCombine(tempPackagesDirectory, "nuget_install_dir"); + _snapFilesystem.DirectoryCreate(redirectedPackagesDirectory); - cacheContext.WithRefreshCacheTrue(); - cacheContext.GeneratedTempFolder = redirectedPackagesDirectory; + cacheContext.WithRefreshCacheTrue(); + cacheContext.GeneratedTempFolder = redirectedPackagesDirectory; - var totalBytesToDownload = downloadContext.PackageFileSize; + var totalBytesToDownload = downloadContext.PackageFileSize; - using (new DisposableAction(() => + using (new DisposableAction(() => + { + try { - try - { - _snapFilesystem.DirectoryDelete(redirectedPackagesDirectory, true); - } - catch (Exception e) - { - _logger.ErrorException("Exception thrown while attempting to delete nuget temp directory", e); - } - })) + _snapFilesystem.DirectoryDelete(redirectedPackagesDirectory, true); + } + catch (Exception e) { - var sourceRepository = _packageSources.GetOrAdd(packageSource); - var downloadResource = await sourceRepository.GetResourceAsync(cancellationToken); + _logger.ErrorException("Exception thrown while attempting to delete nuget temp directory", e); + } + })) + { + var sourceRepository = _packageSources.GetOrAdd(packageSource); + var downloadResource = await sourceRepository.GetResourceAsync(cancellationToken); - Uri downloadUrl; - HttpSource httpSource; - switch (downloadResource) - { - case LocalDownloadResource: - progressSource?.Raise(0, 0, 0, totalBytesToDownload); - - var localPackageMetadataResource = await BuildFindLocalPackagesResourceAsync(packageSource, cancellationToken); - var localPackageInfo = localPackageMetadataResource.GetPackage(downloadContext.PackageIdentity, _nugetLogger, cancellationToken); - if (localPackageInfo == null) - { - return new DownloadResourceResult(DownloadResourceResultStatus.NotFound); - } - - var memoryStream = await _snapFilesystem.FileReadAsync(localPackageInfo.Path, cancellationToken); - var downloadResourceResult = new DownloadResourceResult(memoryStream, packageSource.Name); - - progressSource?.Raise(100, downloadResourceResult.PackageStream.Length, - downloadResourceResult.PackageStream.Length, downloadContext.PackageFileSize); - - return downloadResourceResult; - case DownloadResourceV3 downloadResourceV3: - httpSource = downloadResourceV3.BuildHttpSource(); - downloadUrl = await downloadResourceV3.BuildDownloadUrlV3Async(downloadContext.PackageIdentity, _nugetLogger, cancellationToken); - break; - case DownloadResourceV2Feed downloadResourceV2Feed: - var v2FeedParser = downloadResourceV2Feed.BuildV2FeedParser(); - if (v2FeedParser == null) - { - throw new Exception($"Unable to build {nameof(v2FeedParser)}"); - } + Uri downloadUrl; + HttpSource httpSource; + switch (downloadResource) + { + case LocalDownloadResource: + progressSource?.Raise(0, 0, 0, totalBytesToDownload); + + var localPackageMetadataResource = await BuildFindLocalPackagesResourceAsync(packageSource, cancellationToken); + var localPackageInfo = localPackageMetadataResource.GetPackage(downloadContext.PackageIdentity, _nugetLogger, cancellationToken); + if (localPackageInfo == null) + { + return new DownloadResourceResult(DownloadResourceResultStatus.NotFound); + } + + var memoryStream = await _snapFilesystem.FileReadAsync(localPackageInfo.Path, cancellationToken); + var downloadResourceResult = new DownloadResourceResult(memoryStream, packageSource.Name); + + progressSource?.Raise(100, downloadResourceResult.PackageStream.Length, + downloadResourceResult.PackageStream.Length, downloadContext.PackageFileSize); + + return downloadResourceResult; + case DownloadResourceV3 downloadResourceV3: + httpSource = downloadResourceV3.BuildHttpSource(); + downloadUrl = await downloadResourceV3.BuildDownloadUrlV3Async(downloadContext.PackageIdentity, _nugetLogger, cancellationToken); + break; + case DownloadResourceV2Feed downloadResourceV2Feed: + var v2FeedParser = downloadResourceV2Feed.BuildV2FeedParser(); + if (v2FeedParser == null) + { + throw new Exception($"Unable to build {nameof(v2FeedParser)}"); + } - var metadata = await v2FeedParser.GetPackage(downloadContext.PackageIdentity, cacheContext, _nugetLogger, cancellationToken); - Uri.TryCreate(metadata?.DownloadUrl, UriKind.Absolute, out downloadUrl); - httpSource = v2FeedParser.BuildHttpSource(); - break; - default: - throw new NotSupportedException(downloadResource.GetType().FullName); - } + var metadata = await v2FeedParser.GetPackage(downloadContext.PackageIdentity, cacheContext, _nugetLogger, cancellationToken); + Uri.TryCreate(metadata?.DownloadUrl, UriKind.Absolute, out downloadUrl); + httpSource = v2FeedParser.BuildHttpSource(); + break; + default: + throw new NotSupportedException(downloadResource.GetType().FullName); + } - if (downloadUrl == null) - { - throw new Exception("Failed to obtain download url"); - } + if (downloadUrl == null) + { + throw new Exception("Failed to obtain download url"); + } - if (httpSource == null) - { - throw new Exception("Failed to obtain http source"); - } + if (httpSource == null) + { + throw new Exception("Failed to obtain http source"); + } - var request = new HttpSourceRequest(downloadUrl, _nugetLogger) - { - IgnoreNotFounds = true, - MaxTries = Math.Max(1, downloadContext.MaxTries) - }; + var request = new HttpSourceRequest(downloadUrl, _nugetLogger) + { + IgnoreNotFounds = true, + MaxTries = Math.Max(1, downloadContext.MaxTries) + }; - async Task ProcessAsync(Stream packageStream) - { - if (packageStream == null) throw new ArgumentNullException(nameof(packageStream)); + async Task ProcessAsync(Stream packageStream) + { + if (packageStream == null) throw new ArgumentNullException(nameof(packageStream)); - var outputStream = new MemoryStream(); - var buffer = ArrayPool.Shared.Rent(84000); // Less than LOH + var outputStream = new MemoryStream(); + var buffer = ArrayPool.Shared.Rent(84000); // Less than LOH - progressSource?.Raise(0, 0, 0, totalBytesToDownload); + progressSource?.Raise(0, 0, 0, totalBytesToDownload); - var totalBytesDownloadedSoFar = 0L; - var bytesRead = 0; - while (!cancellationToken.IsCancellationRequested) + var totalBytesDownloadedSoFar = 0L; + var bytesRead = 0; + while (!cancellationToken.IsCancellationRequested) + { + bytesRead = await packageStream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken); + totalBytesDownloadedSoFar += bytesRead; + if (bytesRead == 0) { - bytesRead = await packageStream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken); - totalBytesDownloadedSoFar += bytesRead; - if (bytesRead == 0) - { - break; - } - - var thisProgressPercentage = downloadContext.PackageFileSize <= 0 ? 50 : - (int) Math.Floor((double) totalBytesDownloadedSoFar / downloadContext.PackageFileSize * 100d); + break; + } + + var thisProgressPercentage = downloadContext.PackageFileSize <= 0 ? 50 : + (int) Math.Floor((double) totalBytesDownloadedSoFar / downloadContext.PackageFileSize * 100d); - progressSource?.Raise(thisProgressPercentage, bytesRead, totalBytesDownloadedSoFar, totalBytesToDownload); + progressSource?.Raise(thisProgressPercentage, bytesRead, totalBytesDownloadedSoFar, totalBytesToDownload); - await outputStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken); - } + await outputStream.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken); + } - if (downloadContext.PackageFileSize <= 0) - { - progressSource?.Raise(100, bytesRead, totalBytesDownloadedSoFar, totalBytesToDownload); - } + if (downloadContext.PackageFileSize <= 0) + { + progressSource?.Raise(100, bytesRead, totalBytesDownloadedSoFar, totalBytesToDownload); + } - ArrayPool.Shared.Return(buffer); + ArrayPool.Shared.Return(buffer); - outputStream.Seek(0, SeekOrigin.Begin); + outputStream.Seek(0, SeekOrigin.Begin); - return new DownloadResourceResult(outputStream, packageSource.Source); - } - - return await httpSource.ProcessStreamAsync(request, ProcessAsync, cacheContext, _nugetLogger, cancellationToken); + return new DownloadResourceResult(outputStream, packageSource.Source); } + + return await httpSource.ProcessStreamAsync(request, ProcessAsync, cacheContext, _nugetLogger, cancellationToken); } + } - async Task> GetMetadatasAsync([NotNull] string packageId, - [NotNull] PackageSource packageSource, bool includePrerelease, bool noCache = false, CancellationToken cancellationToken = default) - { - if (string.IsNullOrWhiteSpace(packageId)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageId)); - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + async Task> GetMetadatasAsync([NotNull] string packageId, + [NotNull] PackageSource packageSource, bool includePrerelease, bool noCache = false, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(packageId)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(packageId)); + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - LocalPackageMetadataResource localPackageMetadataResource = null; + LocalPackageMetadataResource localPackageMetadataResource = null; - if (packageSource.IsLocalOrUncPath()) + if (packageSource.IsLocalOrUncPath()) + { + var sourceDirectory = packageSource.SourceUri.AbsolutePath; + if (!_snapFilesystem.DirectoryExists(sourceDirectory)) { - var sourceDirectory = packageSource.SourceUri.AbsolutePath; - if (!_snapFilesystem.DirectoryExists(sourceDirectory)) - { - return Enumerable.Empty(); - } - - localPackageMetadataResource = new LocalPackageMetadataResource(await BuildFindLocalPackagesResourceAsync(packageSource, cancellationToken)); + return Enumerable.Empty(); } - using var cacheContext = new SourceCacheContext(); - - if (noCache) - { - cacheContext.NoCache = true; - cacheContext.WithRefreshCacheTrue(); - } + localPackageMetadataResource = new LocalPackageMetadataResource(await BuildFindLocalPackagesResourceAsync(packageSource, cancellationToken)); + } - var sourceRepository = _packageSources.GetOrAdd(packageSource); - var metadataResource = localPackageMetadataResource ?? await sourceRepository.GetResourceAsync(cancellationToken); - var metadatas = await metadataResource.GetMetadataAsync(packageId, includePrerelease, - false, cacheContext, _nugetLogger, cancellationToken); + using var cacheContext = new SourceCacheContext(); - return metadatas.Select(metadata => BuildNuGetPackageSearchMedatadata(packageSource, metadata)); + if (noCache) + { + cacheContext.NoCache = true; + cacheContext.WithRefreshCacheTrue(); } - async Task BuildFindLocalPackagesResourceAsync([NotNull] PackageSource packageSource, CancellationToken cancellationToken) - { - if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); + var sourceRepository = _packageSources.GetOrAdd(packageSource); + var metadataResource = localPackageMetadataResource ?? await sourceRepository.GetResourceAsync(cancellationToken); + var metadatas = await metadataResource.GetMetadataAsync(packageId, includePrerelease, + false, cacheContext, _nugetLogger, cancellationToken); + + return metadatas.Select(metadata => BuildNuGetPackageSearchMedatadata(packageSource, metadata)); + } + + async Task BuildFindLocalPackagesResourceAsync([NotNull] PackageSource packageSource, CancellationToken cancellationToken) + { + if (packageSource == null) throw new ArgumentNullException(nameof(packageSource)); - if (!packageSource.IsLocalOrUncPath()) - { - throw new Exception($"Package packageSource is not a local resource. Name: {packageSource.Name}. Source: {packageSource.Source}"); - } + if (!packageSource.IsLocalOrUncPath()) + { + throw new Exception($"Package packageSource is not a local resource. Name: {packageSource.Name}. Source: {packageSource.Source}"); + } - var sourceDirectory = packageSource.SourceUri.AbsolutePath; - _snapFilesystem.DirectoryExistsThrowIfNotExists(sourceDirectory); + var sourceDirectory = packageSource.SourceUri.AbsolutePath; + _snapFilesystem.DirectoryExistsThrowIfNotExists(sourceDirectory); - var localPackagesInfosTasks = _snapFilesystem - .EnumerateFiles(sourceDirectory) - .Where(x => x.FullName.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) - .Select(async fileInfo => - { - await using var stream = _snapFilesystem.FileRead(fileInfo.FullName); + var localPackagesInfosTasks = _snapFilesystem + .EnumerateFiles(sourceDirectory) + .Where(x => x.FullName.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase)) + .Select(async fileInfo => + { + await using var stream = _snapFilesystem.FileRead(fileInfo.FullName); - var packageArchiveReader = new PackageArchiveReader(stream); - var nuspecReader = await packageArchiveReader.GetNuspecReaderAsync(cancellationToken); - var lazyNuspecReader = new Lazy(() => nuspecReader); - var packageIdentity = await packageArchiveReader.GetIdentityAsync(cancellationToken); - PackageReaderBase GetPackageReader() => packageArchiveReader; + var packageArchiveReader = new PackageArchiveReader(stream); + var nuspecReader = await packageArchiveReader.GetNuspecReaderAsync(cancellationToken); + var lazyNuspecReader = new Lazy(() => nuspecReader); + var packageIdentity = await packageArchiveReader.GetIdentityAsync(cancellationToken); - return new LocalPackageInfo(packageIdentity, fileInfo.FullName, fileInfo.LastWriteTimeUtc, lazyNuspecReader, GetPackageReader); - }); + return new LocalPackageInfo(packageIdentity, fileInfo.FullName, fileInfo.LastWriteTimeUtc, lazyNuspecReader, false); + }); - var localPackageInfos = await Task.WhenAll(localPackagesInfosTasks); + var localPackageInfos = await Task.WhenAll(localPackagesInfosTasks); - return new FindLocalPackagesResource(localPackageInfos); - } + return new FindLocalPackagesResource(localPackageInfos); + } - static NuGetPackageSearchMedatadata BuildNuGetPackageSearchMedatadata(PackageSource source, IPackageSearchMetadata metadata) - { - var deps = metadata.DependencySets - .SelectMany(set => set.Packages) - .Distinct(); + static NuGetPackageSearchMedatadata BuildNuGetPackageSearchMedatadata(PackageSource source, IPackageSearchMetadata metadata) + { + var deps = metadata.DependencySets + .SelectMany(set => set.Packages) + .Distinct(); - return new NuGetPackageSearchMedatadata(metadata.Identity, source, metadata.Published, deps); - } + return new NuGetPackageSearchMedatadata(metadata.Identity, source, metadata.Published, deps); + } - static string BuildApiKey(INuGetPackageSources packageSources, PackageSource packageSource) + static string BuildApiKey(INuGetPackageSources packageSources, PackageSource packageSource) + { + if (packageSources.Settings == null + || packageSource.Source == null) { - if (packageSources.Settings == null - || packageSource.Source == null) - { - return string.Empty; - } + return string.Empty; + } - var decryptedApikey = SettingsUtility.GetDecryptedValueForAddItem( - packageSources.Settings, ConfigurationConstants.ApiKeys, packageSource.Source); + var decryptedApikey = SettingsUtility.GetDecryptedValueForAddItem( + packageSources.Settings, ConfigurationConstants.ApiKeys, packageSource.Source); - return decryptedApikey ?? string.Empty; // NB! Has to be string.Empty - } + return decryptedApikey ?? string.Empty; // NB! Has to be string.Empty } -} +} \ No newline at end of file diff --git a/src/Snap/Reflection/CecilAssemblyReflector.cs b/src/Snap/Reflection/CecilAssemblyReflector.cs index 35bb6bca..0a035141 100644 --- a/src/Snap/Reflection/CecilAssemblyReflector.cs +++ b/src/Snap/Reflection/CecilAssemblyReflector.cs @@ -7,98 +7,97 @@ using Snap.Extensions; using Snap.Reflection.Exceptions; -namespace Snap.Reflection +namespace Snap.Reflection; + +internal interface IAssemblyReflector { - internal interface IAssemblyReflector + string Location { get; } + string FileName { get; } + string FullName { get; } + ModuleDefinition MainModule {get;} + IEnumerable GetAttributes() where T : Attribute; + IAttributeReflector GetAttribute() where T : Attribute; + IEnumerable GetTypes(); + CecilResourceReflector GetResourceReflector(); + void AddResource(EmbeddedResource embeddedResource); + void AddCustomAttribute(CustomAttribute attribute); + void RewriteOrThrow(Expression> selector, Action rewriter); +} + +internal class CecilAssemblyReflector : IAssemblyReflector +{ + readonly AssemblyDefinition _assemblyDefinition; + + public string Location => _assemblyDefinition.MainModule.FileName; + public string FileName => _assemblyDefinition.MainModule.Name; + public string FullName => _assemblyDefinition.FullName; + public ModuleDefinition MainModule => _assemblyDefinition.MainModule; + + public CecilAssemblyReflector([NotNull] AssemblyDefinition assemblyDefinition) { - string Location { get; } - string FileName { get; } - string FullName { get; } - ModuleDefinition MainModule {get;} - IEnumerable GetAttributes() where T : Attribute; - IAttributeReflector GetAttribute() where T : Attribute; - IEnumerable GetTypes(); - CecilResourceReflector GetResourceReflector(); - void AddResource(EmbeddedResource embeddedResource); - void AddCustomAttribute(CustomAttribute attribute); - void RewriteOrThrow(Expression> selector, Action rewriter); + _assemblyDefinition = assemblyDefinition ?? throw new ArgumentNullException(nameof(assemblyDefinition)); } - internal class CecilAssemblyReflector : IAssemblyReflector + public IEnumerable GetAttributes() where T : Attribute { - readonly AssemblyDefinition _assemblyDefinition; - - public string Location => _assemblyDefinition.MainModule.FileName; - public string FileName => _assemblyDefinition.MainModule.Name; - public string FullName => _assemblyDefinition.FullName; - public ModuleDefinition MainModule => _assemblyDefinition.MainModule; - - public CecilAssemblyReflector([NotNull] AssemblyDefinition assemblyDefinition) + if (!_assemblyDefinition.HasCustomAttributes) { - _assemblyDefinition = assemblyDefinition ?? throw new ArgumentNullException(nameof(assemblyDefinition)); + return Array.Empty(); } - public IEnumerable GetAttributes() where T : Attribute - { - if (!_assemblyDefinition.HasCustomAttributes) - { - return Array.Empty(); - } + var expectedTypeName = typeof(T).Name; + return _assemblyDefinition.CustomAttributes + .Where(a => a.AttributeType.Name == expectedTypeName) + .Select(a => new CecilAttributeReflector(a)) + .ToList(); + } - var expectedTypeName = typeof(T).Name; - return _assemblyDefinition.CustomAttributes - .Where(a => a.AttributeType.Name == expectedTypeName) - .Select(a => new CecilAttributeReflector(a)) - .ToList(); - } + public IAttributeReflector GetAttribute() where T : Attribute + { + return GetAttributes().SingleOrDefault(); + } - public IAttributeReflector GetAttribute() where T : Attribute + public IEnumerable GetTypes() + { + var result = new List(); + var modules = _assemblyDefinition.Modules; + foreach (var module in modules) { - return GetAttributes().SingleOrDefault(); + var types = module.GetTypes(); + result.AddRange(types.Select(type => new CecilTypeReflector(type))); } + return result; + } - public IEnumerable GetTypes() - { - var result = new List(); - var modules = _assemblyDefinition.Modules; - foreach (var module in modules) - { - var types = module.GetTypes(); - result.AddRange(types.Select(type => new CecilTypeReflector(type))); - } - return result; - } + public CecilResourceReflector GetResourceReflector() + { + return new(_assemblyDefinition); + } - public CecilResourceReflector GetResourceReflector() - { - return new(_assemblyDefinition); - } + public void AddCustomAttribute([NotNull] CustomAttribute attribute) + { + if (attribute == null) throw new ArgumentNullException(nameof(attribute)); + _assemblyDefinition.CustomAttributes.Add(attribute); + } - public void AddCustomAttribute([NotNull] CustomAttribute attribute) - { - if (attribute == null) throw new ArgumentNullException(nameof(attribute)); - _assemblyDefinition.CustomAttributes.Add(attribute); - } + public void AddResource([NotNull] EmbeddedResource embeddedResource) + { + if (embeddedResource == null) throw new ArgumentNullException(nameof(embeddedResource)); + _assemblyDefinition.MainModule.Resources.Add(embeddedResource); + } - public void AddResource([NotNull] EmbeddedResource embeddedResource) - { - if (embeddedResource == null) throw new ArgumentNullException(nameof(embeddedResource)); - _assemblyDefinition.MainModule.Resources.Add(embeddedResource); - } + public void RewriteOrThrow([NotNull] Expression> selector, + [NotNull] Action rewriter) + { + if (selector == null) throw new ArgumentNullException(nameof(selector)); + if (rewriter == null) throw new ArgumentNullException(nameof(rewriter)); - public void RewriteOrThrow([NotNull] Expression> selector, - [NotNull] Action rewriter) + var (typeDefinition, autoPropertyDefinition, setterName, getterName) = _assemblyDefinition.ResolveAutoProperty(selector); + if (autoPropertyDefinition == null) { - if (selector == null) throw new ArgumentNullException(nameof(selector)); - if (rewriter == null) throw new ArgumentNullException(nameof(rewriter)); - - var (typeDefinition, autoPropertyDefinition, setterName, getterName) = _assemblyDefinition.ResolveAutoProperty(selector); - if (autoPropertyDefinition == null) - { - throw new CecilAutoPropertyNotFoundException(_assemblyDefinition, selector.BuildMemberName()); - } - - rewriter(typeDefinition, getterName, setterName, autoPropertyDefinition); + throw new CecilAutoPropertyNotFoundException(_assemblyDefinition, selector.BuildMemberName()); } + + rewriter(typeDefinition, getterName, setterName, autoPropertyDefinition); } -} +} \ No newline at end of file diff --git a/src/Snap/Reflection/CecilAttributeReflector.cs b/src/Snap/Reflection/CecilAttributeReflector.cs index cac3cd0e..df7a234b 100644 --- a/src/Snap/Reflection/CecilAttributeReflector.cs +++ b/src/Snap/Reflection/CecilAttributeReflector.cs @@ -4,47 +4,46 @@ using JetBrains.Annotations; using Mono.Cecil; -namespace Snap.Reflection +namespace Snap.Reflection; + +internal interface IAttributeReflector +{ + IDictionary Values { get; } +} + +internal class CecilAttributeReflector : IAttributeReflector { - internal interface IAttributeReflector + readonly CustomAttribute _attribute; + IDictionary _values; + + public CecilAttributeReflector([NotNull] CustomAttribute attribute) { - IDictionary Values { get; } + _attribute = attribute ?? throw new ArgumentNullException(nameof(attribute)); } - internal class CecilAttributeReflector : IAttributeReflector + public IDictionary Values { - readonly CustomAttribute _attribute; - IDictionary _values; - - public CecilAttributeReflector([NotNull] CustomAttribute attribute) + get { - _attribute = attribute ?? throw new ArgumentNullException(nameof(attribute)); - } + if (_values != null) + { + return _values; + } - public IDictionary Values - { - get + _values = new Dictionary(); + var constructorArguments = _attribute.Constructor.Resolve().Parameters.Select(p => p.Name).ToList(); + var constructorParameters = _attribute.ConstructorArguments.Select(a => a.Value.ToString()).ToList(); + for (var i = 0; i < constructorArguments.Count; i++) { - if (_values != null) - { - return _values; - } - - _values = new Dictionary(); - var constructorArguments = _attribute.Constructor.Resolve().Parameters.Select(p => p.Name).ToList(); - var constructorParameters = _attribute.ConstructorArguments.Select(a => a.Value.ToString()).ToList(); - for (var i = 0; i < constructorArguments.Count; i++) - { - _values.Add(constructorArguments[i], constructorParameters[i]); - } - - foreach (var prop in _attribute.Properties) - { - _values.Add(prop.Name, prop.Argument.Value.ToString()); - } + _values.Add(constructorArguments[i], constructorParameters[i]); + } - return _values; + foreach (var prop in _attribute.Properties) + { + _values.Add(prop.Name, prop.Argument.Value.ToString()); } + + return _values; } } -} +} \ No newline at end of file diff --git a/src/Snap/Reflection/CecilResourceReflector.cs b/src/Snap/Reflection/CecilResourceReflector.cs index 3116aa25..173e2a06 100644 --- a/src/Snap/Reflection/CecilResourceReflector.cs +++ b/src/Snap/Reflection/CecilResourceReflector.cs @@ -5,54 +5,53 @@ using Mono.Cecil; using Snap.Reflection.Exceptions; -namespace Snap.Reflection +namespace Snap.Reflection; + +internal interface IResourceReflector +{ + IEnumerable GetResources(); + void RemoveOrThrow([NotNull] string name); + void RemoveAllOrThrow(string @namespace); +} + +internal class CecilResourceReflector : IResourceReflector { - internal interface IResourceReflector + readonly AssemblyDefinition _assemblyDefinition; + + public CecilResourceReflector([NotNull] AssemblyDefinition assemblyDefinition) { - IEnumerable GetResources(); - void RemoveOrThrow([NotNull] string name); - void RemoveAllOrThrow(string @namespace); + _assemblyDefinition = assemblyDefinition ?? throw new ArgumentNullException(nameof(assemblyDefinition)); } - internal class CecilResourceReflector : IResourceReflector + public IEnumerable GetResources() { - readonly AssemblyDefinition _assemblyDefinition; + return _assemblyDefinition.MainModule.Resources; + } - public CecilResourceReflector([NotNull] AssemblyDefinition assemblyDefinition) + public void RemoveOrThrow(string name) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + var resource = GetResources().SingleOrDefault(x => x.Name == name); + if (resource == null) { - _assemblyDefinition = assemblyDefinition ?? throw new ArgumentNullException(nameof(assemblyDefinition)); + throw new CecilResourceNotFoundException(_assemblyDefinition, name); } - public IEnumerable GetResources() - { - return _assemblyDefinition.MainModule.Resources; - } + _assemblyDefinition.MainModule.Resources.Remove(resource); + } - public void RemoveOrThrow(string name) + public void RemoveAllOrThrow(string @namespace) + { + var resourcesRemoved = 0; + foreach (var resource in GetResources().Where(x => @namespace == null || x.Name.StartsWith(@namespace)).ToList()) { - if (name == null) throw new ArgumentNullException(nameof(name)); - var resource = GetResources().SingleOrDefault(x => x.Name == name); - if (resource == null) - { - throw new CecilResourceNotFoundException(_assemblyDefinition, name); - } - - _assemblyDefinition.MainModule.Resources.Remove(resource); + RemoveOrThrow(resource.Name); + resourcesRemoved++; } - public void RemoveAllOrThrow(string @namespace) + if (resourcesRemoved <= 0) { - var resourcesRemoved = 0; - foreach (var resource in GetResources().Where(x => @namespace == null || x.Name.StartsWith(@namespace)).ToList()) - { - RemoveOrThrow(resource.Name); - resourcesRemoved++; - } - - if (resourcesRemoved <= 0) - { - throw new Exception($"Failed to remove any resources from assembly: {_assemblyDefinition.FullName}"); - } + throw new Exception($"Failed to remove any resources from assembly: {_assemblyDefinition.FullName}"); } } -} +} \ No newline at end of file diff --git a/src/Snap/Reflection/CecilTypeReflector.cs b/src/Snap/Reflection/CecilTypeReflector.cs index 0258417b..7d5372d3 100644 --- a/src/Snap/Reflection/CecilTypeReflector.cs +++ b/src/Snap/Reflection/CecilTypeReflector.cs @@ -4,40 +4,39 @@ using JetBrains.Annotations; using Mono.Cecil; -namespace Snap.Reflection +namespace Snap.Reflection; + +internal interface ITypeReflector { - internal interface ITypeReflector - { - IEnumerable GetAttributes() where T : Attribute; - string FullName { get; } - string Name { get; } - } + IEnumerable GetAttributes() where T : Attribute; + string FullName { get; } + string Name { get; } +} - internal class CecilTypeReflector : ITypeReflector - { - readonly TypeDefinition _type; +internal class CecilTypeReflector : ITypeReflector +{ + readonly TypeDefinition _type; - public string FullName => _type.FullName; - public string Name => _type.Name; + public string FullName => _type.FullName; + public string Name => _type.Name; - public CecilTypeReflector([NotNull] TypeDefinition type) - { - _type = type ?? throw new ArgumentNullException(nameof(type)); - } + public CecilTypeReflector([NotNull] TypeDefinition type) + { + _type = type ?? throw new ArgumentNullException(nameof(type)); + } - public IEnumerable GetAttributes() where T : Attribute + public IEnumerable GetAttributes() where T : Attribute + { + if (!_type.HasCustomAttributes) { - if (!_type.HasCustomAttributes) - { - return Array.Empty(); - } - - var expectedTypeName = typeof(T).Name; - return _type.CustomAttributes - .Where(a => a.AttributeType.Name == expectedTypeName) - .Select(a => new CecilAttributeReflector(a)) - .ToList(); + return Array.Empty(); } + var expectedTypeName = typeof(T).Name; + return _type.CustomAttributes + .Where(a => a.AttributeType.Name == expectedTypeName) + .Select(a => new CecilAttributeReflector(a)) + .ToList(); } -} + +} \ No newline at end of file diff --git a/src/Snap/Reflection/Exceptions/CecilAutoPropertyNotFoundException.cs b/src/Snap/Reflection/Exceptions/CecilAutoPropertyNotFoundException.cs index 7ea14f87..50d43f5a 100644 --- a/src/Snap/Reflection/Exceptions/CecilAutoPropertyNotFoundException.cs +++ b/src/Snap/Reflection/Exceptions/CecilAutoPropertyNotFoundException.cs @@ -1,15 +1,14 @@ using JetBrains.Annotations; using Mono.Cecil; -namespace Snap.Reflection.Exceptions +namespace Snap.Reflection.Exceptions; + +internal class CecilAutoPropertyNotFoundException : CecilReflectorException { - internal class CecilAutoPropertyNotFoundException : CecilReflectorException + public CecilAutoPropertyNotFoundException([NotNull] AssemblyDefinition assemblyDefinition, string propertyName) : + base(assemblyDefinition, $"Unable to find auto property: {propertyName}") { - public CecilAutoPropertyNotFoundException([NotNull] AssemblyDefinition assemblyDefinition, string propertyName) : - base(assemblyDefinition, $"Unable to find auto property: {propertyName}") - { - - } } -} + +} \ No newline at end of file diff --git a/src/Snap/Reflection/Exceptions/CecilReflectorException.cs b/src/Snap/Reflection/Exceptions/CecilReflectorException.cs index 5339db76..bc123d3e 100644 --- a/src/Snap/Reflection/Exceptions/CecilReflectorException.cs +++ b/src/Snap/Reflection/Exceptions/CecilReflectorException.cs @@ -2,16 +2,15 @@ using JetBrains.Annotations; using Mono.Cecil; -namespace Snap.Reflection.Exceptions +namespace Snap.Reflection.Exceptions; + +internal abstract class CecilReflectorException : Exception { - internal abstract class CecilReflectorException : Exception - { - public AssemblyDefinition AssemblyDefinition { [UsedImplicitly] get; } + public AssemblyDefinition AssemblyDefinition { [UsedImplicitly] get; } - protected CecilReflectorException([NotNull] AssemblyDefinition assemblyDefinition, [NotNull] string message) : base($"{message}. Assembly: {assemblyDefinition.FullName}.") - { - if (message == null) throw new ArgumentNullException(nameof(message)); - AssemblyDefinition = assemblyDefinition ?? throw new ArgumentNullException(nameof(assemblyDefinition)); - } + protected CecilReflectorException([NotNull] AssemblyDefinition assemblyDefinition, [NotNull] string message) : base($"{message}. Assembly: {assemblyDefinition.FullName}.") + { + if (message == null) throw new ArgumentNullException(nameof(message)); + AssemblyDefinition = assemblyDefinition ?? throw new ArgumentNullException(nameof(assemblyDefinition)); } -} +} \ No newline at end of file diff --git a/src/Snap/Reflection/Exceptions/CecilResourceNotFoundException.cs b/src/Snap/Reflection/Exceptions/CecilResourceNotFoundException.cs index d1422813..363fb3fe 100644 --- a/src/Snap/Reflection/Exceptions/CecilResourceNotFoundException.cs +++ b/src/Snap/Reflection/Exceptions/CecilResourceNotFoundException.cs @@ -1,13 +1,12 @@ using Mono.Cecil; -namespace Snap.Reflection.Exceptions +namespace Snap.Reflection.Exceptions; + +internal class CecilResourceNotFoundException : CecilReflectorException { - internal class CecilResourceNotFoundException : CecilReflectorException + public CecilResourceNotFoundException(AssemblyDefinition assemblyDefinition, string resourceName) : base(assemblyDefinition, + $"Unable to find resource with name: {resourceName}. Assembly: {assemblyDefinition.FullName}") { - public CecilResourceNotFoundException(AssemblyDefinition assemblyDefinition, string resourceName) : base(assemblyDefinition, - $"Unable to find resource with name: {resourceName}. Assembly: {assemblyDefinition.FullName}") - { - } } -} +} \ No newline at end of file diff --git a/src/Snap/Resources/SnapEmbeddedResourcesTypeRoot.cs b/src/Snap/Resources/SnapEmbeddedResourcesTypeRoot.cs index 57a0c6d3..8c4db339 100644 --- a/src/Snap/Resources/SnapEmbeddedResourcesTypeRoot.cs +++ b/src/Snap/Resources/SnapEmbeddedResourcesTypeRoot.cs @@ -1,6 +1,5 @@ -namespace Snap.Resources +namespace Snap.Resources; + +internal class SnapEmbeddedResourcesTypeRoot { - internal class SnapEmbeddedResourcesTypeRoot - { - } -} +} \ No newline at end of file diff --git a/src/Snap/Snap.Deps.targets b/src/Snap/Snap.Deps.targets index 6059e2bf..d591c0b9 100644 --- a/src/Snap/Snap.Deps.targets +++ b/src/Snap/Snap.Deps.targets @@ -1,20 +1,11 @@ - - all - - - - - - - - - - - - - - + + + + + + + diff --git a/src/Snap/Snap.csproj b/src/Snap/Snap.csproj index b838d798..0a16f445 100644 --- a/src/Snap/Snap.csproj +++ b/src/Snap/Snap.csproj @@ -8,7 +8,7 @@ true true true - net5.0 + net6.0 true false @@ -23,7 +23,6 @@ all runtime; build; native; contentfiles; analyzers - diff --git a/src/Snapx.Tests/Snapx.Tests.csproj b/src/Snapx.Tests/Snapx.Tests.csproj index 7f1ac886..9e13a613 100644 --- a/src/Snapx.Tests/Snapx.Tests.csproj +++ b/src/Snapx.Tests/Snapx.Tests.csproj @@ -5,7 +5,7 @@ false true - net5.0 + net6.0 @@ -19,10 +19,10 @@ - + - - + + all diff --git a/src/Snapx.sln.DotSettings.user b/src/Snapx.sln.DotSettings.user new file mode 100644 index 00000000..932ea636 --- /dev/null +++ b/src/Snapx.sln.DotSettings.user @@ -0,0 +1,4 @@ + + <SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <Solution /> +</SessionState> \ No newline at end of file diff --git a/src/Snapx/Api/Snapx.Dev.cs b/src/Snapx/Api/Snapx.Dev.cs index 4396e94b..3bc596f7 100644 --- a/src/Snapx/Api/Snapx.Dev.cs +++ b/src/Snapx/Api/Snapx.Dev.cs @@ -1,30 +1,29 @@ using ServiceStack; using System; -namespace snapx.Api -{ - [Route("/lock")] - [Route("/lock/{Name}/{Duration}")] - public class Lock : IReturn - { - public string Name { get; set; } - public TimeSpan Duration { get; set; } - } +namespace snapx.Api; - [Route("/renewlock")] - [Route("/renew/{Name}/{Duration}")] - public class RenewLock : IReturn - { - public string Name { get; set; } - public string Challenge { get; set; } - } +[Route("/lock")] +[Route("/lock/{Name}/{Duration}")] +public class Lock : IReturn +{ + public string Name { get; set; } + public TimeSpan Duration { get; set; } +} - [Route("/unlock")] - [Route("/unlock/{Name}/{Challenge}")] - public class Unlock : IReturn - { - public string Name { get; set; } - public string Challenge { get; set; } - public TimeSpan? BreakPeriod { get; set; } - } +[Route("/renewlock")] +[Route("/renew/{Name}/{Duration}")] +public class RenewLock : IReturn +{ + public string Name { get; set; } + public string Challenge { get; set; } } + +[Route("/unlock")] +[Route("/unlock/{Name}/{Challenge}")] +public class Unlock : IReturn +{ + public string Name { get; set; } + public string Challenge { get; set; } + public TimeSpan? BreakPeriod { get; set; } +} \ No newline at end of file diff --git a/src/Snapx/Core/ConsoleTable.cs b/src/Snapx/Core/ConsoleTable.cs index d5026b5d..6e70c89c 100644 --- a/src/Snapx/Core/ConsoleTable.cs +++ b/src/Snapx/Core/ConsoleTable.cs @@ -30,133 +30,131 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE using JetBrains.Annotations; using Snap.Logging; -namespace snapx.Core +namespace snapx.Core; + +internal sealed class ConsoleTable { - internal sealed class ConsoleTable + public string Header { get; set; } + [UsedImplicitly] public IList Columns { get; } + [UsedImplicitly] public IList Rows { get; } + [UsedImplicitly] public ConsoleTableOptions Options { get; } + + public ConsoleTable([NotNull] params string[] columns) + :this(columns.ToList()) { - public string Header { get; set; } - [UsedImplicitly] public IList Columns { get; } - [UsedImplicitly] public IList Rows { get; } - [UsedImplicitly] public ConsoleTableOptions Options { get; } + } - public ConsoleTable([NotNull] params string[] columns) - :this(columns.ToList()) - { - } + public ConsoleTable([NotNull] IEnumerable columns) + : this(columns.ToList()) + { - public ConsoleTable([NotNull] IEnumerable columns) - : this(columns.ToList()) - { + } - } + public ConsoleTable([NotNull] List columns) + : this(new ConsoleTableOptions { Columns = new List(columns) }) + { + if (columns == null) throw new ArgumentNullException(nameof(columns)); + if (columns.Count == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(columns)); + } - public ConsoleTable([NotNull] List columns) - : this(new ConsoleTableOptions { Columns = new List(columns) }) - { - if (columns == null) throw new ArgumentNullException(nameof(columns)); - if (columns.Count == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(columns)); - } + public ConsoleTable([NotNull] ConsoleTableOptions options) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + Options = options ?? throw new ArgumentNullException(nameof(options)); + Rows = new List(); + Columns = new List(options.Columns); + } - public ConsoleTable([NotNull] ConsoleTableOptions options) - { - if (options == null) throw new ArgumentNullException(nameof(options)); - Options = options ?? throw new ArgumentNullException(nameof(options)); - Rows = new List(); - Columns = new List(options.Columns); - } + // ReSharper disable once UnusedMethodReturnValue.Global + public ConsoleTable AddRow([NotNull] object[] values) + { + if (values == null) throw new ArgumentNullException(nameof(values)); - // ReSharper disable once UnusedMethodReturnValue.Global - public ConsoleTable AddRow([NotNull] object[] values) + if (!Columns.Any()) { - if (values == null) throw new ArgumentNullException(nameof(values)); - - if (!Columns.Any()) - { - throw new Exception("Please set the columns first"); - } - - if (Columns.Count != values.Length) - { - throw new Exception( - $"The number columns in the row ({Columns.Count}) does not match the values ({values.Length}"); - } - - Rows.Add(values.ToArray()); - return this; + throw new Exception("Please set the columns first"); } - List ColumnLengths() + if (Columns.Count != values.Length) { - var columnLengths = Columns - .Select((t, i) => Rows.Select(x => x[i]) - .Union(new[] { Columns[i] }) - .Where(x => x != null) - .Select(x => - { - var s = x.ToString(); - return s?.Length ?? 0; - }).Max()) - .ToList(); - return columnLengths; + throw new Exception( + $"The number columns in the row ({Columns.Count}) does not match the values ({values.Length}"); } - public void Write([NotNull] ILog logger) - { - if (logger == null) throw new ArgumentNullException(nameof(logger)); + Rows.Add(values.ToArray()); + return this; + } - // find the longest column by searching each row - var columnLengths = ColumnLengths(); + List ColumnLengths() + { + var columnLengths = Columns + .Select((t, i) => Rows.Select(x => x[i]) + .Union(new[] { Columns[i] }) + .Where(x => x != null) + .Select(x => + { + var s = x.ToString(); + return s?.Length ?? 0; + }).Max()) + .ToList(); + return columnLengths; + } - // create the string format with padding - var format = Enumerable.Range(0, Columns.Count) - .Select(i => "| {" + i + ",-" + columnLengths[i] + "} ") - .Aggregate((s, a) => s + a) + " |"; + public void Write([NotNull] ILog logger) + { + if (logger == null) throw new ArgumentNullException(nameof(logger)); - // remove last pipe (|) - format = format[0..^1]; + // find the longest column by searching each row + var columnLengths = ColumnLengths(); - // find the longest formatted line - var maxRowLength = Math.Max(0, Rows.Any() ? Rows.Max(row => string.Format(format, row).Length) : 0); - var columnHeaders = string.Format(format, Columns.ToArray()); + // create the string format with padding + var format = Enumerable.Range(0, Columns.Count) + .Select(i => "| {" + i + ",-" + columnLengths[i] + "} ") + .Aggregate((s, a) => s + a) + " |"; - // longest line is greater of formatted columnHeader and longest row - var longestLine = Math.Min(Program.TerminalBufferWidth, Math.Max(maxRowLength, columnHeaders.Length)); + // remove last pipe (|) + format = format[0..^1]; - // add each row - var results = Rows.Select(row => string.Format(format, row)).ToList(); + // find the longest formatted line + var maxRowLength = Math.Max(0, Rows.Any() ? Rows.Max(row => string.Format(format, row).Length) : 0); + var columnHeaders = string.Format(format, Columns.ToArray()); - // create the divider - var divider = $"{string.Join(string.Empty, Enumerable.Repeat("-", longestLine))} "; + // longest line is greater of formatted columnHeader and longest row + var longestLine = Math.Min(Program.TerminalBufferWidth, Math.Max(maxRowLength, columnHeaders.Length)); - if (Header != null) - { - var dividerHeader = string.Join(string.Empty, Enumerable.Repeat("=", longestLine)); - foreach (var line in Header.Split("\n")) - { - logger.Info(line); - } - logger.Info(dividerHeader); - } + // add each row + var results = Rows.Select(row => string.Format(format, row)).ToList(); - logger.Info(columnHeaders); + // create the divider + var divider = $"{string.Join(string.Empty, Enumerable.Repeat("-", longestLine))} "; - foreach (var row in results) + if (Header != null) + { + var dividerHeader = string.Join(string.Empty, Enumerable.Repeat("=", longestLine)); + foreach (var line in Header.Split("\n")) { - logger.Info(divider); - logger.Info(row); + logger.Info(line); } + logger.Info(dividerHeader); + } - if (!Options.EnableCount) return; - - logger.Info(string.Empty); - logger.Info(" Count: {0}", Rows.Count); + logger.Info(columnHeaders); + + foreach (var row in results) + { + logger.Info(divider); + logger.Info(row); } - } - public class ConsoleTableOptions - { - public IEnumerable Columns { get; set; } = new List(); - public bool EnableCount { get; [UsedImplicitly] set; } = false; + if (!Options.EnableCount) return; + + logger.Info(string.Empty); + logger.Info(" Count: {0}", Rows.Count); } - } + +public class ConsoleTableOptions +{ + public IEnumerable Columns { get; set; } = new List(); + public bool EnableCount { get; [UsedImplicitly] set; } = false; +} \ No newline at end of file diff --git a/src/Snapx/Core/DistributedMutex.cs b/src/Snapx/Core/DistributedMutex.cs index 9b9fe3ed..c5adc30b 100644 --- a/src/Snapx/Core/DistributedMutex.cs +++ b/src/Snapx/Core/DistributedMutex.cs @@ -6,247 +6,246 @@ using Snap.Logging; using snapx.Api; -namespace snapx.Core +namespace snapx.Core; + +internal sealed class DistributedMutexUnknownException : Exception { - internal sealed class DistributedMutexUnknownException : Exception + public DistributedMutexUnknownException(string message) : base(message, null) { - public DistributedMutexUnknownException(string message) : base(message, null) - { - } } +} + +internal interface IDistributedMutexClient +{ + Task AcquireAsync(string name, TimeSpan lockDuration); + Task ReleaseLockAsync(string name, string challenge, TimeSpan? breakPeriod = null); + Task RenewAsync(string name, string challenge); +} - internal interface IDistributedMutexClient +internal sealed class DistributedMutexClient : IDistributedMutexClient +{ + readonly IHttpRestClientAsync _httpRestClientAsync; + + public DistributedMutexClient([NotNull] IHttpRestClientAsync httpRestClientAsync) { - Task AcquireAsync(string name, TimeSpan lockDuration); - Task ReleaseLockAsync(string name, string challenge, TimeSpan? breakPeriod = null); - Task RenewAsync(string name, string challenge); + _httpRestClientAsync = httpRestClientAsync ?? throw new ArgumentNullException(nameof(httpRestClientAsync)); } - internal sealed class DistributedMutexClient : IDistributedMutexClient + public Task AcquireAsync(string name, TimeSpan lockDuration) { - readonly IHttpRestClientAsync _httpRestClientAsync; - - public DistributedMutexClient([NotNull] IHttpRestClientAsync httpRestClientAsync) - { - _httpRestClientAsync = httpRestClientAsync ?? throw new ArgumentNullException(nameof(httpRestClientAsync)); - } - - public Task AcquireAsync(string name, TimeSpan lockDuration) - { - return _httpRestClientAsync.PostAsync(new Lock { Name = name, Duration = lockDuration }); - } + return _httpRestClientAsync.PostAsync(new Lock { Name = name, Duration = lockDuration }); + } - public Task ReleaseLockAsync(string name, string challenge, TimeSpan? breakPeriod) - { - return _httpRestClientAsync.DeleteAsync(new Unlock { Name = name, Challenge = challenge, BreakPeriod = breakPeriod }); - } + public Task ReleaseLockAsync(string name, string challenge, TimeSpan? breakPeriod) + { + return _httpRestClientAsync.DeleteAsync(new Unlock { Name = name, Challenge = challenge, BreakPeriod = breakPeriod }); + } - public Task RenewAsync(string name, string challenge) - { - return _httpRestClientAsync.PutAsync(new RenewLock { Name = name, Challenge = challenge }); - } + public Task RenewAsync(string name, string challenge) + { + return _httpRestClientAsync.PutAsync(new RenewLock { Name = name, Challenge = challenge }); } +} - internal interface IDistributedMutex : IAsyncDisposable +internal interface IDistributedMutex : IAsyncDisposable +{ + string Name { get; } + bool Acquired { get; } + bool Disposed { get; } + Task TryAquireAsync(TimeSpan retryDelayTs = default, int retries = 0); + Task TryReleaseAsync(); +} + +internal sealed class DistributedMutex : IDistributedMutex +{ + long _acquired; + long _disposed; + + readonly IDistributedMutexClient _distributedMutexClient; + readonly ILog _logger; + readonly CancellationToken _cancellationToken; + readonly bool _releaseOnDispose; + readonly SemaphoreSlim _semaphore; + string _challenge; + + public string Name { get; } + public bool Acquired => Interlocked.Read(ref _acquired) == 1; + public bool Disposed => Interlocked.Read(ref _disposed) == 1; + + public DistributedMutex([NotNull] IDistributedMutexClient distributedMutexClient, + [NotNull] ILog logger, [NotNull] string name, CancellationToken cancellationToken, bool releaseOnDispose = true) { - string Name { get; } - bool Acquired { get; } - bool Disposed { get; } - Task TryAquireAsync(TimeSpan retryDelayTs = default, int retries = 0); - Task TryReleaseAsync(); + _distributedMutexClient = distributedMutexClient ?? throw new ArgumentNullException(nameof(distributedMutexClient)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + Name = name ?? throw new ArgumentNullException(nameof(name)); + _cancellationToken = cancellationToken; + _releaseOnDispose = releaseOnDispose; + _semaphore = new SemaphoreSlim(1, 1); } - internal sealed class DistributedMutex : IDistributedMutex + public async Task TryAquireAsync(TimeSpan retryDelayTs = default, int retries = 0) { - long _acquired; - long _disposed; - - readonly IDistributedMutexClient _distributedMutexClient; - readonly ILog _logger; - readonly CancellationToken _cancellationToken; - readonly bool _releaseOnDispose; - readonly SemaphoreSlim _semaphore; - string _challenge; - - public string Name { get; } - public bool Acquired => Interlocked.Read(ref _acquired) == 1; - public bool Disposed => Interlocked.Read(ref _disposed) == 1; - - public DistributedMutex([NotNull] IDistributedMutexClient distributedMutexClient, - [NotNull] ILog logger, [NotNull] string name, CancellationToken cancellationToken, bool releaseOnDispose = true) + if (Disposed) { - _distributedMutexClient = distributedMutexClient ?? throw new ArgumentNullException(nameof(distributedMutexClient)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - Name = name ?? throw new ArgumentNullException(nameof(name)); - _cancellationToken = cancellationToken; - _releaseOnDispose = releaseOnDispose; - _semaphore = new SemaphoreSlim(1, 1); + throw new ObjectDisposedException(nameof(DistributedMutex), $"Mutex is already disposed. Name: {Name}"); } - public async Task TryAquireAsync(TimeSpan retryDelayTs = default, int retries = 0) + if (Acquired) { - if (Disposed) - { - throw new ObjectDisposedException(nameof(DistributedMutex), $"Mutex is already disposed. Name: {Name}"); - } + throw new SynchronizationLockException($"Mutex is already acquired: {Name}"); + } + + await _semaphore.WaitAsync(_cancellationToken); - if (Acquired) + retries = Math.Max(0, retries); + + return await RetryAsync(async () => + { + if (_cancellationToken.IsCancellationRequested) { - throw new SynchronizationLockException($"Mutex is already acquired: {Name}"); + return false; } - await _semaphore.WaitAsync(_cancellationToken); + _logger.Info($"Attempting to acquire mutex: {Name}."); - retries = Math.Max(0, retries); + try + { + _challenge = await _distributedMutexClient.AcquireAsync(Name, TimeSpan.FromHours(24)); + } + catch (Exception e) + { + _logger.InfoException($"Failed to acquire mutex: {Name}.", e); + throw; + } - return await RetryAsync(async () => + if (!string.IsNullOrEmpty(_challenge)) { - if (_cancellationToken.IsCancellationRequested) - { - return false; - } + Interlocked.Exchange(ref _acquired, 1); + _logger.Info($"Successfully acquired mutex: {Name}. "); + return true; + } - _logger.Info($"Attempting to acquire mutex: {Name}."); + throw new DistributedMutexUnknownException($"Challenge should not be null or empty. Mutex: {Name}. Challenge: {_challenge}"); - try - { - _challenge = await _distributedMutexClient.AcquireAsync(Name, TimeSpan.FromHours(24)); - } - catch (Exception e) - { - _logger.InfoException($"Failed to acquire mutex: {Name}.", e); - throw; - } + }, retryDelayTs, retries, ex => ex is DistributedMutexUnknownException); + } - if (!string.IsNullOrEmpty(_challenge)) - { - Interlocked.Exchange(ref _acquired, 1); - _logger.Info($"Successfully acquired mutex: {Name}. "); - return true; - } + public async Task TryReleaseAsync() + { + if (Disposed || !Acquired) + { + return false; + } - throw new DistributedMutexUnknownException($"Challenge should not be null or empty. Mutex: {Name}. Challenge: {_challenge}"); + try + { + _logger.Info($"Attempting to force release of mutex: {Name}."); + await _distributedMutexClient.ReleaseLockAsync(Name, _challenge, TimeSpan.Zero); + Interlocked.Exchange(ref _acquired, 0); + _logger.Info($"Successfully released mutex: {Name}."); + return true; + } + catch (WebServiceException exception) + { + _logger.InfoException($"Failed to force release mutex with name: {Name}.", exception); + return false; + } + } - }, retryDelayTs, retries, ex => ex is DistributedMutexUnknownException); + public static async Task TryForceReleaseAsync([NotNull] string name, [NotNull] IDistributedMutexClient distributedMutexClient, + [NotNull] ILog logger) + { + if (name == null) throw new ArgumentNullException(nameof(name)); + if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + try + { + logger.Info($"Attempting to force release of mutex: {name}."); + await distributedMutexClient.ReleaseLockAsync(name, null, TimeSpan.Zero); + logger.Info($"Successfully released mutex: {name}."); + return true; } + catch (WebServiceException exception) + { + logger.InfoException($"Failed to force release mutex with name: {name}.", exception); + return false; + } + } - public async Task TryReleaseAsync() + public async ValueTask DisposeAsync() + { + var disposed = Interlocked.Exchange(ref _disposed, 1) == 1; + var acquired = Interlocked.Read(ref _acquired) == 1; + if (disposed) { - if (Disposed || !Acquired) - { - return false; - } + return; + } - try + _semaphore.Dispose(); + + if (acquired && _releaseOnDispose) + { + var success = await RetryAsync(async () => { - _logger.Info($"Attempting to force release of mutex: {Name}."); - await _distributedMutexClient.ReleaseLockAsync(Name, _challenge, TimeSpan.Zero); + _logger.Info($"Disposing mutex: {Name}"); + await _distributedMutexClient.ReleaseLockAsync(Name, _challenge); Interlocked.Exchange(ref _acquired, 0); - _logger.Info($"Successfully released mutex: {Name}."); + _logger.Info($"Successfully disposed mutex: {Name}"); return true; - } - catch (WebServiceException exception) + }, TimeSpan.FromMilliseconds(500), 3, ex => { - _logger.InfoException($"Failed to force release mutex with name: {Name}.", exception); + _logger.ErrorException($"Unknown error disposing mutex: {Name}", ex); return false; - } - } + }); - public static async Task TryForceReleaseAsync([NotNull] string name, [NotNull] IDistributedMutexClient distributedMutexClient, - [NotNull] ILog logger) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - - try + if (!success) { - logger.Info($"Attempting to force release of mutex: {name}."); - await distributedMutexClient.ReleaseLockAsync(name, null, TimeSpan.Zero); - logger.Info($"Successfully released mutex: {name}."); - return true; - } - catch (WebServiceException exception) - { - logger.InfoException($"Failed to force release mutex with name: {name}.", exception); - return false; + _logger.Error($"Unknown error disposing mutex: {Name}."); } } + } - public async ValueTask DisposeAsync() + async Task RetryAsync(Func> retryFunc, TimeSpan delayTs, int retries = 0, Func shouldThrowFunc = null) + { + while (true) { - var disposed = Interlocked.Exchange(ref _disposed, 1) == 1; - var acquired = Interlocked.Read(ref _acquired) == 1; - if (disposed) + try { - return; + return await retryFunc(); } - - _semaphore.Dispose(); - - if (acquired && _releaseOnDispose) + catch (Exception e) { - var success = await RetryAsync(async () => - { - _logger.Info($"Disposing mutex: {Name}"); - await _distributedMutexClient.ReleaseLockAsync(Name, _challenge); - Interlocked.Exchange(ref _acquired, 0); - _logger.Info($"Successfully disposed mutex: {Name}"); - return true; - }, TimeSpan.FromMilliseconds(500), 3, ex => - { - _logger.ErrorException($"Unknown error disposing mutex: {Name}", ex); - return false; - }); - - if (!success) + if (--retries > 0) { - _logger.Error($"Unknown error disposing mutex: {Name}."); - } - } - } - - async Task RetryAsync(Func> retryFunc, TimeSpan delayTs, int retries = 0, Func shouldThrowFunc = null) - { - while (true) - { - try - { - return await retryFunc(); - } - catch (Exception e) - { - if (--retries > 0) + try { - try - { - await Task.Delay(delayTs, _cancellationToken); - } - catch (OperationCanceledException) - { - break; - } - - if (_cancellationToken.IsCancellationRequested) - { - break; - } - - continue; + await Task.Delay(delayTs, _cancellationToken); + } + catch (OperationCanceledException) + { + break; } - var shouldThrow = shouldThrowFunc?.Invoke(e) ?? false; - if (shouldThrow) + if (_cancellationToken.IsCancellationRequested) { - throw; + break; } - return default; + continue; } - } - return default; + var shouldThrow = shouldThrowFunc?.Invoke(e) ?? false; + if (shouldThrow) + { + throw; + } + + return default; + } } + + return default; } -} +} \ No newline at end of file diff --git a/src/Snapx/Core/SnapxEmbeddedResources.cs b/src/Snapx/Core/SnapxEmbeddedResources.cs index f5f3afe4..d5e8e616 100644 --- a/src/Snapx/Core/SnapxEmbeddedResources.cs +++ b/src/Snapx/Core/SnapxEmbeddedResources.cs @@ -3,169 +3,168 @@ using snapx.Resources; using Snap.Core.Resources; -namespace snapx.Core +namespace snapx.Core; + +internal interface ISnapxEmbeddedResources : IEmbedResources { - internal interface ISnapxEmbeddedResources : IEmbedResources - { - MemoryStream SetupWindowsX86 { get; } - MemoryStream WarpPackerWindowsX86 { get; } + MemoryStream SetupWindowsX86 { get; } + MemoryStream WarpPackerWindowsX86 { get; } - MemoryStream SetupWindowsX64 { get; } - MemoryStream WarpPackerWindowsX64 { get; } + MemoryStream SetupWindowsX64 { get; } + MemoryStream WarpPackerWindowsX64 { get; } - MemoryStream SetupLinuxX64 { get; } - MemoryStream WarpPackerLinuxX64 { get; } + MemoryStream SetupLinuxX64 { get; } + MemoryStream WarpPackerLinuxX64 { get; } - MemoryStream SetupLinuxArm64 { get; } - MemoryStream WarpPackerLinuxArm64 { get; } - } + MemoryStream SetupLinuxArm64 { get; } + MemoryStream WarpPackerLinuxArm64 { get; } +} - internal sealed class SnapxEmbeddedResources : EmbeddedResources, ISnapxEmbeddedResources - { - const string SetupWindowsX86Filename = "Setup-win-x86.zip"; - const string SetupWarpPackerWindowsX86Filename = "warp-packer-win-x86.exe"; +internal sealed class SnapxEmbeddedResources : EmbeddedResources, ISnapxEmbeddedResources +{ + const string SetupWindowsX86Filename = "Setup-win-x86.zip"; + const string SetupWarpPackerWindowsX86Filename = "warp-packer-win-x86.exe"; - const string SetupWindowsX64Filename = "Setup-win-x64.zip"; - const string SetupWarpPackerWindowsX64Filename = "warp-packer-win-x64.exe"; + const string SetupWindowsX64Filename = "Setup-win-x64.zip"; + const string SetupWarpPackerWindowsX64Filename = "warp-packer-win-x64.exe"; - const string SetupLinuxX64Filename = "Setup-linux-x64.zip"; - const string SetupWarpPackerLinuxX64Filename = "warp-packer-linux-x64.exe"; + const string SetupLinuxX64Filename = "Setup-linux-x64.zip"; + const string SetupWarpPackerLinuxX64Filename = "warp-packer-linux-x64.exe"; - const string SetupLinuxArm64Filename = "Setup-linux-arm64.zip"; - const string SetupWarpPackerLinuxArm64Filename = "warp-packer-linux-arm64.exe"; + const string SetupLinuxArm64Filename = "Setup-linux-arm64.zip"; + const string SetupWarpPackerLinuxArm64Filename = "warp-packer-linux-arm64.exe"; - readonly EmbeddedResource _setupWindowsX86; - readonly EmbeddedResource _warpPackerWindowsX86; + readonly EmbeddedResource _setupWindowsX86; + readonly EmbeddedResource _warpPackerWindowsX86; - readonly EmbeddedResource _setupWindowsX64; - readonly EmbeddedResource _warpPackerWindowsX64; + readonly EmbeddedResource _setupWindowsX64; + readonly EmbeddedResource _warpPackerWindowsX64; - readonly EmbeddedResource _setupLinuxX64; - readonly EmbeddedResource _warpPackerLinuxX64; + readonly EmbeddedResource _setupLinuxX64; + readonly EmbeddedResource _warpPackerLinuxX64; - readonly EmbeddedResource _setupLinuxArm64; - readonly EmbeddedResource _warpPackerLinuxArm64; + readonly EmbeddedResource _setupLinuxArm64; + readonly EmbeddedResource _warpPackerLinuxArm64; - public MemoryStream SetupWindowsX86 + public MemoryStream SetupWindowsX86 + { + get { - get + if (_setupWindowsX86 == null) { - if (_setupWindowsX86 == null) - { - throw new FileNotFoundException($"{SetupWindowsX86Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_setupWindowsX86.Stream.ToArray()); + throw new FileNotFoundException($"{SetupWindowsX86Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_setupWindowsX86.Stream.ToArray()); } + } - public MemoryStream WarpPackerWindowsX86 + public MemoryStream WarpPackerWindowsX86 + { + get { - get + if (_warpPackerWindowsX86 == null) { - if (_warpPackerWindowsX86 == null) - { - throw new FileNotFoundException($"{SetupWarpPackerWindowsX86Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_warpPackerWindowsX86.Stream.ToArray()); + throw new FileNotFoundException($"{SetupWarpPackerWindowsX86Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_warpPackerWindowsX86.Stream.ToArray()); } + } - public MemoryStream SetupWindowsX64 + public MemoryStream SetupWindowsX64 + { + get { - get + if (_setupWindowsX64 == null) { - if (_setupWindowsX64 == null) - { - throw new FileNotFoundException($"{SetupWindowsX64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_setupWindowsX64.Stream.ToArray()); + throw new FileNotFoundException($"{SetupWindowsX64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_setupWindowsX64.Stream.ToArray()); } + } - public MemoryStream WarpPackerWindowsX64 + public MemoryStream WarpPackerWindowsX64 + { + get { - get + if (_warpPackerWindowsX64 == null) { - if (_warpPackerWindowsX64 == null) - { - throw new FileNotFoundException($"{SetupWarpPackerWindowsX64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_warpPackerWindowsX64.Stream.ToArray()); + throw new FileNotFoundException($"{SetupWarpPackerWindowsX64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_warpPackerWindowsX64.Stream.ToArray()); } + } - public MemoryStream SetupLinuxX64 + public MemoryStream SetupLinuxX64 + { + get { - get + if (_setupLinuxX64 == null) { - if (_setupLinuxX64 == null) - { - throw new FileNotFoundException($"{SetupLinuxX64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_setupLinuxX64.Stream.ToArray()); + throw new FileNotFoundException($"{SetupLinuxX64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_setupLinuxX64.Stream.ToArray()); } + } - public MemoryStream WarpPackerLinuxX64 + public MemoryStream WarpPackerLinuxX64 + { + get { - get + if (_warpPackerLinuxX64 == null) { - if (_warpPackerLinuxX64 == null) - { - throw new FileNotFoundException($"{SetupWarpPackerLinuxX64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_warpPackerLinuxX64.Stream.ToArray()); + throw new FileNotFoundException($"{SetupWarpPackerLinuxX64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_warpPackerLinuxX64.Stream.ToArray()); } + } - public MemoryStream SetupLinuxArm64 + public MemoryStream SetupLinuxArm64 + { + get { - get + if (_setupLinuxArm64 == null) { - if (_setupLinuxArm64 == null) - { - throw new FileNotFoundException($"{SetupLinuxArm64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_setupLinuxArm64.Stream.ToArray()); + throw new FileNotFoundException($"{SetupLinuxArm64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_setupLinuxArm64.Stream.ToArray()); } + } - public MemoryStream WarpPackerLinuxArm64 + public MemoryStream WarpPackerLinuxArm64 + { + get { - get + if (_warpPackerLinuxArm64 == null) { - if (_warpPackerLinuxArm64 == null) - { - throw new FileNotFoundException($"{SetupWarpPackerLinuxArm64Filename} was not found in current assembly resources manifest"); - } - - return new MemoryStream(_warpPackerLinuxArm64.Stream.ToArray()); + throw new FileNotFoundException($"{SetupWarpPackerLinuxArm64Filename} was not found in current assembly resources manifest"); } + + return new MemoryStream(_warpPackerLinuxArm64.Stream.ToArray()); } + } - public SnapxEmbeddedResources() - { - AddFromTypeRoot(typeof(ResourcesTypeRoot)); + public SnapxEmbeddedResources() + { + AddFromTypeRoot(typeof(ResourcesTypeRoot)); - _setupWindowsX86 = Resources.SingleOrDefault(x => x.Filename == $"Setup.{SetupWindowsX86Filename}"); - _warpPackerWindowsX86 = Resources.SingleOrDefault(x => x.Filename == $"Tools.{SetupWarpPackerWindowsX86Filename}"); + _setupWindowsX86 = Resources.SingleOrDefault(x => x.Filename == $"Setup.{SetupWindowsX86Filename}"); + _warpPackerWindowsX86 = Resources.SingleOrDefault(x => x.Filename == $"Tools.{SetupWarpPackerWindowsX86Filename}"); - _setupWindowsX64 = Resources.SingleOrDefault(x => x.Filename == $"Setup.{SetupWindowsX64Filename}"); - _warpPackerWindowsX64 = Resources.SingleOrDefault(x => x.Filename == $"Tools.{SetupWarpPackerWindowsX64Filename}"); + _setupWindowsX64 = Resources.SingleOrDefault(x => x.Filename == $"Setup.{SetupWindowsX64Filename}"); + _warpPackerWindowsX64 = Resources.SingleOrDefault(x => x.Filename == $"Tools.{SetupWarpPackerWindowsX64Filename}"); - _setupLinuxX64 = Resources.SingleOrDefault(x => x.Filename == $"Setup.{SetupLinuxX64Filename}"); - _warpPackerLinuxX64 = Resources.SingleOrDefault(x => x.Filename == $"Tools.{SetupWarpPackerLinuxX64Filename}"); - - _setupLinuxArm64 = Resources.SingleOrDefault(x => x.Filename == $"Setup.{SetupLinuxArm64Filename}"); - _warpPackerLinuxArm64 = Resources.SingleOrDefault(x => x.Filename == $"Tools.{SetupWarpPackerLinuxArm64Filename}"); - } + _setupLinuxX64 = Resources.SingleOrDefault(x => x.Filename == $"Setup.{SetupLinuxX64Filename}"); + _warpPackerLinuxX64 = Resources.SingleOrDefault(x => x.Filename == $"Tools.{SetupWarpPackerLinuxX64Filename}"); + _setupLinuxArm64 = Resources.SingleOrDefault(x => x.Filename == $"Setup.{SetupLinuxArm64Filename}"); + _warpPackerLinuxArm64 = Resources.SingleOrDefault(x => x.Filename == $"Tools.{SetupWarpPackerLinuxArm64Filename}"); } -} + +} \ No newline at end of file diff --git a/src/Snapx/Options/BaseSubOptions.cs b/src/Snapx/Options/BaseSubOptions.cs index 977e380a..e24e3a94 100644 --- a/src/Snapx/Options/BaseSubOptions.cs +++ b/src/Snapx/Options/BaseSubOptions.cs @@ -1,7 +1,6 @@ -namespace snapx.Options +namespace snapx.Options; + +internal abstract class BaseSubOptions { - internal abstract class BaseSubOptions - { - } -} +} \ No newline at end of file diff --git a/src/Snapx/Options/DemoteOptions.cs b/src/Snapx/Options/DemoteOptions.cs index e80c0083..18231d5c 100644 --- a/src/Snapx/Options/DemoteOptions.cs +++ b/src/Snapx/Options/DemoteOptions.cs @@ -3,77 +3,80 @@ using CommandLine.Text; using JetBrains.Annotations; -namespace snapx.Options +namespace snapx.Options; + +[Verb("demote", HelpText = "Demote one or multiple releases")] +[UsedImplicitly] +internal class DemoteOptions : BaseSubOptions { - [Verb("demote", HelpText = "Demote one or multiple releases")] - [UsedImplicitly] - internal class DemoteOptions : BaseSubOptions - { - const int DefaultLockRetries = 3; + const int DefaultLockRetries = 3; - [Option('r', "rid", - HelpText = "The Runtime identifier (RID), e.g win-x64.")] - public string Rid { get; [UsedImplicitly] set; } + [Option('r', "rid", + HelpText = "The Runtime identifier (RID), e.g win-x64.")] + public string Rid { get; [UsedImplicitly] set; } - [Option("from-version", - HelpText = "Remove all releases newer than this version." - )] - public string FromVersion { get; set; } + [Option("from-version", + HelpText = "Remove all releases newer than this version." + )] + public string FromVersion { get; set; } - [Option("remove-all", - HelpText = "Remove all matching releases.")] - public bool RemoveAll { get; set; } + [Option("remove-all", + HelpText = "Remove all matching releases.")] + public bool RemoveAll { get; set; } - [Option("lock-retries", - Default = DefaultLockRetries, - HelpText = "The number of retries if a mutex fails to be acquired. Set -1 if you want to retry forever.")] - public int LockRetries { get; set; } = DefaultLockRetries; + [Option("lock-retries", + Default = DefaultLockRetries, + HelpText = "The number of retries if a mutex fails to be acquired. Set -1 if you want to retry forever.")] + public int LockRetries { get; set; } = DefaultLockRetries; - [Option("lock-token", - HelpText = "Override lock token.")] - public string LockToken { get; set; } + [Option("lock-token", + HelpText = "Override lock token.")] + public string LockToken { get; set; } - [Option("skip-await-update", - HelpText = "Skip waiting for the nuget feed update.")] - public bool SkipAwaitUpdate { get; set; } + [Option("skip-await-update", + HelpText = "Skip waiting for the nuget feed update.")] + public bool SkipAwaitUpdate { get; set; } - [Value(0, - HelpText = "The Application id.", - Required = true)] - public string Id { get; [UsedImplicitly] set; } + [Option("ntp-server", + HelpText = "Set network time provider server and port. Example: time.cloudflare.com:123")] + public string NetworkTimeProviderConnectionString { get; set; } - [Usage(ApplicationAlias = "snapx")] - [UsedImplicitly] - public static IEnumerable Examples + [Value(0, + HelpText = "The Application id.", + Required = true)] + public string Id { get; [UsedImplicitly] set; } + + [Usage(ApplicationAlias = "snapx")] + [UsedImplicitly] + public static IEnumerable Examples + { + get { - get + yield return new Example("Remove current release from all runtime identifiers", new DemoteOptions + { + Id = "demoapp" + }); + yield return new Example("Remove current release from win-x64", new DemoteOptions + { + Id = "demoapp", + Rid = "win-x64" + }); + yield return new Example("Remove all releases from win-x64", new DemoteOptions + { + Id = "demoapp", + Rid = "win-x64" + }); + yield return new Example("Remove all releases from all runtime identifiers", new DemoteOptions + { + Id = "demoapp", + RemoveAll = true + }); + yield return new Example("Remove all releases from all runtime identifiers greater than --from-version", new DemoteOptions { - yield return new Example("Remove current release from all runtime identifiers", new DemoteOptions - { - Id = "demoapp" - }); - yield return new Example("Remove current release from win-x64", new DemoteOptions - { - Id = "demoapp", - Rid = "win-x64" - }); - yield return new Example("Remove all releases from win-x64", new DemoteOptions - { - Id = "demoapp", - Rid = "win-x64" - }); - yield return new Example("Remove all releases from all runtime identifiers", new DemoteOptions - { - Id = "demoapp", - RemoveAll = true - }); - yield return new Example("Remove all releases from all runtime identifiers greater than --from-version", new DemoteOptions - { - Id = "demoapp", - FromVersion = "1.0.0", - RemoveAll = true - }); - } + Id = "demoapp", + FromVersion = "1.0.0", + RemoveAll = true + }); } } -} +} \ No newline at end of file diff --git a/src/Snapx/Options/ListOptions.cs b/src/Snapx/Options/ListOptions.cs index ecba31b4..7ffc42d1 100644 --- a/src/Snapx/Options/ListOptions.cs +++ b/src/Snapx/Options/ListOptions.cs @@ -3,29 +3,28 @@ using CommandLine.Text; using JetBrains.Annotations; -namespace snapx.Options +namespace snapx.Options; + +[Verb("list", true, HelpText = "Show current releases")] +[UsedImplicitly] +internal class ListOptions : BaseSubOptions { - [Verb("list", true, HelpText = "Show current releases")] + [Value(0, + HelpText = "The application id.", + Required = false)] + public string Id { get; [UsedImplicitly] set; } + + [Usage(ApplicationAlias = "snapx")] [UsedImplicitly] - internal class ListOptions : BaseSubOptions + public static IEnumerable Examples { - [Value(0, - HelpText = "The application id.", - Required = false)] - public string Id { get; [UsedImplicitly] set; } - - [Usage(ApplicationAlias = "snapx")] - [UsedImplicitly] - public static IEnumerable Examples + get { - get + yield return new Example("Show releases for all applications", new ListOptions()); + yield return new Example("Show releases for demoapp application", new ListOptions { - yield return new Example("Show releases for all applications", new ListOptions()); - yield return new Example("Show releases for demoapp application", new ListOptions - { - Id = "demoapp" - }); - } + Id = "demoapp" + }); } } -} +} \ No newline at end of file diff --git a/src/Snapx/Options/LockOptions.cs b/src/Snapx/Options/LockOptions.cs index 5e56c776..0aa08282 100644 --- a/src/Snapx/Options/LockOptions.cs +++ b/src/Snapx/Options/LockOptions.cs @@ -3,41 +3,40 @@ using CommandLine.Text; using JetBrains.Annotations; -namespace snapx.Options +namespace snapx.Options; + +[Verb("lock", HelpText = "Lock management")] +[UsedImplicitly] +public class LockOptions { - [Verb("lock", HelpText = "Lock management")] - [UsedImplicitly] - public class LockOptions - { - [Option("release", HelpText = "Force release lock specified in snapx.yml.", Required = true)] - public bool Release { get; [UsedImplicitly] set; } + [Option("release", HelpText = "Force release lock specified in snapx.yml.", Required = true)] + public bool Release { get; [UsedImplicitly] set; } - [Option("token", HelpText = "Input lock token to release.")] - public string Token { get; [UsedImplicitly] set; } + [Option("token", HelpText = "Input lock token to release.")] + public string Token { get; [UsedImplicitly] set; } - [Value(0, - HelpText = "The application id", - Required = true)] - public string Id { get; [UsedImplicitly] set; } + [Value(0, + HelpText = "The application id", + Required = true)] + public string Id { get; [UsedImplicitly] set; } - [Usage(ApplicationAlias = "snapx")] - [UsedImplicitly] - public static IEnumerable Examples + [Usage(ApplicationAlias = "snapx")] + [UsedImplicitly] + public static IEnumerable Examples + { + get { - get + yield return new Example("Release demoapp lock token", new LockOptions + { + Id = "demoapp", + Release = true + }); + yield return new Example("Release demoapp using custom input lock token", new LockOptions { - yield return new Example("Release demoapp lock token", new LockOptions - { - Id = "demoapp", - Release = true - }); - yield return new Example("Release demoapp using custom input lock token", new LockOptions - { - Id = "demoapp", - Release = true, - Token = "abc123" - }); - } + Id = "demoapp", + Release = true, + Token = "abc123" + }); } } -} +} \ No newline at end of file diff --git a/src/Snapx/Options/PackOptions.cs b/src/Snapx/Options/PackOptions.cs index c66d6863..2673aaa1 100644 --- a/src/Snapx/Options/PackOptions.cs +++ b/src/Snapx/Options/PackOptions.cs @@ -3,93 +3,96 @@ using CommandLine.Text; using JetBrains.Annotations; -namespace snapx.Options +namespace snapx.Options; + +[Verb("pack", HelpText = "Publish a new release")] +[UsedImplicitly] +internal class PackOptions : BaseSubOptions { - [Verb("pack", HelpText = "Publish a new release")] - [UsedImplicitly] - internal class PackOptions : BaseSubOptions - { - const int DefaultDbVersion = -1; - const int DefaultLockRetries = 3; + const int DefaultDbVersion = -1; + const int DefaultLockRetries = 3; + + [Option('r', "rid", + HelpText = "Runtime identifier (RID), e.g win-x64", + Required = true)] + public string Rid { get; [UsedImplicitly] set; } - [Option('r', "rid", - HelpText = "Runtime identifier (RID), e.g win-x64", - Required = true)] - public string Rid { get; [UsedImplicitly] set; } + [Option('v', "version", + HelpText = "The application version.", Required = true)] + public string Version { get; [UsedImplicitly] set; } - [Option('v', "version", - HelpText = "The application version.", Required = true)] - public string Version { get; [UsedImplicitly] set; } + [Option('y', "yes", + HelpText = "Yes (y) to all prompts")] + public bool YesToAllPrompts { get; [UsedImplicitly] set; } - [Option('y', "yes", - HelpText = "Yes (y) to all prompts")] - public bool YesToAllPrompts { get; [UsedImplicitly] set; } + [Option("gc", + HelpText = "Removes all delta releases and creates a new full release")] + public bool Gc { get; set; } - [Option("gc", - HelpText = "Removes all delta releases and creates a new full release")] - public bool Gc { get; set; } + [Option("db-version", + HelpText = "Manually specify next db version. Has to be greater than current version.", + Default = DefaultDbVersion)] + public int DbVersion { get; set; } = DefaultDbVersion; - [Option("db-version", - HelpText = "Manually specify next db version. Has to be greater than current version.", - Default = DefaultDbVersion)] - public int DbVersion { get; set; } = DefaultDbVersion; + [Option("lock-retries", + HelpText = + "The number of retries if a mutex fails to be acquired (default: 3). Specify -1 if you want to retry forever.", + Default = DefaultLockRetries)] + public int LockRetries { get; set; } = DefaultLockRetries; - [Option("lock-retries", - HelpText = - "The number of retries if a mutex fails to be acquired (default: 3). Specify -1 if you want to retry forever.", - Default = DefaultLockRetries)] - public int LockRetries { get; set; } = DefaultLockRetries; + [Option("lock-token", + HelpText = "Override default lock token")] + public string LockToken { get; set; } - [Option("lock-token", - HelpText = "Override default lock token")] - public string LockToken { get; set; } + [Option("skip-installers", + HelpText = "Skip building installers.")] + public bool SkipInstallers { get; set; } - [Option("skip-installers", - HelpText = "Skip building installers.")] - public bool SkipInstallers { get; set; } + [Option("skip-await-update", + HelpText = "Skip waiting for the nuget feed update.")] + public bool SkipAwaitUpdate { get; set; } - [Option("skip-await-update", - HelpText = "Skip waiting for the nuget feed update.")] - public bool SkipAwaitUpdate { get; set; } + [Option("release-notes", + HelpText = "Overwrite release notes defined in YML manifest.")] + public string ReleasesNotes { get; set; } - [Option("release-notes", - HelpText = "Overwrite release notes defined in YML manifest.")] - public string ReleasesNotes { get; set; } + [Option("ntp-server", + HelpText = "Set network time provider server and port. Example: time.cloudflare.com:123")] + public string NetworkTimeProviderConnectionString { get; set; } - [Value(0, - HelpText = "The application id.", - Required = true)] - public string Id { get; [UsedImplicitly] set; } + [Value(0, + HelpText = "The application id.", + Required = true)] + public string Id { get; [UsedImplicitly] set; } - [Usage(ApplicationAlias = "snapx")] - [UsedImplicitly] - public static IEnumerable Examples + [Usage(ApplicationAlias = "snapx")] + [UsedImplicitly] + public static IEnumerable Examples + { + get { - get + yield return new Example("Publish a new release for win-x64", new PackOptions + { + Id = "demoapp", + Rid = "win-x64", + Version = "1.0.0-prerelease", + ReleasesNotes = "My first release :)", + }); + yield return new Example("Publish a new release for win-x64 and remove all previous releases", new PackOptions + { + Id = "demoapp", + Rid = "win-x64", + Version = "1.0.0-prerelease", + Gc = true + }); + yield return new Example("Publish a new release for win-x64 (non-interactive front-end, e.g Github Actions)", new PackOptions { - yield return new Example("Publish a new release for win-x64", new PackOptions - { - Id = "demoapp", - Rid = "win-x64", - Version = "1.0.0-prerelease", - ReleasesNotes = "My first release :)", - }); - yield return new Example("Publish a new release for win-x64 and remove all previous releases", new PackOptions - { - Id = "demoapp", - Rid = "win-x64", - Version = "1.0.0-prerelease", - Gc = true - }); - yield return new Example("Publish a new release for win-x64 (non-interactive front-end, e.g Github Actions)", new PackOptions - { - Id = "demoapp", - Rid = "win-x64", - Version = "1.0.0-prerelease", - ReleasesNotes = "My first release :)", - YesToAllPrompts = true - }); - } + Id = "demoapp", + Rid = "win-x64", + Version = "1.0.0-prerelease", + ReleasesNotes = "My first release :)", + YesToAllPrompts = true + }); } } -} +} \ No newline at end of file diff --git a/src/Snapx/Options/PromoteOptions.cs b/src/Snapx/Options/PromoteOptions.cs index dd312fce..4cbb5c84 100644 --- a/src/Snapx/Options/PromoteOptions.cs +++ b/src/Snapx/Options/PromoteOptions.cs @@ -3,79 +3,81 @@ using CommandLine.Text; using JetBrains.Annotations; -namespace snapx.Options +namespace snapx.Options; + +[Verb("promote", HelpText = "Promote a snap to next release channel. E.g.: test -> staging -> production")] +[UsedImplicitly] +internal class PromoteOptions : BaseSubOptions { - [Verb("promote", HelpText = "Promote a snap to next release channel. E.g.: test -> staging -> production")] - [UsedImplicitly] - internal class PromoteOptions : BaseSubOptions - { - const int DefaultLockRetries = 3; + const int DefaultLockRetries = 3; - [Option('r', "rid", - HelpText = "The runtime identifier (RID), e.g win-x64", - Required = true)] - public string Rid { get; [UsedImplicitly] set; } + [Option('r', "rid", + HelpText = "The runtime identifier (RID), e.g win-x64", + Required = true)] + public string Rid { get; [UsedImplicitly] set; } - [Option('c', "channel", - HelpText = "The base channel to promote from.", - Required = true)] - public string Channel { get; [UsedImplicitly] set; } + [Option('c', "channel", + HelpText = "The base channel to promote from.", + Required = true)] + public string Channel { get; [UsedImplicitly] set; } - [Option("all", - HelpText = "Promote to all remaining channels.")] - public bool ToAllRemainingChannels { get; [UsedImplicitly] set; } + [Option("all", + HelpText = "Promote to all remaining channels.")] + public bool ToAllRemainingChannels { get; [UsedImplicitly] set; } - [Option("lock-retries", - Default = DefaultLockRetries, - HelpText = "The number of retries if a mutex fails to be acquired. Set -1 if you want to retry forever.")] - public int LockRetries { get; set; } = DefaultLockRetries; + [Option("lock-retries", + Default = DefaultLockRetries, + HelpText = "The number of retries if a mutex fails to be acquired. Set -1 if you want to retry forever.")] + public int LockRetries { get; set; } = DefaultLockRetries; - [Option("lock-token", - HelpText = "Override lock token.")] - public string LockToken { get; set; } + [Option("lock-token", + HelpText = "Override lock token.")] + public string LockToken { get; set; } - [Option('y', "yes", - HelpText = "Yes (y) to all prompts")] - public bool YesToAllPrompts { get; [UsedImplicitly] set; } + [Option('y', "yes", + HelpText = "Yes (y) to all prompts")] + public bool YesToAllPrompts { get; [UsedImplicitly] set; } - [Option("skip-installers", - HelpText = "Skip building installers.")] - public bool SkipInstallers { get; set; } + [Option("skip-installers", + HelpText = "Skip building installers.")] + public bool SkipInstallers { get; set; } - [Option("skip-await-update", - HelpText = "Skip waiting for the nuget feed update.")] - public bool SkipAwaitUpdate { get; set; } + [Option("skip-await-update", + HelpText = "Skip waiting for the nuget feed update.")] + public bool SkipAwaitUpdate { get; set; } + [Option("ntp-server", + HelpText = "Set network time provider server and port. Example: time.cloudflare.com:123")] + public string NetworkTimeProviderConnectionString { get; set; } - [Value(0, HelpText = "Application id", Required = true)] - public string Id { get; [UsedImplicitly] set; } + [Value(0, HelpText = "Application id", Required = true)] + public string Id { get; [UsedImplicitly] set; } - [Usage(ApplicationAlias = "snapx")] - [UsedImplicitly] - public static IEnumerable Examples + [Usage(ApplicationAlias = "snapx")] + [UsedImplicitly] + public static IEnumerable Examples + { + get { - get + yield return new Example("Promote current win-x64 release from test to staging", new PromoteOptions + { + Id = "demoapp", + Channel = "test", + Rid = "win-x64" + }); + yield return new Example("Promote current win-x64 release from staging to production", new PromoteOptions + { + Id = "demoapp", + Channel = "staging", + Rid = "win-x64" + }); + yield return new Example("Promote current win-x64 release from test to staging, production", new PromoteOptions { - yield return new Example("Promote current win-x64 release from test to staging", new PromoteOptions - { - Id = "demoapp", - Channel = "test", - Rid = "win-x64" - }); - yield return new Example("Promote current win-x64 release from staging to production", new PromoteOptions - { - Id = "demoapp", - Channel = "staging", - Rid = "win-x64" - }); - yield return new Example("Promote current win-x64 release from test to staging, production", new PromoteOptions - { - Id = "demoapp", - Channel = "test", - Rid = "win-x64", - ToAllRemainingChannels = true - }); - } + Id = "demoapp", + Channel = "test", + Rid = "win-x64", + ToAllRemainingChannels = true + }); } } -} +} \ No newline at end of file diff --git a/src/Snapx/Options/RcEditOptions.cs b/src/Snapx/Options/RcEditOptions.cs index 323d4d14..893500a1 100644 --- a/src/Snapx/Options/RcEditOptions.cs +++ b/src/Snapx/Options/RcEditOptions.cs @@ -3,42 +3,41 @@ using CommandLine.Text; using JetBrains.Annotations; -namespace snapx.Options +namespace snapx.Options; + +[Verb("rcedit", HelpText = "Manipulate resources for either Windows or Linux binaries")] +[UsedImplicitly] +internal class RcEditOptions : BaseSubOptions { - [Verb("rcedit", HelpText = "Manipulate resources for either Windows or Linux binaries")] - [UsedImplicitly] - internal class RcEditOptions : BaseSubOptions - { - [Option("gui-app", - HelpText = "Change Windows Subsystem from Console to WindowsGui")] - public bool ConvertSubSystemToWindowsGui { get; set; } + [Option("gui-app", + HelpText = "Change Windows Subsystem from Console to WindowsGui")] + public bool ConvertSubSystemToWindowsGui { get; set; } - [Option("icon", - HelpText = "Set icon for a windows executable")] - public string IconFilename { get; set; } + [Option("icon", + HelpText = "Set icon for a windows executable")] + public string IconFilename { get; set; } - [Value(0, - HelpText = "The input filename.", - Required = true)] - public string Filename { get; set; } + [Value(0, + HelpText = "The input filename.", + Required = true)] + public string Filename { get; set; } - [Usage(ApplicationAlias = "snapx")] - [UsedImplicitly] - public static IEnumerable Examples + [Usage(ApplicationAlias = "snapx")] + [UsedImplicitly] + public static IEnumerable Examples + { + get { - get + yield return new Example("Change Windows Subsystem from Console to WindowsGui", new RcEditOptions + { + Filename = "demoapp.exe", + ConvertSubSystemToWindowsGui = true + }); + yield return new Example("Set icon for windows executable", new RcEditOptions { - yield return new Example("Change Windows Subsystem from Console to WindowsGui", new RcEditOptions - { - Filename = "demoapp.exe", - ConvertSubSystemToWindowsGui = true - }); - yield return new Example("Set icon for windows executable", new RcEditOptions - { - Filename = "demoapp.exe", - IconFilename = "path/to/my/my.ico" - }); - } + Filename = "demoapp.exe", + IconFilename = "path/to/my/my.ico" + }); } } -} +} \ No newline at end of file diff --git a/src/Snapx/Options/RestoreOptions.cs b/src/Snapx/Options/RestoreOptions.cs index 28b5e161..0ea37747 100644 --- a/src/Snapx/Options/RestoreOptions.cs +++ b/src/Snapx/Options/RestoreOptions.cs @@ -4,66 +4,65 @@ using JetBrains.Annotations; using Snap.Core; -namespace snapx.Options +namespace snapx.Options; + +[Verb("restore", HelpText = "Restore missing or corrupt packages")] +[UsedImplicitly] +internal class RestoreOptions : BaseSubOptions { - [Verb("restore", HelpText = "Restore missing or corrupt packages")] - [UsedImplicitly] - internal class RestoreOptions : BaseSubOptions - { - const int DefaultRestoreConcurrency = 4; - const int DefaultDownloadConcurrency = 4; + const int DefaultRestoreConcurrency = 4; + const int DefaultDownloadConcurrency = 4; - [Option('r', "rid", - HelpText = "The runtime identifier (RID), e.g win-x64. If left unspecified all runtime identifiers will be restored.")] - public string Rid { get; [UsedImplicitly] set; } + [Option('r', "rid", + HelpText = "The runtime identifier (RID), e.g win-x64. If left unspecified all runtime identifiers will be restored.")] + public string Rid { get; [UsedImplicitly] set; } - [Option('i', "build-installers", - HelpText = "Build installers.")] - public bool BuildInstallers { get; set; } + [Option('i', "build-installers", + HelpText = "Build installers.")] + public bool BuildInstallers { get; set; } - [Option("rc|restore-concurrency", - HelpText = "The number of concurrent restores.", - Default = DefaultRestoreConcurrency)] - public int RestoreConcurrency { get; set; } = DefaultRestoreConcurrency; + [Option("rc|restore-concurrency", + HelpText = "The number of concurrent restores.", + Default = DefaultRestoreConcurrency)] + public int RestoreConcurrency { get; set; } = DefaultRestoreConcurrency; - [Option("dc|download-concurrency", - HelpText = "The number of concurrent downloads for missing packages.", - Default = DefaultDownloadConcurrency)] - public int DownloadConcurrency { get; set; } = DefaultDownloadConcurrency; + [Option("dc|download-concurrency", + HelpText = "The number of concurrent downloads for missing packages.", + Default = DefaultDownloadConcurrency)] + public int DownloadConcurrency { get; set; } = DefaultDownloadConcurrency; - [Value(0, - HelpText = "The application id to restore. Leave this value empty if you want to restore all applications.")] - public string Id { get; [UsedImplicitly] set; } + [Value(0, + HelpText = "The application id to restore. Leave this value empty if you want to restore all applications.")] + public string Id { get; [UsedImplicitly] set; } - public SnapPackageManagerRestoreType RestoreStrategyType { get; set; } = SnapPackageManagerRestoreType.Default; + public SnapPackageManagerRestoreType RestoreStrategyType { get; set; } = SnapPackageManagerRestoreType.Default; - [Usage(ApplicationAlias = "snapx")] - [UsedImplicitly] - public static IEnumerable Examples + [Usage(ApplicationAlias = "snapx")] + [UsedImplicitly] + public static IEnumerable Examples + { + get { - get + yield return new Example("Restore packages for all applications", new RestoreOptions()); + yield return new Example("Restore packages for all applications and build installers", new RestoreOptions + { + BuildInstallers = true + }); + yield return new Example("Restore packages for demoapp application", new RestoreOptions + { + Id = "demoapp" + }); + yield return new Example("Restore packages for demoapp win-x64 application", new RestoreOptions + { + Id = "demoapp", + Rid = "win-x64" + }); + yield return new Example("Restore packages for demoapp win-x64 application and build installers", new RestoreOptions { - yield return new Example("Restore packages for all applications", new RestoreOptions()); - yield return new Example("Restore packages for all applications and build installers", new RestoreOptions - { - BuildInstallers = true - }); - yield return new Example("Restore packages for demoapp application", new RestoreOptions - { - Id = "demoapp" - }); - yield return new Example("Restore packages for demoapp win-x64 application", new RestoreOptions - { - Id = "demoapp", - Rid = "win-x64" - }); - yield return new Example("Restore packages for demoapp win-x64 application and build installers", new RestoreOptions - { - Id = "demoapp", - Rid = "win-x64", - BuildInstallers = true - }); - } + Id = "demoapp", + Rid = "win-x64", + BuildInstallers = true + }); } } -} +} \ No newline at end of file diff --git a/src/Snapx/Options/Sha256Options.cs b/src/Snapx/Options/Sha256Options.cs index f8ed85f6..17b1cf5e 100644 --- a/src/Snapx/Options/Sha256Options.cs +++ b/src/Snapx/Options/Sha256Options.cs @@ -3,29 +3,28 @@ using CommandLine.Text; using JetBrains.Annotations; -namespace snapx.Options +namespace snapx.Options; + +[Verb("sha256", HelpText = "Calculate SHA-256 checksum for a given file")] +[UsedImplicitly] +internal class Sha256Options : BaseSubOptions { - [Verb("sha256", HelpText = "Calculate SHA-256 checksum for a given file")] + [Value(0, + HelpText = "Input file to be processed.", + MetaName = "input file", + Required = true)] + public string Filename { get; [UsedImplicitly] set; } + + [Usage(ApplicationAlias = "snapx")] [UsedImplicitly] - internal class Sha256Options : BaseSubOptions + public static IEnumerable Examples { - [Value(0, - HelpText = "Input file to be processed.", - MetaName = "input file", - Required = true)] - public string Filename { get; [UsedImplicitly] set; } - - [Usage(ApplicationAlias = "snapx")] - [UsedImplicitly] - public static IEnumerable Examples + get { - get + yield return new Example("Calculate SHA-256 checksum for a given file", new Sha256Options { - yield return new Example("Calculate SHA-256 checksum for a given file", new Sha256Options - { - Filename = "test.txt" - }); - } + Filename = "test.txt" + }); } } -} +} \ No newline at end of file diff --git a/src/Snapx/Program.CommandCrypto.cs b/src/Snapx/Program.CommandCrypto.cs index ffd13392..147a0533 100644 --- a/src/Snapx/Program.CommandCrypto.cs +++ b/src/Snapx/Program.CommandCrypto.cs @@ -5,34 +5,33 @@ using Snap.Core; using Snap.Logging; -namespace snapx -{ - internal partial class Program - { - static int CommandSha256([NotNull] Sha256Options sha256Options, [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ISnapCryptoProvider snapCryptoProvider, [NotNull] ILog logger) - { - if (sha256Options == null) throw new ArgumentNullException(nameof(sha256Options)); - if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); - if (snapCryptoProvider == null) throw new ArgumentNullException(nameof(snapCryptoProvider)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); +namespace snapx; + +internal partial class Program +{ + static int CommandSha256([NotNull] Sha256Options sha256Options, [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ISnapCryptoProvider snapCryptoProvider, [NotNull] ILog logger) + { + if (sha256Options == null) throw new ArgumentNullException(nameof(sha256Options)); + if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); + if (snapCryptoProvider == null) throw new ArgumentNullException(nameof(snapCryptoProvider)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (sha256Options.Filename == null || !snapFilesystem.FileExists(sha256Options.Filename)) - { - logger.Error($"File not found: {sha256Options.Filename}"); - return 1; - } + if (sha256Options.Filename == null || !snapFilesystem.FileExists(sha256Options.Filename)) + { + logger.Error($"File not found: {sha256Options.Filename}"); + return 1; + } - try - { - using var fileStream = new FileStream(sha256Options.Filename, FileMode.Open, FileAccess.Read); - logger.Info(snapCryptoProvider.Sha256(fileStream)); - return 0; - } - catch (Exception e) - { - logger.ErrorException($"Error computing SHA256-checksum for filename: {sha256Options.Filename}", e); - return 1; - } + try + { + using var fileStream = new FileStream(sha256Options.Filename, FileMode.Open, FileAccess.Read); + logger.Info(snapCryptoProvider.Sha256(fileStream)); + return 0; + } + catch (Exception e) + { + logger.ErrorException($"Error computing SHA256-checksum for filename: {sha256Options.Filename}", e); + return 1; } } -} +} \ No newline at end of file diff --git a/src/Snapx/Program.CommandDemote.cs b/src/Snapx/Program.CommandDemote.cs index 1a789dbe..7f9cbb0f 100644 --- a/src/Snapx/Program.CommandDemote.cs +++ b/src/Snapx/Program.CommandDemote.cs @@ -17,277 +17,276 @@ using Snap.Logging; using Snap.NuGet; -namespace snapx +namespace snapx; + +internal partial class Program { - internal partial class Program + static async Task CommandDemoteAsync([NotNull] DemoteOptions options, [NotNull] ISnapFilesystem filesystem, + [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INuGetPackageSources nuGetPackageSources, + [NotNull] INugetService nugetService, [NotNull] IDistributedMutexClient distributedMutexClient, + [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ISnapPack snapPack, + [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, [NotNull] ISnapExtractor snapExtractor, [NotNull] ISnapOs snapOs, + [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ICoreRunLib coreRunLib, + [NotNull] ILog logger, [NotNull] string workingDirectory, CancellationToken cancellationToken) { - static async Task CommandDemoteAsync([NotNull] DemoteOptions options, [NotNull] ISnapFilesystem filesystem, - [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INuGetPackageSources nuGetPackageSources, - [NotNull] INugetService nugetService, [NotNull] IDistributedMutexClient distributedMutexClient, - [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ISnapPack snapPack, - [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, [NotNull] ISnapExtractor snapExtractor, [NotNull] ISnapOs snapOs, - [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ICoreRunLib coreRunLib, - [NotNull] ILog logger, [NotNull] string workingDirectory, CancellationToken cancellationToken) + if (options == null) throw new ArgumentNullException(nameof(options)); + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); + if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); + if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); + if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); + if (snapNetworkTimeProvider == null) throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); + if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); + if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); + if (snapxEmbeddedResources == null) throw new ArgumentNullException(nameof(snapxEmbeddedResources)); + if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + if (options == null) throw new ArgumentNullException(nameof(options)); + if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); + + var stopwatch = new Stopwatch(); + stopwatch.Restart(); + + var anyRid = options.Rid == null; + var anyVersion = options.FromVersion == null; + SnapApp anyRidSnapApp = null; + SemanticVersion fromVersion = null; + var runtimeIdentifiers = new List(); + + if (!anyVersion) { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); - if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); - if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); - if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); - if (snapNetworkTimeProvider == null) throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); - if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); - if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); - if (snapxEmbeddedResources == null) throw new ArgumentNullException(nameof(snapxEmbeddedResources)); - if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - if (options == null) throw new ArgumentNullException(nameof(options)); - if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); - - var stopwatch = new Stopwatch(); - stopwatch.Restart(); - - var anyRid = options.Rid == null; - var anyVersion = options.FromVersion == null; - SnapApp anyRidSnapApp = null; - SemanticVersion fromVersion = null; - var runtimeIdentifiers = new List(); - - if (!anyVersion) + if (!SemanticVersion.TryParse(options.FromVersion, out fromVersion)) { - if (!SemanticVersion.TryParse(options.FromVersion, out fromVersion)) - { - Console.WriteLine($"Unable to parse from version: {options.FromVersion}"); - return 1; - } - - if (!options.RemoveAll) - { - Console.WriteLine("You must specify --remove-all if you want to demote releases newer than --from-version."); - return 1; - } + Console.WriteLine($"Unable to parse from version: {options.FromVersion}"); + return 1; } - var snapApps = BuildSnapAppsFromDirectory(filesystem, snapAppReader, workingDirectory); - if (!snapApps.Apps.Any()) + if (!options.RemoveAll) { + Console.WriteLine("You must specify --remove-all if you want to demote releases newer than --from-version."); return 1; } + } - foreach(var snapsApp in snapApps.Apps) - { - anyRidSnapApp = snapApps.BuildSnapApp(snapsApp.Id, snapsApp.Target.Rid, nuGetPackageSources, filesystem); - runtimeIdentifiers.AddRange(snapApps.GetRids(anyRidSnapApp)); - break; - } + var snapApps = BuildSnapAppsFromDirectory(filesystem, snapAppReader, workingDirectory); + if (!snapApps.Apps.Any()) + { + return 1; + } - if (anyRidSnapApp == null) - { - if (anyRid) - { - logger.Error($"Unable to find application with id: {options.Id}."); - return 1; - } + foreach(var snapsApp in snapApps.Apps) + { + anyRidSnapApp = snapApps.BuildSnapApp(snapsApp.Id, snapsApp.Target.Rid, nuGetPackageSources, filesystem); + runtimeIdentifiers.AddRange(snapApps.GetRids(anyRidSnapApp)); + break; + } - logger.Error($"Unable to find application with id: {options.Id}. Rid: {options.Rid}"); + if (anyRidSnapApp == null) + { + if (anyRid) + { + logger.Error($"Unable to find application with id: {options.Id}."); return 1; } - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Error($"Unable to find application with id: {options.Id}. Rid: {options.Rid}"); + return 1; + } - Console.WriteLine($"Demoting application with id: {anyRidSnapApp.Id}."); - Console.WriteLine($"Runtime identifiers (RID): {string.Join(", ", runtimeIdentifiers)}"); + logger.Info('-'.Repeat(TerminalBufferWidth)); - if (anyRid) - { - if (!logger.Prompt("y|yes", "You have not specified a rid, all releases in listed runtime identifiers will be removed. " + - "Do you want to continue? [y|n]") - ) - { - return 1; - } - } - - MaybeOverrideLockToken(snapApps, logger, options.Id, options.LockToken); + Console.WriteLine($"Demoting application with id: {anyRidSnapApp.Id}."); + Console.WriteLine($"Runtime identifiers (RID): {string.Join(", ", runtimeIdentifiers)}"); - if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) + if (anyRid) + { + if (!logger.Prompt("y|yes", "You have not specified a rid, all releases in listed runtime identifiers will be removed. " + + "Do you want to continue? [y|n]") + ) { - logger.Error("Please specify a token in your snapx.yml file. A random UUID is sufficient."); return 1; } + } + + MaybeOverrideLockToken(snapApps, logger, options.Id, options.LockToken); - logger.Info('-'.Repeat(TerminalBufferWidth)); + if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) + { + logger.Error("Please specify a token in your snapx.yml file. A random UUID is sufficient."); + return 1; + } - var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory); - filesystem.DirectoryCreateIfNotExists(packagesDirectory); + logger.Info('-'.Repeat(TerminalBufferWidth)); - await using var distributedMutex = WithDistributedMutex(distributedMutexClient, logger, - snapApps.BuildLockKey(anyRidSnapApp), cancellationToken); + var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory); + filesystem.DirectoryCreateIfNotExists(packagesDirectory); - var tryAcquireRetries = options.LockRetries == -1 ? int.MaxValue : options.LockRetries; - if (!await distributedMutex.TryAquireAsync(TimeSpan.FromSeconds(15), tryAcquireRetries)) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - return 1; - } + await using var distributedMutex = WithDistributedMutex(distributedMutexClient, logger, + snapApps.BuildLockKey(anyRidSnapApp), cancellationToken); + var tryAcquireRetries = options.LockRetries == -1 ? int.MaxValue : options.LockRetries; + if (!await distributedMutex.TryAquireAsync(TimeSpan.FromSeconds(15), tryAcquireRetries)) + { logger.Info('-'.Repeat(TerminalBufferWidth)); + return 1; + } - logger.Info("Downloading releases nupkg."); + logger.Info('-'.Repeat(TerminalBufferWidth)); - var (snapAppsReleases, _, currentReleasesMemoryStream) = await snapPackageManager - .GetSnapsReleasesAsync(anyRidSnapApp, logger, cancellationToken); - if (currentReleasesMemoryStream != null) - { - await currentReleasesMemoryStream.DisposeAsync(); - } + logger.Info("Downloading releases nupkg."); - if (snapAppsReleases == null) - { - return 1; - } + var (snapAppsReleases, _, currentReleasesMemoryStream) = await snapPackageManager + .GetSnapsReleasesAsync(anyRidSnapApp, logger, cancellationToken); + if (currentReleasesMemoryStream != null) + { + await currentReleasesMemoryStream.DisposeAsync(); + } - if (!snapAppsReleases.Any()) - { - logger.Error($"Releases nupkg does not contain application id: {anyRidSnapApp.Id}"); - return 1; - } + if (snapAppsReleases == null) + { + return 1; + } + + if (!snapAppsReleases.Any()) + { + logger.Error($"Releases nupkg does not contain application id: {anyRidSnapApp.Id}"); + return 1; + } - logger.Info($"Downloaded releases nupkg. Current version: {snapAppsReleases.Version}."); + logger.Info($"Downloaded releases nupkg. Current version: {snapAppsReleases.Version}."); - var snapAppReleases = options.RemoveAll ? - snapAppsReleases.GetReleases(anyRidSnapApp, x => + var snapAppReleases = options.RemoveAll ? + snapAppsReleases.GetReleases(anyRidSnapApp, x => + { + bool VersionFilter() { - bool VersionFilter() - { - return anyVersion || x.Version > fromVersion; - } + return anyVersion || x.Version > fromVersion; + } - bool RidFilter() - { - return anyRid || x.Target.Rid == anyRidSnapApp.Target.Rid; - } + bool RidFilter() + { + return anyRid || x.Target.Rid == anyRidSnapApp.Target.Rid; + } - return RidFilter() && VersionFilter(); - }) : - snapAppsReleases.GetMostRecentReleases(anyRidSnapApp, x => anyRid || x.Target.Rid == anyRidSnapApp.Target.Rid); + return RidFilter() && VersionFilter(); + }) : + snapAppsReleases.GetMostRecentReleases(anyRidSnapApp, x => anyRid || x.Target.Rid == anyRidSnapApp.Target.Rid); - if (!snapAppReleases.Any()) - { - logger.Error("Unable to find any releases that matches demotion criterias."); - return 1; - } + if (!snapAppReleases.Any()) + { + logger.Error("Unable to find any releases that matches demotion criterias."); + return 1; + } - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info('-'.Repeat(TerminalBufferWidth)); - var consoleTable = new ConsoleTable("Rid", "Channels", "Version", "Count") - { - Header = $"Demote summary overview. Total releases: {snapAppReleases.Count()}." - }; + var consoleTable = new ConsoleTable("Rid", "Channels", "Version", "Count") + { + Header = $"Demote summary overview. Total releases: {snapAppReleases.Count()}." + }; - foreach (var (rid, releases) in snapAppReleases.ToDictionaryByKey(x => x.Target.Rid)) + foreach (var (rid, releases) in snapAppReleases.ToDictionaryByKey(x => x.Target.Rid)) + { + var channels = releases.SelectMany(x => x.Channels).Distinct().ToList(); + var releaseVersion = options.RemoveAll ? "All versions" : releases.First().Version.ToString(); + consoleTable.AddRow(new object[] { - var channels = releases.SelectMany(x => x.Channels).Distinct().ToList(); - var releaseVersion = options.RemoveAll ? "All versions" : releases.First().Version.ToString(); - consoleTable.AddRow(new object[] - { - rid, - string.Join(", ", channels), - releaseVersion, - releases.Count.ToString() - }); - } + rid, + string.Join(", ", channels), + releaseVersion, + releases.Count.ToString() + }); + } - consoleTable.Write(logger); + consoleTable.Write(logger); - if (!logger.Prompt("y|yes", "Ready to demote releases. Do you want to continue? [y|n]")) - { - return 1; - } + if (!logger.Prompt("y|yes", "Ready to demote releases. Do you want to continue? [y|n]")) + { + return 1; + } - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Retrieving network time from: {snapNetworkTimeProvider}."); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Retrieving network time from: {snapNetworkTimeProvider}."); - var nowUtc = await SnapUtility.RetryAsync(async () => await snapNetworkTimeProvider.NowUtcAsync(), 3); - if (!nowUtc.HasValue) - { - logger.Error($"Unknown error retrieving network time from: {snapNetworkTimeProvider}"); - return 1; - } + var nowUtc = await SnapUtility.RetryAsync(async () => await snapNetworkTimeProvider.NowUtcAsync(), 3); + if (!nowUtc.HasValue) + { + logger.Error($"Unknown error retrieving network time from: {snapNetworkTimeProvider}"); + return 1; + } - var localTimeStr = TimeZoneInfo - .ConvertTimeFromUtc(nowUtc.Value, TimeZoneInfo.Local) - .ToString("F", CultureInfo.CurrentCulture); - logger.Info($"Successfully retrieved network time. Time is now: {localTimeStr}"); - logger.Info('-'.Repeat(TerminalBufferWidth)); + var localTimeStr = TimeZoneInfo + .ConvertTimeFromUtc(nowUtc.Value, TimeZoneInfo.Local) + .ToString("F", CultureInfo.CurrentCulture); + logger.Info($"Successfully retrieved network time. Time is now: {localTimeStr}"); + logger.Info('-'.Repeat(TerminalBufferWidth)); - var snapAppsReleasesDemotedCount = snapAppsReleases.Demote(snapAppReleases); - if (snapAppsReleasesDemotedCount != snapAppReleases.Count()) - { - logger.Error("Unknown error when removing demoted releases. " + - $"Expected to remove {snapAppReleases.Count()} but only {snapAppsReleasesDemotedCount} was removed."); - return 1; - } + var snapAppsReleasesDemotedCount = snapAppsReleases.Demote(snapAppReleases); + if (snapAppsReleasesDemotedCount != snapAppReleases.Count()) + { + logger.Error("Unknown error when removing demoted releases. " + + $"Expected to remove {snapAppReleases.Count()} but only {snapAppsReleasesDemotedCount} was removed."); + return 1; + } - logger.Info("Building releases nupkg. " + - $"Current database version: {snapAppsReleases.Version}. " + - $"Releases count: {snapAppsReleases.Count()}."); + logger.Info("Building releases nupkg. " + + $"Current database version: {snapAppsReleases.Version}. " + + $"Releases count: {snapAppsReleases.Count()}."); - var releasesMemoryStream = !snapAppsReleases.Any() ? - snapPack.BuildEmptyReleasesPackage(anyRidSnapApp, snapAppsReleases) : - snapPack.BuildReleasesPackage(anyRidSnapApp, snapAppsReleases); + var releasesMemoryStream = !snapAppsReleases.Any() ? + snapPack.BuildEmptyReleasesPackage(anyRidSnapApp, snapAppsReleases) : + snapPack.BuildReleasesPackage(anyRidSnapApp, snapAppsReleases); - var releasesNupkgAbsolutePath = snapOs.Filesystem.PathCombine(packagesDirectory, anyRidSnapApp.BuildNugetReleasesFilename()); - var releasesNupkgFilename = filesystem.PathGetFileName(releasesNupkgAbsolutePath); - await snapOs.Filesystem.FileWriteAsync(releasesMemoryStream, releasesNupkgAbsolutePath, cancellationToken); + var releasesNupkgAbsolutePath = snapOs.Filesystem.PathCombine(packagesDirectory, anyRidSnapApp.BuildNugetReleasesFilename()); + var releasesNupkgFilename = filesystem.PathGetFileName(releasesNupkgAbsolutePath); + await snapOs.Filesystem.FileWriteAsync(releasesMemoryStream, releasesNupkgAbsolutePath, cancellationToken); - logger.Info("Finished building releases nupkg.\n" + - $"Filename: {releasesNupkgFilename}.\n" + - $"Size: {releasesMemoryStream.Length.BytesAsHumanReadable()}.\n" + - $"New database version: {snapAppsReleases.Version}.\n" + - $"Pack id: {snapAppsReleases.PackId:N}."); + logger.Info("Finished building releases nupkg.\n" + + $"Filename: {releasesNupkgFilename}.\n" + + $"Size: {releasesMemoryStream.Length.BytesAsHumanReadable()}.\n" + + $"New database version: {snapAppsReleases.Version}.\n" + + $"Pack id: {snapAppsReleases.PackId:N}."); - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info('-'.Repeat(TerminalBufferWidth)); - var anySnapTargetDefaultChannel = anyRidSnapApp.Channels.First(); - var nugetSources = anyRidSnapApp.BuildNugetSources(filesystem.PathGetTempPath()); - var packageSource = nugetSources.Items.Single(x => x.Name == anySnapTargetDefaultChannel.PushFeed.Name); + var anySnapTargetDefaultChannel = anyRidSnapApp.Channels.First(); + var nugetSources = anyRidSnapApp.BuildNugetSources(filesystem.PathGetTempPath()); + var packageSource = nugetSources.Items.Single(x => x.Name == anySnapTargetDefaultChannel.PushFeed.Name); - await PushPackageAsync(nugetService, filesystem, distributedMutex, nuGetPackageSources, packageSource, - anySnapTargetDefaultChannel, releasesNupkgAbsolutePath, logger, cancellationToken); + await PushPackageAsync(nugetService, filesystem, distributedMutex, nuGetPackageSources, packageSource, + anySnapTargetDefaultChannel, releasesNupkgAbsolutePath, logger, cancellationToken); - var skipInitialBlock = packageSource.IsLocalOrUncPath(); + var skipInitialBlock = packageSource.IsLocalOrUncPath(); - await BlockUntilSnapUpdatedReleasesNupkgAsync(logger, snapPackageManager, snapAppsReleases, anyRidSnapApp, - anySnapTargetDefaultChannel, TimeSpan.FromSeconds(15), cancellationToken, skipInitialBlock, options.SkipAwaitUpdate); + await BlockUntilSnapUpdatedReleasesNupkgAsync(logger, snapPackageManager, snapAppsReleases, anyRidSnapApp, + anySnapTargetDefaultChannel, TimeSpan.FromSeconds(15), cancellationToken, skipInitialBlock, options.SkipAwaitUpdate); - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info('-'.Repeat(TerminalBufferWidth)); - await CommandRestoreAsync(new RestoreOptions - { - Id = anyRidSnapApp.Id, - Rid = anyRid ? null : anyRidSnapApp.Target.Rid, - BuildInstallers = true - }, filesystem, snapAppReader, snapAppWriter, - nuGetPackageSources, snapPackageManager, snapOs, snapxEmbeddedResources, - coreRunLib, snapPack, logger, workingDirectory, cancellationToken); + await CommandRestoreAsync(new RestoreOptions + { + Id = anyRidSnapApp.Id, + Rid = anyRid ? null : anyRidSnapApp.Target.Rid, + BuildInstallers = true + }, filesystem, snapAppReader, snapAppWriter, + nuGetPackageSources, snapPackageManager, snapOs, snapxEmbeddedResources, + coreRunLib, snapPack, logger, workingDirectory, cancellationToken); - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Fetching releases overview from feed: {anySnapTargetDefaultChannel.PushFeed.Name}"); + logger.Info($"Fetching releases overview from feed: {anySnapTargetDefaultChannel.PushFeed.Name}"); - await CommandListAsync(new ListOptions {Id = anyRidSnapApp.Id }, filesystem, snapAppReader, - nuGetPackageSources, nugetService, snapExtractor, snapPackageManager, logger, workingDirectory, cancellationToken); + await CommandListAsync(new ListOptions {Id = anyRidSnapApp.Id }, filesystem, snapAppReader, + nuGetPackageSources, nugetService, snapExtractor, snapPackageManager, logger, workingDirectory, cancellationToken); - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Demote completed in {stopwatch.Elapsed.TotalSeconds:F1}s."); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Demote completed in {stopwatch.Elapsed.TotalSeconds:F1}s."); - return 0; - } + return 0; } -} +} \ No newline at end of file diff --git a/src/Snapx/Program.CommandList.cs b/src/Snapx/Program.CommandList.cs index 4d12fb02..6c0f26f3 100644 --- a/src/Snapx/Program.CommandList.cs +++ b/src/Snapx/Program.CommandList.cs @@ -17,174 +17,173 @@ using Snap.NuGet; using ConsoleTable = snapx.Core.ConsoleTable; -namespace snapx -{ - internal partial class Program - { - static async Task CommandListAsync([NotNull] ListOptions options, [NotNull] ISnapFilesystem filesystem, - [NotNull] ISnapAppReader appReader, [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] INugetService nugetService, - [NotNull] ISnapExtractor snapExtractor, ISnapPackageManager packageManager, [NotNull] ILog logger, - [NotNull] string workingDirectory, CancellationToken cancellationToken) +namespace snapx; + +internal partial class Program +{ + static async Task CommandListAsync([NotNull] ListOptions options, [NotNull] ISnapFilesystem filesystem, + [NotNull] ISnapAppReader appReader, [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] INugetService nugetService, + [NotNull] ISnapExtractor snapExtractor, ISnapPackageManager packageManager, [NotNull] ILog logger, + [NotNull] string workingDirectory, CancellationToken cancellationToken) + { + if (options == null) throw new ArgumentNullException(nameof(options)); + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (appReader == null) throw new ArgumentNullException(nameof(appReader)); + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); + if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + + var (snapApps, snapAppses, errorBuildingSnapApps, _) = BuildSnapAppsesFromDirectory(filesystem, + appReader, nuGetPackageSources, workingDirectory, requirePushFeed: false); + + if (!snapApps.Apps.Any() || errorBuildingSnapApps) { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (appReader == null) throw new ArgumentNullException(nameof(appReader)); - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); - if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - - var (snapApps, snapAppses, errorBuildingSnapApps, _) = BuildSnapAppsesFromDirectory(filesystem, - appReader, nuGetPackageSources, workingDirectory, requirePushFeed: false); - - if (!snapApps.Apps.Any() || errorBuildingSnapApps) - { - return 1; - } + return 1; + } - if (options.Id != null) + if (options.Id != null) + { + if (!snapApps.Apps.Any(x => string.Equals(x.Id, options.Id, StringComparison.OrdinalIgnoreCase))) { - if (!snapApps.Apps.Any(x => string.Equals(x.Id, options.Id, StringComparison.OrdinalIgnoreCase))) - { - logger.Error($"Unable to find application with id: {options.Id}"); - return 1; - } + logger.Error($"Unable to find application with id: {options.Id}"); + return 1; } + } - var stopwatch = new Stopwatch(); - stopwatch.Restart(); + var stopwatch = new Stopwatch(); + stopwatch.Restart(); - var snapAppsesPackageSources = new List<(SnapApp snapApp, string fullOrDeltaPackageId, PackageSource packageSource)>(); + var snapAppsesPackageSources = new List<(SnapApp snapApp, string fullOrDeltaPackageId, PackageSource packageSource)>(); - var tables = new List<(SnapApp snapApp, ConsoleTable table)>(); + var tables = new List<(SnapApp snapApp, ConsoleTable table)>(); - foreach (var snapApp in snapAppses) + foreach (var snapApp in snapAppses) + { + if (options.Id != null + && !string.Equals(snapApp.Id, options.Id, StringComparison.OrdinalIgnoreCase)) { - if (options.Id != null - && !string.Equals(snapApp.Id, options.Id, StringComparison.OrdinalIgnoreCase)) - { - continue; - } + continue; + } - var packageSource = await packageManager.GetPackageSourceAsync(snapApp, logger); - snapAppsesPackageSources.Add((snapApp, snapApp.BuildNugetUpstreamId(), packageSource)); + var packageSource = await packageManager.GetPackageSourceAsync(snapApp, logger); + snapAppsesPackageSources.Add((snapApp, snapApp.BuildNugetUpstreamId(), packageSource)); - var table = tables.SingleOrDefault(x => x.snapApp.Id == snapApp.Id); - if (table != default) continue; + var table = tables.SingleOrDefault(x => x.snapApp.Id == snapApp.Id); + if (table != default) continue; - var tableColumns = new List {"Rid"}; - tableColumns.AddRange(snapApp.Channels.Select(x => $"Channel: {x.Name}")); - tableColumns.Add("Summary"); + var tableColumns = new List {"Rid"}; + tableColumns.AddRange(snapApp.Channels.Select(x => $"Channel: {x.Name}")); + tableColumns.Add("Summary"); - tables.Add((snapApp, new ConsoleTable(tableColumns) - { - Header = $"Id: {snapApp.Id}" - })); - } + tables.Add((snapApp, new ConsoleTable(tableColumns) + { + Header = $"Id: {snapApp.Id}" + })); + } - const int maxConcurrentMetadataTasks = 2; - const int retriesPerTask = 5; - const int delayInMilliseconds = 1200; + const int maxConcurrentMetadataTasks = 2; + const int retriesPerTask = 5; + const int delayInMilliseconds = 1200; - var downloadResults = new List<(bool downloadSuccess, DownloadResourceResult downloadResourceResult, string id)>(); + var downloadResults = new List<(bool downloadSuccess, DownloadResourceResult downloadResourceResult, string id)>(); - var snapDatabaseIds = string.Join(", ", snapAppsesPackageSources.DistinctBy(x => x.snapApp.Id).Select(x => x.snapApp.Id)); - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Downloading application databases: {snapDatabaseIds}."); + var snapDatabaseIds = string.Join(", ", snapAppsesPackageSources.DistinctBy(x => x.snapApp.Id).Select(x => x.snapApp.Id)); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Downloading application databases: {snapDatabaseIds}."); - await snapAppsesPackageSources.DistinctBy(x => x.snapApp.Id).ForEachAsync(async x => + await snapAppsesPackageSources.DistinctBy(x => x.snapApp.Id).ForEachAsync(async x => + { + try { - try - { - var downloadResult = await SnapUtility.RetryAsync(async () => + var downloadResult = await SnapUtility.RetryAsync(async () => await nugetService.DownloadLatestAsync(x.snapApp.BuildNugetReleasesUpstreamId(), x.packageSource, false, true, cancellationToken), - retriesPerTask, delayInMilliseconds); - downloadResults.Add((downloadResult.SuccessSafe(), downloadResult, x.snapApp.Id)); - } - catch (Exception) - { - downloadResults.Add((false, null, x.snapApp.Id)); - } - }, maxConcurrentMetadataTasks); + retriesPerTask, delayInMilliseconds); + downloadResults.Add((downloadResult.SuccessSafe(), downloadResult, x.snapApp.Id)); + } + catch (Exception) + { + downloadResults.Add((false, null, x.snapApp.Id)); + } + }, maxConcurrentMetadataTasks); - foreach (var (thisSnapApps, table) in tables) + foreach (var (thisSnapApps, table) in tables) + { + var (downloadSuccess, downloadResourceResult, _) = downloadResults.Single(x => x.id == thisSnapApps.Id); + + if (!downloadSuccess) { - var (downloadSuccess, downloadResourceResult, _) = downloadResults.Single(x => x.id == thisSnapApps.Id); + logger.Error($"Failed to download releases nupkg for application: {thisSnapApps.Id}. Status: {downloadResourceResult?.Status}."); + continue; + } - if (!downloadSuccess) + SnapAppsReleases snapAppsReleases; + var databaseSize = downloadResourceResult.PackageStream.Length; + using (var packageArchiveReader = new PackageArchiveReader(downloadResourceResult.PackageStream)) + { + snapAppsReleases = await snapExtractor.GetSnapAppsReleasesAsync(packageArchiveReader, appReader, cancellationToken); + if (snapAppsReleases == null) { - logger.Error($"Failed to download releases nupkg for application: {thisSnapApps.Id}. Status: {downloadResourceResult?.Status}."); + logger.Error($"Failed to unpack releases nupkg for application: {thisSnapApps.Id}"); continue; - } + } + } - SnapAppsReleases snapAppsReleases; - var databaseSize = downloadResourceResult.PackageStream.Length; - using (var packageArchiveReader = new PackageArchiveReader(downloadResourceResult.PackageStream)) + var lastUpdatedDateStr = TimeZoneInfo.ConvertTimeFromUtc(snapAppsReleases.LastWriteAccessUtc, + TimeZoneInfo.Local).ToString("F", CultureInfo.CurrentCulture); + + table.Header += $"\nVersion: {snapAppsReleases.Version}" + + $"\nTotal database size: {databaseSize.BytesAsHumanReadable()}" + + $"\nTotal release count: {snapAppsReleases.Count()}" + + $"\nPublish date: {lastUpdatedDateStr}" + + $"\nSnapx pack id: {snapAppsReleases.PackId:N}" + + $"\nSnapx version: {snapAppsReleases.PackVersion}"; + + foreach (var target in snapApps.Apps.Where(x => x.Id == thisSnapApps.Id).Select(x => x.Target)) + { + var rowValues = new List { - snapAppsReleases = await snapExtractor.GetSnapAppsReleasesAsync(packageArchiveReader, appReader, cancellationToken); - if (snapAppsReleases == null) - { - logger.Error($"Failed to unpack releases nupkg for application: {thisSnapApps.Id}"); - continue; - } - } + target.Rid + }; - var lastUpdatedDateStr = TimeZoneInfo.ConvertTimeFromUtc(snapAppsReleases.LastWriteAccessUtc, - TimeZoneInfo.Local).ToString("F", CultureInfo.CurrentCulture); + var targetSnapApp = snapAppses.Single(x => x.Id == thisSnapApps.Id && x.Target.Rid == target.Rid); + var targetSnapAppReleases = snapAppsReleases.GetReleases(targetSnapApp); - table.Header += $"\nVersion: {snapAppsReleases.Version}" + - $"\nTotal database size: {databaseSize.BytesAsHumanReadable()}" + - $"\nTotal release count: {snapAppsReleases.Count()}" + - $"\nPublish date: {lastUpdatedDateStr}" + - $"\nSnapx pack id: {snapAppsReleases.PackId:N}" + - $"\nSnapx version: {snapAppsReleases.PackVersion}"; - - foreach (var target in snapApps.Apps.Where(x => x.Id == thisSnapApps.Id).Select(x => x.Target)) - { - var rowValues = new List - { - target.Rid - }; - - var targetSnapApp = snapAppses.Single(x => x.Id == thisSnapApps.Id && x.Target.Rid == target.Rid); - var targetSnapAppReleases = snapAppsReleases.GetReleases(targetSnapApp); - - foreach (var channelName in thisSnapApps.Channels) - { - var mostRecentRelease = targetSnapAppReleases.GetMostRecentRelease(channelName); - - string rowValue = null; - if (mostRecentRelease == null) - { - rowValue += "-"; - } - else - { - var fullOrDeltaTxt = mostRecentRelease.IsFull ? "full" : "delta"; - var fullOrDeltaSize = mostRecentRelease.IsFull ? mostRecentRelease.FullFilesize : mostRecentRelease.DeltaFilesize; - rowValue = $"{mostRecentRelease.Version} ({fullOrDeltaTxt}) - {fullOrDeltaSize.BytesAsHumanReadable()}"; - } - - rowValues.Add(rowValue); + foreach (var channelName in thisSnapApps.Channels) + { + var mostRecentRelease = targetSnapAppReleases.GetMostRecentRelease(channelName); + string rowValue = null; + if (mostRecentRelease == null) + { + rowValue += "-"; + } + else + { + var fullOrDeltaTxt = mostRecentRelease.IsFull ? "full" : "delta"; + var fullOrDeltaSize = mostRecentRelease.IsFull ? mostRecentRelease.FullFilesize : mostRecentRelease.DeltaFilesize; + rowValue = $"{mostRecentRelease.Version} ({fullOrDeltaTxt}) - {fullOrDeltaSize.BytesAsHumanReadable()}"; } - var totalRidBytes = targetSnapAppReleases.Sum(x => x.IsFull ? x.FullFilesize : x.DeltaFilesize); - rowValues.Add($"{targetSnapAppReleases.Count()} releases - {totalRidBytes.BytesAsHumanReadable()}"); + rowValues.Add(rowValue); - table.AddRow(rowValues.ToArray()); } - logger.Info('-'.Repeat(TerminalBufferWidth)); - - table.Write(logger); + var totalRidBytes = targetSnapAppReleases.Sum(x => x.IsFull ? x.FullFilesize : x.DeltaFilesize); + rowValues.Add($"{targetSnapAppReleases.Count()} releases - {totalRidBytes.BytesAsHumanReadable()}"); + + table.AddRow(rowValues.ToArray()); } logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"List completed in {stopwatch.Elapsed.TotalSeconds:F1}s."); - - return 0; + + table.Write(logger); } + + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"List completed in {stopwatch.Elapsed.TotalSeconds:F1}s."); + + return 0; } -} +} \ No newline at end of file diff --git a/src/Snapx/Program.CommandLock.cs b/src/Snapx/Program.CommandLock.cs index 5b4e9897..1fb2a3a9 100644 --- a/src/Snapx/Program.CommandLock.cs +++ b/src/Snapx/Program.CommandLock.cs @@ -8,48 +8,47 @@ using snapx.Core; using snapx.Options; -namespace snapx +namespace snapx; + +internal partial class Program { - internal partial class Program + static async Task CommandLock([NotNull] LockOptions lockOptions, [NotNull] IDistributedMutexClient distributedMutexClient, + [NotNull] ISnapFilesystem filesystem, [NotNull] ISnapAppReader appReader, + [NotNull] ILog logger, [NotNull] string workingDirectory, CancellationToken cancellationToken) { - static async Task CommandLock([NotNull] LockOptions lockOptions, [NotNull] IDistributedMutexClient distributedMutexClient, - [NotNull] ISnapFilesystem filesystem, [NotNull] ISnapAppReader appReader, - [NotNull] ILog logger, [NotNull] string workingDirectory, CancellationToken cancellationToken) + if (lockOptions == null) throw new ArgumentNullException(nameof(lockOptions)); + if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + + var snapApps = BuildSnapAppsFromDirectory(filesystem, appReader, workingDirectory); + + var snapApp = snapApps.Apps.FirstOrDefault(x => string.Equals(x.Id, lockOptions.Id, StringComparison.OrdinalIgnoreCase)); + if (snapApp == null) + { + logger.Error($"Unable to find application with id: {lockOptions.Id}."); + return 1; + } + + MaybeOverrideLockToken(snapApps, logger, lockOptions.Id, lockOptions.Token, "--token"); + + if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) + { + logger.Error("Please specify a token in your snapx.yml file. A random UUID is sufficient."); + return 1; + } + + await using var distributedMutex = WithDistributedMutex(distributedMutexClient, + logger, snapApps.BuildLockKey(snapApp), cancellationToken, false); + + bool success; + if (!lockOptions.Release) { - if (lockOptions == null) throw new ArgumentNullException(nameof(lockOptions)); - if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - - var snapApps = BuildSnapAppsFromDirectory(filesystem, appReader, workingDirectory); - - var snapApp = snapApps.Apps.FirstOrDefault(x => string.Equals(x.Id, lockOptions.Id, StringComparison.OrdinalIgnoreCase)); - if (snapApp == null) - { - logger.Error($"Unable to find application with id: {lockOptions.Id}."); - return 1; - } - - MaybeOverrideLockToken(snapApps, logger, lockOptions.Id, lockOptions.Token, "--token"); - - if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) - { - logger.Error("Please specify a token in your snapx.yml file. A random UUID is sufficient."); - return 1; - } - - await using var distributedMutex = WithDistributedMutex(distributedMutexClient, - logger, snapApps.BuildLockKey(snapApp), cancellationToken, false); - - bool success; - if (!lockOptions.Release) - { - success = await distributedMutex.TryAquireAsync(); - return success ? 0 : 1; - } - - success = await DistributedMutex.TryForceReleaseAsync(distributedMutex.Name, distributedMutexClient, logger); + success = await distributedMutex.TryAquireAsync(); return success ? 0 : 1; } + + success = await DistributedMutex.TryForceReleaseAsync(distributedMutex.Name, distributedMutexClient, logger); + return success ? 0 : 1; } -} +} \ No newline at end of file diff --git a/src/Snapx/Program.CommandPack.cs b/src/Snapx/Program.CommandPack.cs index 42f255dd..22cdc4c9 100644 --- a/src/Snapx/Program.CommandPack.cs +++ b/src/Snapx/Program.CommandPack.cs @@ -17,408 +17,407 @@ using Snap.Logging; using Snap.NuGet; -namespace snapx +namespace snapx; + +internal partial class Program { - internal partial class Program + static async Task CommandPackAsync([NotNull] PackOptions packOptions, [NotNull] ISnapFilesystem filesystem, + [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INuGetPackageSources nuGetPackageSources, + [NotNull] ISnapPack snapPack, [NotNull] INugetService nugetService, [NotNull] ISnapOs snapOs, + [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ISnapExtractor snapExtractor, + [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ICoreRunLib coreRunLib, [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, + [NotNull] ILog logger, [NotNull] IDistributedMutexClient distributedMutexClient, [NotNull] string workingDirectory, CancellationToken cancellationToken) { - static async Task CommandPackAsync([NotNull] PackOptions packOptions, [NotNull] ISnapFilesystem filesystem, - [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INuGetPackageSources nuGetPackageSources, - [NotNull] ISnapPack snapPack, [NotNull] INugetService nugetService, [NotNull] ISnapOs snapOs, - [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ISnapExtractor snapExtractor, - [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ICoreRunLib coreRunLib, [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, - [NotNull] ILog logger, [NotNull] IDistributedMutexClient distributedMutexClient, [NotNull] string workingDirectory, CancellationToken cancellationToken) + if (packOptions == null) throw new ArgumentNullException(nameof(packOptions)); + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); + if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); + if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); + if (snapxEmbeddedResources == null) throw new ArgumentNullException(nameof(snapxEmbeddedResources)); + if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); + if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); + if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); + if (snapNetworkTimeProvider == null) throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + + var stopwatch = new Stopwatch(); + stopwatch.Restart(); + + var (snapApps, snapApp, error, snapsManifestAbsoluteFilename) = BuildSnapAppFromDirectory(filesystem, snapAppReader, + nuGetPackageSources, packOptions.Id, packOptions.Rid, workingDirectory); + if (snapApp == null) { - if (packOptions == null) throw new ArgumentNullException(nameof(packOptions)); - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); - if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); - if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); - if (snapxEmbeddedResources == null) throw new ArgumentNullException(nameof(snapxEmbeddedResources)); - if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); - if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); - if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); - if (snapNetworkTimeProvider == null) throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - - var stopwatch = new Stopwatch(); - stopwatch.Restart(); - - var (snapApps, snapApp, error, snapsManifestAbsoluteFilename) = BuildSnapAppFromDirectory(filesystem, snapAppReader, - nuGetPackageSources, packOptions.Id, packOptions.Rid, workingDirectory); - if (snapApp == null) + if (!error) { - if (!error) - { - logger.Error($"Snap with id {packOptions.Id} was not found in manifest: {snapsManifestAbsoluteFilename}"); - } - - return 1; + logger.Error($"Snap with id {packOptions.Id} was not found in manifest: {snapsManifestAbsoluteFilename}"); } - if (!SemanticVersion.TryParse(packOptions.Version, out var semanticVersion)) - { - logger.Error($"Unable to parse semantic version (v2): {packOptions.Version}"); - return 1; - } + return 1; + } - snapApp.Version = semanticVersion; + if (!SemanticVersion.TryParse(packOptions.Version, out var semanticVersion)) + { + logger.Error($"Unable to parse semantic version (v2): {packOptions.Version}"); + return 1; + } - if (packOptions.ReleasesNotes != null) - { - snapApp.ReleaseNotes = packOptions.ReleasesNotes; - } + snapApp.Version = semanticVersion; - var artifactsDirectory = BuildArtifactsDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); - var installersDirectory = BuildInstallersDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); - var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); + if (packOptions.ReleasesNotes != null) + { + snapApp.ReleaseNotes = packOptions.ReleasesNotes; + } + + var artifactsDirectory = BuildArtifactsDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); + var installersDirectory = BuildInstallersDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); + var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); - filesystem.DirectoryCreateIfNotExists(installersDirectory); - filesystem.DirectoryCreateIfNotExists(packagesDirectory); + filesystem.DirectoryCreateIfNotExists(installersDirectory); + filesystem.DirectoryCreateIfNotExists(packagesDirectory); - var snapAppChannel = snapApp.GetDefaultChannelOrThrow(); + var snapAppChannel = snapApp.GetDefaultChannelOrThrow(); - MaybeOverrideLockToken(snapApps, logger, packOptions.Id, packOptions.LockToken); + MaybeOverrideLockToken(snapApps, logger, packOptions.Id, packOptions.LockToken); - if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) - { - logger.Error("Please specify a token in your snapx.yml file. A random UUID is sufficient."); - return 1; - } + if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) + { + logger.Error("Please specify a token in your snapx.yml file. A random UUID is sufficient."); + return 1; + } - await using var distributedMutex = WithDistributedMutex(distributedMutexClient, logger, snapApps.BuildLockKey(snapApp), cancellationToken); + await using var distributedMutex = WithDistributedMutex(distributedMutexClient, logger, snapApps.BuildLockKey(snapApp), cancellationToken); - logger.Info($"Schema version: {snapApps.Schema}"); - logger.Info($"Packages directory: {packagesDirectory}"); - logger.Info($"Artifacts directory: {artifactsDirectory}"); - logger.Info($"Installers directory: {installersDirectory}"); - logger.Info($"Pack strategy: {snapApps.Generic.PackStrategy}"); - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Id: {snapApp.Id}"); - logger.Info($"Version: {snapApp.Version}"); - logger.Info($"Channel: {snapAppChannel.Name}"); - logger.Info($"Rid: {snapApp.Target.Rid}"); - logger.Info($"OS: {snapApp.Target.Os.ToString().ToLowerInvariant()}"); - var installersStr = !snapApp.Target.Installers.Any() ? "None" : string.Join(", ", snapApp.Target.Installers); - logger.Info($"Installers: {installersStr}"); - var shortcutsStr = !snapApp.Target.Shortcuts.Any() ? "None" : string.Join(", ", snapApp.Target.Shortcuts); - logger.Info($"Shortcuts: {shortcutsStr}"); - + logger.Info($"Schema version: {snapApps.Schema}"); + logger.Info($"Packages directory: {packagesDirectory}"); + logger.Info($"Artifacts directory: {artifactsDirectory}"); + logger.Info($"Installers directory: {installersDirectory}"); + logger.Info($"Pack strategy: {snapApps.Generic.PackStrategy}"); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Id: {snapApp.Id}"); + logger.Info($"Version: {snapApp.Version}"); + logger.Info($"Channel: {snapAppChannel.Name}"); + logger.Info($"Rid: {snapApp.Target.Rid}"); + logger.Info($"OS: {snapApp.Target.Os.ToString().ToLowerInvariant()}"); + var installersStr = !snapApp.Target.Installers.Any() ? "None" : string.Join(", ", snapApp.Target.Installers); + logger.Info($"Installers: {installersStr}"); + var shortcutsStr = !snapApp.Target.Shortcuts.Any() ? "None" : string.Join(", ", snapApp.Target.Shortcuts); + logger.Info($"Shortcuts: {shortcutsStr}"); + + logger.Info('-'.Repeat(TerminalBufferWidth)); + + var tryAcquireRetries = packOptions.LockRetries == -1 ? int.MaxValue : packOptions.LockRetries; + if (!await distributedMutex.TryAquireAsync(TimeSpan.FromSeconds(15), tryAcquireRetries)) + { logger.Info('-'.Repeat(TerminalBufferWidth)); + return 1; + } - var tryAcquireRetries = packOptions.LockRetries == -1 ? int.MaxValue : packOptions.LockRetries; - if (!await distributedMutex.TryAquireAsync(TimeSpan.FromSeconds(15), tryAcquireRetries)) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - return 1; - } + logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info('-'.Repeat(TerminalBufferWidth)); + var updateFeedPackageSource = await snapPackageManager.GetPackageSourceAsync(snapApp); - var updateFeedPackageSource = await snapPackageManager.GetPackageSourceAsync(snapApp); + logger.Info("Downloading releases nupkg."); - logger.Info("Downloading releases nupkg."); + var snapReleasesPackageDirectory = filesystem.DirectoryGetParent(packagesDirectory); + filesystem.DirectoryCreateIfNotExists(snapReleasesPackageDirectory); - var snapReleasesPackageDirectory = filesystem.DirectoryGetParent(packagesDirectory); - filesystem.DirectoryCreateIfNotExists(snapReleasesPackageDirectory); + var (snapAppsReleases, _, currentReleasesMemoryStream) = await snapPackageManager.GetSnapsReleasesAsync(snapApp, logger, cancellationToken); + if (currentReleasesMemoryStream != null) + { + await currentReleasesMemoryStream.DisposeAsync(); + } - var (snapAppsReleases, _, currentReleasesMemoryStream) = await snapPackageManager.GetSnapsReleasesAsync(snapApp, logger, cancellationToken); - if (currentReleasesMemoryStream != null) + if (snapAppsReleases == null) + { + if (!logger.Prompt("y|yes", "Unable to find a previous release in any of your NuGet package sources. " + + "Is this the first time you are publishing this application? " + + "NB! The package may not yet be visible to due to upstream caching. [y/n]", infoOnly: packOptions.YesToAllPrompts) + ) { - await currentReleasesMemoryStream.DisposeAsync(); + return 1; } - if (snapAppsReleases == null) - { - if (!logger.Prompt("y|yes", "Unable to find a previous release in any of your NuGet package sources. " + - "Is this the first time you are publishing this application? " + - "NB! The package may not yet be visible to due to upstream caching. [y/n]", infoOnly: packOptions.YesToAllPrompts) - ) - { - return 1; - } + snapAppsReleases = new SnapAppsReleases(); + } + else + { + logger.Info($"Downloaded releases nupkg. Current version: {snapAppsReleases.Version}."); - snapAppsReleases = new SnapAppsReleases(); - } - else + if (packOptions.Gc) { - logger.Info($"Downloaded releases nupkg. Current version: {snapAppsReleases.Version}."); - - if (packOptions.Gc) - { - var releasesRemoved = snapAppsReleases.Gc(snapApp); - logger.Info($"Garbage collected (removed) {releasesRemoved} releases."); - } - - var snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapAppChannel); - - var restoreSummary = await snapPackageManager.RestoreAsync(packagesDirectory, snapAppChannelReleases, - updateFeedPackageSource, SnapPackageManagerRestoreType.Pack, logger: logger, cancellationToken: cancellationToken); - if (!restoreSummary.Success) - { - return 1; - } + var releasesRemoved = snapAppsReleases.Gc(snapApp); + logger.Info($"Garbage collected (removed) {releasesRemoved} releases."); + } - if (snapAppChannelReleases.Any(x => x.Version >= snapApp.Version)) - { - logger.Error($"Version {snapApp.Version} is already published to feed: {updateFeedPackageSource.Name}."); - return 1; - } + var snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, snapAppChannel); + var restoreSummary = await snapPackageManager.RestoreAsync(packagesDirectory, snapAppChannelReleases, + updateFeedPackageSource, SnapPackageManagerRestoreType.Pack, logger: logger, cancellationToken: cancellationToken); + if (!restoreSummary.Success) + { + return 1; } - var snapPackageDetails = new SnapPackageDetails + if (snapAppChannelReleases.Any(x => x.Version >= snapApp.Version)) { - SnapApp = snapApp, - NuspecBaseDirectory = artifactsDirectory, - PackagesDirectory = packagesDirectory, - SnapAppsReleases = snapAppsReleases - }; - - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Building nupkg: {snapApp.Version}."); + logger.Error($"Version {snapApp.Version} is already published to feed: {updateFeedPackageSource.Name}."); + return 1; + } - var pushPackages = new List(); + } - var (fullNupkgMemoryStream, fullSnapApp, fullSnapRelease, deltaNupkgMemorystream, deltaSnapApp, deltaSnapRelease) = - await snapPack.BuildPackageAsync(snapPackageDetails, coreRunLib, cancellationToken); + var snapPackageDetails = new SnapPackageDetails + { + SnapApp = snapApp, + NuspecBaseDirectory = artifactsDirectory, + PackagesDirectory = packagesDirectory, + SnapAppsReleases = snapAppsReleases + }; - var fullNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, fullSnapRelease.Filename); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Building nupkg: {snapApp.Version}."); - await using (fullNupkgMemoryStream) - await using (deltaNupkgMemorystream) - { - logger.Info($"Writing full nupkg to disk: {fullSnapRelease.Filename}. File size: {fullSnapRelease.FullFilesize.BytesAsHumanReadable()}"); - await filesystem.FileWriteAsync(fullNupkgMemoryStream, fullNupkgAbsolutePath, default); + var pushPackages = new List(); - if (!fullSnapRelease.IsGenesis) - { - var deltaNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, deltaSnapRelease.Filename); - logger.Info( - $"Writing delta nupkg to disk: {deltaSnapRelease.Filename}. File size: {deltaSnapRelease.DeltaFilesize.BytesAsHumanReadable()}"); - await filesystem.FileWriteAsync(deltaNupkgMemorystream, deltaNupkgAbsolutePath, default); - } - } + var (fullNupkgMemoryStream, fullSnapApp, fullSnapRelease, deltaNupkgMemorystream, deltaSnapApp, deltaSnapRelease) = + await snapPack.BuildPackageAsync(snapPackageDetails, coreRunLib, cancellationToken); - var fullOrDeltaSnapApp = deltaSnapApp ?? fullSnapApp; - var fullOrDeltaNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, fullOrDeltaSnapApp.BuildNugetFilename()); - pushPackages.Add(fullOrDeltaNupkgAbsolutePath); + var fullNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, fullSnapRelease.Filename); - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Retrieving network time from: {snapNetworkTimeProvider}."); + await using (fullNupkgMemoryStream) + await using (deltaNupkgMemorystream) + { + logger.Info($"Writing full nupkg to disk: {fullSnapRelease.Filename}. File size: {fullSnapRelease.FullFilesize.BytesAsHumanReadable()}"); + await filesystem.FileWriteAsync(fullNupkgMemoryStream, fullNupkgAbsolutePath, default); - var nowUtc = await SnapUtility.RetryAsync(async () => await snapNetworkTimeProvider.NowUtcAsync(), 3); - if (!nowUtc.HasValue) + if (!fullSnapRelease.IsGenesis) { - logger.Error($"Unknown error retrieving network time from: {snapNetworkTimeProvider}"); - return 1; + var deltaNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, deltaSnapRelease.Filename); + logger.Info( + $"Writing delta nupkg to disk: {deltaSnapRelease.Filename}. File size: {deltaSnapRelease.DeltaFilesize.BytesAsHumanReadable()}"); + await filesystem.FileWriteAsync(deltaNupkgMemorystream, deltaNupkgAbsolutePath, default); } + } - var localTimeStr = TimeZoneInfo - .ConvertTimeFromUtc(nowUtc.Value, TimeZoneInfo.Local) - .ToString("F", CultureInfo.CurrentCulture); - logger.Info($"Successfully retrieved network time. Time is now: {localTimeStr}"); - logger.Info('-'.Repeat(TerminalBufferWidth)); + var fullOrDeltaSnapApp = deltaSnapApp ?? fullSnapApp; + var fullOrDeltaNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, fullOrDeltaSnapApp.BuildNugetFilename()); + pushPackages.Add(fullOrDeltaNupkgAbsolutePath); - fullSnapRelease.CreatedDateUtc = nowUtc.Value; - if (deltaSnapRelease != null) - { - deltaSnapRelease.CreatedDateUtc = nowUtc.Value; - } - snapAppsReleases.LastWriteAccessUtc = nowUtc.Value; + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Retrieving network time from: {snapNetworkTimeProvider}."); - int? forcedDbVersion = null; - if (packOptions.DbVersion > 0) - { - if (packOptions.DbVersion <= snapAppsReleases.DbVersion) - { - logger.Error($"Unable to force database version because version is less than or equal to current database version. \n" + - $"Forced version: {packOptions.DbVersion}.\n" + - $"Current database version: {snapAppsReleases.DbVersion}."); - return 1; - } + var nowUtc = await SnapUtility.RetryAsync(async () => await snapNetworkTimeProvider.NowUtcAsync(), 3); + if (!nowUtc.HasValue) + { + logger.Error($"Unknown error retrieving network time from: {snapNetworkTimeProvider}"); + return 1; + } - forcedDbVersion = packOptions.DbVersion; + var localTimeStr = TimeZoneInfo + .ConvertTimeFromUtc(nowUtc.Value, TimeZoneInfo.Local) + .ToString("F", CultureInfo.CurrentCulture); + logger.Info($"Successfully retrieved network time. Time is now: {localTimeStr}"); + logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Database version is forced because of '--db-version' option. Initial database version: {forcedDbVersion}."); - } else if (fullOrDeltaSnapApp.IsGenesis - && fullOrDeltaSnapApp.Version.Major > snapAppsReleases.Version.Major) + fullSnapRelease.CreatedDateUtc = nowUtc.Value; + if (deltaSnapRelease != null) + { + deltaSnapRelease.CreatedDateUtc = nowUtc.Value; + } + snapAppsReleases.LastWriteAccessUtc = nowUtc.Value; + + int? forcedDbVersion = null; + if (packOptions.DbVersion > 0) + { + if (packOptions.DbVersion <= snapAppsReleases.DbVersion) { - forcedDbVersion = fullOrDeltaSnapApp.Version.Major; - logger.Info($"Database version is forced because genesis nupkg detected. Initial database version: {forcedDbVersion}"); + logger.Error($"Unable to force database version because version is less than or equal to current database version. \n" + + $"Forced version: {packOptions.DbVersion}.\n" + + $"Current database version: {snapAppsReleases.DbVersion}."); + return 1; } - logger.Info($"Building releases nupkg. Current database version: {snapAppsReleases.Version}."); + forcedDbVersion = packOptions.DbVersion; + + logger.Info($"Database version is forced because of '--db-version' option. Initial database version: {forcedDbVersion}."); + } else if (fullOrDeltaSnapApp.IsGenesis + && fullOrDeltaSnapApp.Version.Major > snapAppsReleases.Version.Major) + { + forcedDbVersion = fullOrDeltaSnapApp.Version.Major; + logger.Info($"Database version is forced because genesis nupkg detected. Initial database version: {forcedDbVersion}"); + } - var releasesMemoryStream = snapPack.BuildReleasesPackage(fullOrDeltaSnapApp, snapAppsReleases, forcedDbVersion); - var releasesNupkgAbsolutePath = snapOs.Filesystem.PathCombine(snapReleasesPackageDirectory, fullOrDeltaSnapApp.BuildNugetReleasesFilename()); - var releasesNupkgFilename = filesystem.PathGetFileName(releasesNupkgAbsolutePath); - await snapOs.Filesystem.FileWriteAsync(releasesMemoryStream, releasesNupkgAbsolutePath, cancellationToken); - pushPackages.Add(releasesNupkgAbsolutePath); + logger.Info($"Building releases nupkg. Current database version: {snapAppsReleases.Version}."); - logger.Info("Finished building releases nupkg.\n" + - $"Filename: {releasesNupkgFilename}.\n" + - $"Size: {releasesMemoryStream.Length.BytesAsHumanReadable()}.\n" + - $"New database version: {snapAppsReleases.Version}.\n" + - $"Pack id: {snapAppsReleases.PackId:N}."); + var releasesMemoryStream = snapPack.BuildReleasesPackage(fullOrDeltaSnapApp, snapAppsReleases, forcedDbVersion); + var releasesNupkgAbsolutePath = snapOs.Filesystem.PathCombine(snapReleasesPackageDirectory, fullOrDeltaSnapApp.BuildNugetReleasesFilename()); + var releasesNupkgFilename = filesystem.PathGetFileName(releasesNupkgAbsolutePath); + await snapOs.Filesystem.FileWriteAsync(releasesMemoryStream, releasesNupkgAbsolutePath, cancellationToken); + pushPackages.Add(releasesNupkgAbsolutePath); - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info("Finished building releases nupkg.\n" + + $"Filename: {releasesNupkgFilename}.\n" + + $"Size: {releasesMemoryStream.Length.BytesAsHumanReadable()}.\n" + + $"New database version: {snapAppsReleases.Version}.\n" + + $"Pack id: {snapAppsReleases.PackId:N}."); + + logger.Info('-'.Repeat(TerminalBufferWidth)); - await using (releasesMemoryStream) + await using (releasesMemoryStream) + { + if (!packOptions.SkipInstallers && fullOrDeltaSnapApp.Target.Installers.Any()) { - if (!packOptions.SkipInstallers && fullOrDeltaSnapApp.Target.Installers.Any()) - { - var channels = fullOrDeltaSnapApp.IsGenesis ? fullOrDeltaSnapApp.Channels : new List { snapAppChannel }; + var channels = fullOrDeltaSnapApp.IsGenesis ? fullOrDeltaSnapApp.Channels : new List { snapAppChannel }; - foreach (var channel in channels) - { - var snapAppInstaller = new SnapApp(fullOrDeltaSnapApp); - snapAppInstaller.SetCurrentChannel(channel.Name); + foreach (var channel in channels) + { + var snapAppInstaller = new SnapApp(fullOrDeltaSnapApp); + snapAppInstaller.SetCurrentChannel(channel.Name); - if (fullOrDeltaSnapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Offline))) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); + if (fullOrDeltaSnapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Offline))) + { + logger.Info('-'.Repeat(TerminalBufferWidth)); - var (installerOfflineSuccess, canContinueIfError, installerOfflineExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, - installersDirectory, fullNupkgAbsolutePath, releasesNupkgAbsolutePath, - true, cancellationToken); + var (installerOfflineSuccess, canContinueIfError, installerOfflineExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, + installersDirectory, fullNupkgAbsolutePath, releasesNupkgAbsolutePath, + true, cancellationToken); - if (!installerOfflineSuccess) - { - if (!canContinueIfError - || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", - infoOnly: packOptions.YesToAllPrompts)) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Error("Unknown error building offline installer."); - return 1; - } - } - else + if (!installerOfflineSuccess) + { + if (!canContinueIfError + || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", + infoOnly: packOptions.YesToAllPrompts)) { - var installerOfflineExeStat = snapOs.Filesystem.FileStat(installerOfflineExeAbsolutePath); - logger.Info($"Successfully built offline installer. File size: {installerOfflineExeStat.Length.BytesAsHumanReadable()}."); - } - + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Error("Unknown error building offline installer."); + return 1; + } } - - if (fullOrDeltaSnapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Web))) + else { - logger.Info('-'.Repeat(TerminalBufferWidth)); + var installerOfflineExeStat = snapOs.Filesystem.FileStat(installerOfflineExeAbsolutePath); + logger.Info($"Successfully built offline installer. File size: {installerOfflineExeStat.Length.BytesAsHumanReadable()}."); + } + + } + + if (fullOrDeltaSnapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Web))) + { + logger.Info('-'.Repeat(TerminalBufferWidth)); - var (installerWebSuccess, canContinueIfError, installerWebExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, - installersDirectory, null, releasesNupkgAbsolutePath, - false, cancellationToken); + var (installerWebSuccess, canContinueIfError, installerWebExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, + installersDirectory, null, releasesNupkgAbsolutePath, + false, cancellationToken); - if (!installerWebSuccess) - { - if (!canContinueIfError - || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", - infoOnly: packOptions.YesToAllPrompts)) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Error("Unknown error building offline installer."); - return 1; - } - } - else + if (!installerWebSuccess) + { + if (!canContinueIfError + || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", + infoOnly: packOptions.YesToAllPrompts)) { - var installerWebExeStat = snapOs.Filesystem.FileStat(installerWebExeAbsolutePath); - logger.Info($"Successfully built web installer. File size: {installerWebExeStat.Length.BytesAsHumanReadable()}."); - } + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Error("Unknown error building offline installer."); + return 1; + } + } + else + { + var installerWebExeStat = snapOs.Filesystem.FileStat(installerWebExeAbsolutePath); + logger.Info($"Successfully built web installer. File size: {installerWebExeStat.Length.BytesAsHumanReadable()}."); } } } } - - if (snapApps.Generic.PackStrategy == SnapAppsPackStrategy.push) - { - await PushPackagesAsync(packOptions, logger, filesystem, nugetService, - snapPackageManager, distributedMutex, snapAppsReleases, fullOrDeltaSnapApp, snapAppChannel, pushPackages, cancellationToken); - } - - logger.Info($"Fetching releases overview from feed {updateFeedPackageSource.Name}."); - - await CommandListAsync(new ListOptions {Id = fullOrDeltaSnapApp.Id}, filesystem, snapAppReader, - nuGetPackageSources, nugetService, snapExtractor, snapPackageManager, logger, workingDirectory, cancellationToken); - - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Pack completed in {stopwatch.Elapsed.TotalSeconds:F1}s."); - - return 0; } - static async Task PushPackagesAsync([NotNull] PackOptions packOptions, [NotNull] ILog logger, [NotNull] ISnapFilesystem filesystem, - [NotNull] INugetService nugetService, [NotNull] ISnapPackageManager snapPackageManager, [NotNull] IDistributedMutex distributedMutex, [NotNull] SnapAppsReleases snapAppsReleases, - [NotNull] SnapApp snapApp, [NotNull] SnapChannel snapChannel, - [NotNull] List packages, CancellationToken cancellationToken) + if (snapApps.Generic.PackStrategy == SnapAppsPackStrategy.push) { - if (packOptions == null) throw new ArgumentNullException(nameof(packOptions)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); - if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); - if (distributedMutex == null) throw new ArgumentNullException(nameof(distributedMutex)); - if (snapAppsReleases == null) throw new ArgumentNullException(nameof(snapAppsReleases)); - if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); - if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); - if (packages == null) throw new ArgumentNullException(nameof(packages)); - if (packages.Count == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(packages)); - - logger.Info('-'.Repeat(TerminalBufferWidth)); + await PushPackagesAsync(packOptions, logger, filesystem, nugetService, + snapPackageManager, distributedMutex, snapAppsReleases, fullOrDeltaSnapApp, snapAppChannel, pushPackages, cancellationToken); + } - var pushDegreeOfParallelism = Math.Min(Environment.ProcessorCount, packages.Count); + logger.Info($"Fetching releases overview from feed {updateFeedPackageSource.Name}."); - var nugetSources = snapApp.BuildNugetSources(filesystem.PathGetTempPath()); - var pushFeedPackageSource = nugetSources.Items.Single(x => x.Name == snapChannel.PushFeed.Name); + await CommandListAsync(new ListOptions {Id = fullOrDeltaSnapApp.Id}, filesystem, snapAppReader, + nuGetPackageSources, nugetService, snapExtractor, snapPackageManager, logger, workingDirectory, cancellationToken); - if (pushFeedPackageSource.IsLocalOrUncPath()) - { - filesystem.DirectoryCreateIfNotExists(pushFeedPackageSource.SourceUri.AbsolutePath); - } + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Pack completed in {stopwatch.Elapsed.TotalSeconds:F1}s."); - if (snapChannel.UpdateFeed.HasCredentials()) - { - if (!logger.Prompt("y|yes", "Update feed contains credentials. Do you want to continue? [y|n]", infoOnly: packOptions.YesToAllPrompts)) - { - logger.Error("Publish aborted."); - return; - } - } - - logger.Info("Ready to publish application!"); + return 0; + } - logger.Info($"Id: {snapApp.Id}"); - logger.Info($"Rid: {snapApp.Target.Rid}"); - logger.Info($"Channel: {snapChannel.Name}"); - logger.Info($"Version: {snapApp.Version}"); - logger.Info($"Feed name: {snapChannel.PushFeed.Name}"); + static async Task PushPackagesAsync([NotNull] PackOptions packOptions, [NotNull] ILog logger, [NotNull] ISnapFilesystem filesystem, + [NotNull] INugetService nugetService, [NotNull] ISnapPackageManager snapPackageManager, [NotNull] IDistributedMutex distributedMutex, [NotNull] SnapAppsReleases snapAppsReleases, + [NotNull] SnapApp snapApp, [NotNull] SnapChannel snapChannel, + [NotNull] List packages, CancellationToken cancellationToken) + { + if (packOptions == null) throw new ArgumentNullException(nameof(packOptions)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); + if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); + if (distributedMutex == null) throw new ArgumentNullException(nameof(distributedMutex)); + if (snapAppsReleases == null) throw new ArgumentNullException(nameof(snapAppsReleases)); + if (snapApp == null) throw new ArgumentNullException(nameof(snapApp)); + if (snapChannel == null) throw new ArgumentNullException(nameof(snapChannel)); + if (packages == null) throw new ArgumentNullException(nameof(packages)); + if (packages.Count == 0) throw new ArgumentException("Value cannot be an empty collection.", nameof(packages)); + + logger.Info('-'.Repeat(TerminalBufferWidth)); + + var pushDegreeOfParallelism = Math.Min(Environment.ProcessorCount, packages.Count); + + var nugetSources = snapApp.BuildNugetSources(filesystem.PathGetTempPath()); + var pushFeedPackageSource = nugetSources.Items.Single(x => x.Name == snapChannel.PushFeed.Name); + + if (pushFeedPackageSource.IsLocalOrUncPath()) + { + filesystem.DirectoryCreateIfNotExists(pushFeedPackageSource.SourceUri.AbsolutePath); + } - if (!logger.Prompt("y|yes", "Are you ready to push release upstream? [y|n]", infoOnly: packOptions.YesToAllPrompts)) + if (snapChannel.UpdateFeed.HasCredentials()) + { + if (!logger.Prompt("y|yes", "Update feed contains credentials. Do you want to continue? [y|n]", infoOnly: packOptions.YesToAllPrompts)) { logger.Error("Publish aborted."); return; } + } + + logger.Info("Ready to publish application!"); + + logger.Info($"Id: {snapApp.Id}"); + logger.Info($"Rid: {snapApp.Target.Rid}"); + logger.Info($"Channel: {snapChannel.Name}"); + logger.Info($"Version: {snapApp.Version}"); + logger.Info($"Feed name: {snapChannel.PushFeed.Name}"); - var stopwatch = new Stopwatch(); - stopwatch.Restart(); + if (!logger.Prompt("y|yes", "Are you ready to push release upstream? [y|n]", infoOnly: packOptions.YesToAllPrompts)) + { + logger.Error("Publish aborted."); + return; + } - logger.Info($"Pushing packages to default channel: {snapChannel.Name}. Feed: {snapChannel.PushFeed.Name}."); + var stopwatch = new Stopwatch(); + stopwatch.Restart(); - await packages.ForEachAsync(async packageAbsolutePath => - await PushPackageAsync(nugetService, filesystem, distributedMutex, - nugetSources, pushFeedPackageSource, snapChannel, packageAbsolutePath, logger, cancellationToken), pushDegreeOfParallelism); + logger.Info($"Pushing packages to default channel: {snapChannel.Name}. Feed: {snapChannel.PushFeed.Name}."); - logger.Info($"Successfully pushed {packages.Count} packages in {stopwatch.Elapsed.TotalSeconds:F1}s."); + await packages.ForEachAsync(async packageAbsolutePath => + await PushPackageAsync(nugetService, filesystem, distributedMutex, + nugetSources, pushFeedPackageSource, snapChannel, packageAbsolutePath, logger, cancellationToken), pushDegreeOfParallelism); - var skipInitialBlock = pushFeedPackageSource.IsLocalOrUncPath(); + logger.Info($"Successfully pushed {packages.Count} packages in {stopwatch.Elapsed.TotalSeconds:F1}s."); - await BlockUntilSnapUpdatedReleasesNupkgAsync(logger, snapPackageManager, snapAppsReleases, - snapApp, snapChannel, TimeSpan.FromSeconds(15), cancellationToken, skipInitialBlock,packOptions.SkipAwaitUpdate ); - } + var skipInitialBlock = pushFeedPackageSource.IsLocalOrUncPath(); + + await BlockUntilSnapUpdatedReleasesNupkgAsync(logger, snapPackageManager, snapAppsReleases, + snapApp, snapChannel, TimeSpan.FromSeconds(15), cancellationToken, skipInitialBlock,packOptions.SkipAwaitUpdate ); } -} +} \ No newline at end of file diff --git a/src/Snapx/Program.CommandPromote.cs b/src/Snapx/Program.CommandPromote.cs index 50719741..682c8c5b 100644 --- a/src/Snapx/Program.CommandPromote.cs +++ b/src/Snapx/Program.CommandPromote.cs @@ -16,301 +16,300 @@ using Snap.Logging; using Snap.NuGet; -namespace snapx +namespace snapx; + +internal partial class Program { - internal partial class Program + static async Task CommandPromoteAsync([NotNull] PromoteOptions options, [NotNull] ISnapFilesystem filesystem, + [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INuGetPackageSources nuGetPackageSources, + [NotNull] INugetService nugetService, [NotNull] IDistributedMutexClient distributedMutexClient, + [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ISnapPack snapPack, [NotNull] ISnapOsSpecialFolders specialFolders, + [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, [NotNull] ISnapExtractor snapExtractor, [NotNull] ISnapOs snapOs, + [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ICoreRunLib coreRunLib, + [NotNull] ILog logger, [NotNull] string workingDirectory, CancellationToken cancellationToken) { - static async Task CommandPromoteAsync([NotNull] PromoteOptions options, [NotNull] ISnapFilesystem filesystem, - [NotNull] ISnapAppReader snapAppReader, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] INuGetPackageSources nuGetPackageSources, - [NotNull] INugetService nugetService, [NotNull] IDistributedMutexClient distributedMutexClient, - [NotNull] ISnapPackageManager snapPackageManager, [NotNull] ISnapPack snapPack, [NotNull] ISnapOsSpecialFolders specialFolders, - [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, [NotNull] ISnapExtractor snapExtractor, [NotNull] ISnapOs snapOs, - [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ICoreRunLib coreRunLib, - [NotNull] ILog logger, [NotNull] string workingDirectory, CancellationToken cancellationToken) + if (options == null) throw new ArgumentNullException(nameof(options)); + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); + if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); + if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); + if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); + if (specialFolders == null) throw new ArgumentNullException(nameof(specialFolders)); + if (snapNetworkTimeProvider == null) throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); + if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); + if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); + if (snapxEmbeddedResources == null) throw new ArgumentNullException(nameof(snapxEmbeddedResources)); + if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + if (options == null) throw new ArgumentNullException(nameof(options)); + if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); + + var stopWatch = new Stopwatch(); + stopWatch.Restart(); + + options.Channel = string.IsNullOrWhiteSpace(options.Channel) ? null : options.Channel; + + if (options.Channel != null && !options.Channel.IsValidChannelName()) { - if (options == null) throw new ArgumentNullException(nameof(options)); - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); - if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); - if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); - if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); - if (specialFolders == null) throw new ArgumentNullException(nameof(specialFolders)); - if (snapNetworkTimeProvider == null) throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); - if (snapExtractor == null) throw new ArgumentNullException(nameof(snapExtractor)); - if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); - if (snapxEmbeddedResources == null) throw new ArgumentNullException(nameof(snapxEmbeddedResources)); - if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - if (options == null) throw new ArgumentNullException(nameof(options)); - if (nugetService == null) throw new ArgumentNullException(nameof(nugetService)); - - var stopWatch = new Stopwatch(); - stopWatch.Restart(); - - options.Channel = string.IsNullOrWhiteSpace(options.Channel) ? null : options.Channel; - - if (options.Channel != null && !options.Channel.IsValidChannelName()) - { - logger.Error($"Invalid channel name: {options.Channel}"); - return 1; - } + logger.Error($"Invalid channel name: {options.Channel}"); + return 1; + } - var (snapApps, snapApp, error, _) = BuildSnapAppFromDirectory(filesystem, snapAppReader, - nuGetPackageSources, options.Id, options.Rid, workingDirectory); - if (snapApp == null) + var (snapApps, snapApp, error, _) = BuildSnapAppFromDirectory(filesystem, snapAppReader, + nuGetPackageSources, options.Id, options.Rid, workingDirectory); + if (snapApp == null) + { + if (!error) { - if (!error) - { - logger.Error($"Unable to find snap with id: {options.Id}. Rid: {options.Rid}."); - } - - return 1; + logger.Error($"Unable to find snap with id: {options.Id}. Rid: {options.Rid}."); } + + return 1; + } - var installersDirectory = BuildInstallersDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); - var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); + var installersDirectory = BuildInstallersDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); + var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); - var promoteBaseChannel = snapApp.Channels.SingleOrDefault(x => string.Equals(x.Name, options.Channel, StringComparison.OrdinalIgnoreCase)); - if (promoteBaseChannel == null) - { - logger.Error($"Unable to find channel: {options.Channel}."); - return 1; - } + var promoteBaseChannel = snapApp.Channels.SingleOrDefault(x => string.Equals(x.Name, options.Channel, StringComparison.OrdinalIgnoreCase)); + if (promoteBaseChannel == null) + { + logger.Error($"Unable to find channel: {options.Channel}."); + return 1; + } - MaybeOverrideLockToken(snapApps, logger, options.Id, options.LockToken); + MaybeOverrideLockToken(snapApps, logger, options.Id, options.LockToken); - if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) - { - logger.Error("Please specify a lock token in your snapx.yml file. It's sufficient to generate random UUID (Guid)."); - return 1; - } + if (string.IsNullOrWhiteSpace(snapApps.Generic.Token)) + { + logger.Error("Please specify a lock token in your snapx.yml file. It's sufficient to generate random UUID (Guid)."); + return 1; + } - await using var distributedMutex = WithDistributedMutex(distributedMutexClient, logger, snapApps.BuildLockKey(snapApp), cancellationToken); + await using var distributedMutex = WithDistributedMutex(distributedMutexClient, logger, snapApps.BuildLockKey(snapApp), cancellationToken); - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info('-'.Repeat(TerminalBufferWidth)); - var tryAcquireRetries = options.LockRetries == -1 ? int.MaxValue : options.LockRetries; - if (!await distributedMutex.TryAquireAsync(TimeSpan.FromSeconds(15), tryAcquireRetries)) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - return 1; - } + var tryAcquireRetries = options.LockRetries == -1 ? int.MaxValue : options.LockRetries; + if (!await distributedMutex.TryAquireAsync(TimeSpan.FromSeconds(15), tryAcquireRetries)) + { + logger.Info('-'.Repeat(TerminalBufferWidth)); + return 1; + } - var channelsStr = string.Join(", ", snapApp.Channels.Select(x => x.Name)); + var channelsStr = string.Join(", ", snapApp.Channels.Select(x => x.Name)); - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Snap id: {options.Id}"); - logger.Info($"Rid: {options.Rid}"); - logger.Info($"Source channel: {options.Channel}"); - logger.Info($"Channels: {channelsStr}"); - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Snap id: {options.Id}"); + logger.Info($"Rid: {options.Rid}"); + logger.Info($"Source channel: {options.Channel}"); + logger.Info($"Channels: {channelsStr}"); + logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info("Downloading releases nupkg."); - var (snapAppsReleases, _, releasesMemoryStream) = await snapPackageManager.GetSnapsReleasesAsync(snapApp, logger, cancellationToken); - if (releasesMemoryStream != null) - { - await releasesMemoryStream.DisposeAsync(); - } - if (snapAppsReleases == null) - { - logger.Error($"Unknown error downloading releases nupkg: {snapApp.BuildNugetReleasesFilename()}."); - return 1; - } + logger.Info("Downloading releases nupkg."); + var (snapAppsReleases, _, releasesMemoryStream) = await snapPackageManager.GetSnapsReleasesAsync(snapApp, logger, cancellationToken); + if (releasesMemoryStream != null) + { + await releasesMemoryStream.DisposeAsync(); + } + if (snapAppsReleases == null) + { + logger.Error($"Unknown error downloading releases nupkg: {snapApp.BuildNugetReleasesFilename()}."); + return 1; + } - var snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, promoteBaseChannel); + var snapAppChannelReleases = snapAppsReleases.GetReleases(snapApp, promoteBaseChannel); - var mostRecentRelease = snapAppChannelReleases.GetMostRecentRelease(); - if (mostRecentRelease == null) - { - logger.Error($"Unable to find any releases in channel: {promoteBaseChannel.Name}."); - return 1; - } + var mostRecentRelease = snapAppChannelReleases.GetMostRecentRelease(); + if (mostRecentRelease == null) + { + logger.Error($"Unable to find any releases in channel: {promoteBaseChannel.Name}."); + return 1; + } - snapApp.Version = mostRecentRelease.Version; + snapApp.Version = mostRecentRelease.Version; - var currentChannelIndex = mostRecentRelease.Channels.FindIndex(channelName => channelName == promoteBaseChannel.Name); - var promotableChannels = snapApp.Channels - .Skip(currentChannelIndex + 1) - .Select(channel => + var currentChannelIndex = mostRecentRelease.Channels.FindIndex(channelName => channelName == promoteBaseChannel.Name); + var promotableChannels = snapApp.Channels + .Skip(currentChannelIndex + 1) + .Select(channel => + { + var releasesThisChannel = snapAppsReleases.GetReleases(snapApp, channel); + if (releasesThisChannel.Any(x => mostRecentRelease.IsFull ? x.IsFull : x.IsDelta && x.Version == snapApp.Version)) { - var releasesThisChannel = snapAppsReleases.GetReleases(snapApp, channel); - if (releasesThisChannel.Any(x => mostRecentRelease.IsFull ? x.IsFull : x.IsDelta && x.Version == snapApp.Version)) - { - return null; - } + return null; + } - return channel; - }) - .Where(x => x != null) - .ToList(); - if (!promotableChannels.Any()) - { - logger.Info($"Version {snapApp.Version} is already promoted to all channels."); - return 0; - } + return channel; + }) + .Where(x => x != null) + .ToList(); + if (!promotableChannels.Any()) + { + logger.Info($"Version {snapApp.Version} is already promoted to all channels."); + return 0; + } - var promoteToChannels = new List(); - if (options.ToAllRemainingChannels) - { - promoteToChannels.AddRange(promotableChannels); - } - else - { - promoteToChannels.Add(promotableChannels.First()); - } + var promoteToChannels = new List(); + if (options.ToAllRemainingChannels) + { + promoteToChannels.AddRange(promotableChannels); + } + else + { + promoteToChannels.Add(promotableChannels.First()); + } - var promoteToChannelsStr = string.Join(", ", promoteToChannels.Select(x => x.Name)); - if (!logger.Prompt("y|yes", - $"You are about to promote {snapApp.Id} ({snapApp.Version}) to the following " + - $"channel{(promoteToChannels.Count > 1 ? "s" : string.Empty)}: {promoteToChannelsStr}. " + - "Do you want to continue? [y|n]", infoOnly: options.YesToAllPrompts) - ) - { - return 1; - } + var promoteToChannelsStr = string.Join(", ", promoteToChannels.Select(x => x.Name)); + if (!logger.Prompt("y|yes", + $"You are about to promote {snapApp.Id} ({snapApp.Version}) to the following " + + $"channel{(promoteToChannels.Count > 1 ? "s" : string.Empty)}: {promoteToChannelsStr}. " + + "Do you want to continue? [y|n]", infoOnly: options.YesToAllPrompts) + ) + { + return 1; + } - foreach (var snapRelease in snapAppChannelReleases.Where(x => x.Version <= mostRecentRelease.Version)) + foreach (var snapRelease in snapAppChannelReleases.Where(x => x.Version <= mostRecentRelease.Version)) + { + foreach (var promoteToChannel in promoteToChannels) { - foreach (var promoteToChannel in promoteToChannels) + if (!snapRelease.Channels.Contains(promoteToChannel.Name)) { - if (!snapRelease.Channels.Contains(promoteToChannel.Name)) - { - snapRelease.Channels.Add(promoteToChannel.Name); - } + snapRelease.Channels.Add(promoteToChannel.Name); } } + } - logger.Info("Building releases nupkg."); + logger.Info("Building releases nupkg."); - var nowUtc = await SnapUtility.RetryAsync(async () => await snapNetworkTimeProvider.NowUtcAsync(), 3, 1500); - if (!nowUtc.HasValue) - { - logger.Error($"Unknown error while retrieving NTP timestamp from server: {snapNetworkTimeProvider}"); - return 1; - } + var nowUtc = await SnapUtility.RetryAsync(async () => await snapNetworkTimeProvider.NowUtcAsync(), 3, 1500); + if (!nowUtc.HasValue) + { + logger.Error($"Unknown error while retrieving NTP timestamp from server: {snapNetworkTimeProvider}"); + return 1; + } - snapAppsReleases.LastWriteAccessUtc = nowUtc.Value; + snapAppsReleases.LastWriteAccessUtc = nowUtc.Value; - await using var releasesPackageMemoryStream = snapPack.BuildReleasesPackage(snapApp, snapAppsReleases); - logger.Info("Finished building releases nupkg."); + await using var releasesPackageMemoryStream = snapPack.BuildReleasesPackage(snapApp, snapAppsReleases); + logger.Info("Finished building releases nupkg."); - var restoreOptions = new RestoreOptions - { - Id = options.Id, - Rid = options.Rid, - BuildInstallers = false, - RestoreStrategyType = SnapPackageManagerRestoreType.Default - }; + var restoreOptions = new RestoreOptions + { + Id = options.Id, + Rid = options.Rid, + BuildInstallers = false, + RestoreStrategyType = SnapPackageManagerRestoreType.Default + }; - var restoreSuccess = 0 == await CommandRestoreAsync( - restoreOptions, filesystem, snapAppReader, snapAppWriter, nuGetPackageSources, - snapPackageManager, snapOs, snapxEmbeddedResources, coreRunLib, snapPack, - logger, workingDirectory, cancellationToken - ); + var restoreSuccess = 0 == await CommandRestoreAsync( + restoreOptions, filesystem, snapAppReader, snapAppWriter, nuGetPackageSources, + snapPackageManager, snapOs, snapxEmbeddedResources, coreRunLib, snapPack, + logger, workingDirectory, cancellationToken + ); - if (!restoreSuccess) - { - return 1; - } + if (!restoreSuccess) + { + return 1; + } - await using var tmpDir = new DisposableDirectory(specialFolders.NugetCacheDirectory, filesystem); - var releasesPackageFilename = snapApp.BuildNugetReleasesFilename(); - var releasesPackageAbsolutePath = filesystem.PathCombine(tmpDir.WorkingDirectory, releasesPackageFilename); - await filesystem.FileWriteAsync(releasesPackageMemoryStream, releasesPackageAbsolutePath, cancellationToken); + await using var tmpDir = new DisposableDirectory(specialFolders.NugetCacheDirectory, filesystem); + var releasesPackageFilename = snapApp.BuildNugetReleasesFilename(); + var releasesPackageAbsolutePath = filesystem.PathCombine(tmpDir.WorkingDirectory, releasesPackageFilename); + await filesystem.FileWriteAsync(releasesPackageMemoryStream, releasesPackageAbsolutePath, cancellationToken); - if (!options.SkipInstallers && snapApp.Target.Installers.Any()) + if (!options.SkipInstallers && snapApp.Target.Installers.Any()) + { + foreach (var channel in promoteToChannels) { - foreach (var channel in promoteToChannels) - { - var snapAppInstaller = new SnapApp(snapApp); - snapAppInstaller.SetCurrentChannel(channel.Name); + var snapAppInstaller = new SnapApp(snapApp); + snapAppInstaller.SetCurrentChannel(channel.Name); - var fullNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, snapApp.BuildNugetFullFilename()); + var fullNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, snapApp.BuildNugetFullFilename()); - if (snapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Offline))) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); + if (snapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Offline))) + { + logger.Info('-'.Repeat(TerminalBufferWidth)); - var (installerOfflineSuccess, canContinueIfError, installerOfflineExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, - snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, - installersDirectory, fullNupkgAbsolutePath, releasesPackageAbsolutePath, - true, cancellationToken); + var (installerOfflineSuccess, canContinueIfError, installerOfflineExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, + snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, + installersDirectory, fullNupkgAbsolutePath, releasesPackageAbsolutePath, + true, cancellationToken); - if (!installerOfflineSuccess) - { - if (!canContinueIfError || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", infoOnly: options.YesToAllPrompts)) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Error("Unknown error building offline installer."); - return 1; - } - } - else + if (!installerOfflineSuccess) + { + if (!canContinueIfError || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", infoOnly: options.YesToAllPrompts)) { - var installerOfflineExeStat = filesystem.FileStat(installerOfflineExeAbsolutePath); - logger.Info($"Successfully built offline installer. File size: {installerOfflineExeStat.Length.BytesAsHumanReadable()}."); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Error("Unknown error building offline installer."); + return 1; } } - - if (snapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Web))) + else { - logger.Info('-'.Repeat(TerminalBufferWidth)); + var installerOfflineExeStat = filesystem.FileStat(installerOfflineExeAbsolutePath); + logger.Info($"Successfully built offline installer. File size: {installerOfflineExeStat.Length.BytesAsHumanReadable()}."); + } + } - var (installerWebSuccess, canContinueIfError, installerWebExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, - installersDirectory, null, releasesPackageAbsolutePath, - false, cancellationToken); + if (snapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Web))) + { + logger.Info('-'.Repeat(TerminalBufferWidth)); - if (!installerWebSuccess) - { - if (!canContinueIfError - || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", infoOnly: options.YesToAllPrompts)) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Error("Unknown error building web installer."); - return 1; - } - } - else + var (installerWebSuccess, canContinueIfError, installerWebExeAbsolutePath) = await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, + installersDirectory, null, releasesPackageAbsolutePath, + false, cancellationToken); + + if (!installerWebSuccess) + { + if (!canContinueIfError + || !logger.Prompt("y|yes", "Installer was not built. Do you still want to continue? (y|n)", infoOnly: options.YesToAllPrompts)) { - var installerWebExeStat = filesystem.FileStat(installerWebExeAbsolutePath); - logger.Info($"Successfully built web installer. File size: {installerWebExeStat.Length.BytesAsHumanReadable()}."); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Error("Unknown error building web installer."); + return 1; } } + else + { + var installerWebExeStat = filesystem.FileStat(installerWebExeAbsolutePath); + logger.Info($"Successfully built web installer. File size: {installerWebExeStat.Length.BytesAsHumanReadable()}."); + } } } + } - logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info('-'.Repeat(TerminalBufferWidth)); - foreach (var (channel, packageSource) in promoteToChannels.Select(snapChannel => - { - var packageSource = nuGetPackageSources.Items.Single(x => x.Name == snapChannel.PushFeed.Name); - return (snapChannel, packageSource); - }).DistinctBy(x => x.packageSource.SourceUri)) - { - logger.Info($"Uploading releases nupkg to feed: {packageSource.Name}."); + foreach (var (channel, packageSource) in promoteToChannels.Select(snapChannel => + { + var packageSource = nuGetPackageSources.Items.Single(x => x.Name == snapChannel.PushFeed.Name); + return (snapChannel, packageSource); + }).DistinctBy(x => x.packageSource.SourceUri)) + { + logger.Info($"Uploading releases nupkg to feed: {packageSource.Name}."); - await PushPackageAsync(nugetService, filesystem, distributedMutex, nuGetPackageSources, packageSource, - channel, releasesPackageAbsolutePath, logger, cancellationToken); + await PushPackageAsync(nugetService, filesystem, distributedMutex, nuGetPackageSources, packageSource, + channel, releasesPackageAbsolutePath, logger, cancellationToken); - var skipInitialBlock = packageSource.IsLocalOrUncPath(); + var skipInitialBlock = packageSource.IsLocalOrUncPath(); - await BlockUntilSnapUpdatedReleasesNupkgAsync(logger, snapPackageManager, - snapAppsReleases, snapApp, channel, TimeSpan.FromSeconds(15), cancellationToken, skipInitialBlock,options.SkipAwaitUpdate ); + await BlockUntilSnapUpdatedReleasesNupkgAsync(logger, snapPackageManager, + snapAppsReleases, snapApp, channel, TimeSpan.FromSeconds(15), cancellationToken, skipInitialBlock,options.SkipAwaitUpdate ); - logger.Info($"Successfully uploaded releases nupkg to channel: {channel.Name}."); - logger.Info('-'.Repeat(TerminalBufferWidth)); - } + logger.Info($"Successfully uploaded releases nupkg to channel: {channel.Name}."); + logger.Info('-'.Repeat(TerminalBufferWidth)); + } - logger.Info($"Promote completed in {stopWatch.Elapsed.TotalSeconds:0.0}s."); + logger.Info($"Promote completed in {stopWatch.Elapsed.TotalSeconds:0.0}s."); - await CommandListAsync(new ListOptions {Id = snapApp.Id}, filesystem, snapAppReader, - nuGetPackageSources, nugetService, snapExtractor, snapPackageManager, logger, workingDirectory, cancellationToken); + await CommandListAsync(new ListOptions {Id = snapApp.Id}, filesystem, snapAppReader, + nuGetPackageSources, nugetService, snapExtractor, snapPackageManager, logger, workingDirectory, cancellationToken); - return 0; - } + return 0; } -} +} \ No newline at end of file diff --git a/src/Snapx/Program.CommandRcEdit.cs b/src/Snapx/Program.CommandRcEdit.cs index 0a435288..ecab12ae 100644 --- a/src/Snapx/Program.CommandRcEdit.cs +++ b/src/Snapx/Program.CommandRcEdit.cs @@ -7,70 +7,69 @@ using Snap.Extensions; using Snap.Logging; -namespace snapx +namespace snapx; + +internal partial class Program { - internal partial class Program + static int CommandRcEdit([NotNull] RcEditOptions opts, [NotNull] ICoreRunLib coreRunLib, + [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ILog logger) { - static int CommandRcEdit([NotNull] RcEditOptions opts, [NotNull] ICoreRunLib coreRunLib, - [NotNull] ISnapFilesystem snapFilesystem, [NotNull] ILog logger) - { - if (opts == null) throw new ArgumentNullException(nameof(opts)); - if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); - if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); + if (opts == null) throw new ArgumentNullException(nameof(opts)); + if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); + if (snapFilesystem == null) throw new ArgumentNullException(nameof(snapFilesystem)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); - var exitCode = 1; + var exitCode = 1; - if (!snapFilesystem.FileExists(opts.Filename)) - { - logger.Error($"Filename does not exist: {opts.Filename}."); - goto done; - } + if (!snapFilesystem.FileExists(opts.Filename)) + { + logger.Error($"Filename does not exist: {opts.Filename}."); + goto done; + } - if (opts.ConvertSubSystemToWindowsGui) - { - logger.Info($"Attempting to change subsystem to Windows GUI for executable: {snapFilesystem.PathGetFileName(opts.Filename)}."); + if (opts.ConvertSubSystemToWindowsGui) + { + logger.Info($"Attempting to change subsystem to Windows GUI for executable: {snapFilesystem.PathGetFileName(opts.Filename)}."); - using (var srcStream = snapFilesystem.FileReadWrite(opts.Filename, false)) + using (var srcStream = snapFilesystem.FileReadWrite(opts.Filename, false)) + { + if (!srcStream.ChangeSubsystemToWindowsGui(SnapLogger)) { - if (!srcStream.ChangeSubsystemToWindowsGui(SnapLogger)) - { - goto done; - } - - logger.Info("Subsystem has been successfully changed to Windows GUI."); + goto done; } - exitCode = 0; + logger.Info("Subsystem has been successfully changed to Windows GUI."); } - if (opts.IconFilename != null) - { - if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - logger.Error("Modifying executable icon is not supported on this OS platform."); - goto done; - } - opts.IconFilename = snapFilesystem.PathGetFullPath(opts.IconFilename); - if (!snapFilesystem.FileExists(opts.IconFilename)) - { - logger.Error($"Unable to find icon with filename: {opts.IconFilename}"); - goto done; - } + exitCode = 0; + } - if (!coreRunLib.SetIcon(opts.Filename, opts.IconFilename)) - { - logger.Error($"Unknown error setting icon for executable {opts.Filename}. Icon filename: {opts.Filename}."); - goto done; - } + if (opts.IconFilename != null) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + logger.Error("Modifying executable icon is not supported on this OS platform."); + goto done; + } + opts.IconFilename = snapFilesystem.PathGetFullPath(opts.IconFilename); + if (!snapFilesystem.FileExists(opts.IconFilename)) + { + logger.Error($"Unable to find icon with filename: {opts.IconFilename}"); + goto done; + } - logger.Info("Icon has been successfully updated."); - exitCode = 0; + if (!coreRunLib.SetIcon(opts.Filename, opts.IconFilename)) + { + logger.Error($"Unknown error setting icon for executable {opts.Filename}. Icon filename: {opts.Filename}."); + goto done; } - done: - return exitCode; + logger.Info("Icon has been successfully updated."); + exitCode = 0; } + done: + return exitCode; } -} + +} \ No newline at end of file diff --git a/src/Snapx/Program.CommandRestore.cs b/src/Snapx/Program.CommandRestore.cs index cd5c73a9..535b9c04 100644 --- a/src/Snapx/Program.CommandRestore.cs +++ b/src/Snapx/Program.CommandRestore.cs @@ -16,178 +16,177 @@ using Snap.NuGet; using Snap.Extensions; -namespace snapx +namespace snapx; + +internal partial class Program { - internal partial class Program + static async Task CommandRestoreAsync([NotNull] RestoreOptions restoreOptions, + [NotNull] ISnapFilesystem filesystem, [NotNull] ISnapAppReader snapAppReader, ISnapAppWriter snapAppWriter, + [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] ISnapPackageManager snapPackageManager, + [NotNull] ISnapOs snapOs, [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ICoreRunLib coreRunLib, + [NotNull] ISnapPack snapPack, [NotNull] ILog logger, + [NotNull] string workingDirectory, CancellationToken cancellationToken) { - static async Task CommandRestoreAsync([NotNull] RestoreOptions restoreOptions, - [NotNull] ISnapFilesystem filesystem, [NotNull] ISnapAppReader snapAppReader, ISnapAppWriter snapAppWriter, - [NotNull] INuGetPackageSources nuGetPackageSources, [NotNull] ISnapPackageManager snapPackageManager, - [NotNull] ISnapOs snapOs, [NotNull] ISnapxEmbeddedResources snapxEmbeddedResources, [NotNull] ICoreRunLib coreRunLib, - [NotNull] ISnapPack snapPack, [NotNull] ILog logger, - [NotNull] string workingDirectory, CancellationToken cancellationToken) + if (restoreOptions == null) throw new ArgumentNullException(nameof(restoreOptions)); + if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); + if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); + if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); + if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); + if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); + if (snapxEmbeddedResources == null) throw new ArgumentNullException(nameof(snapxEmbeddedResources)); + if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); + if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); + if (logger == null) throw new ArgumentNullException(nameof(logger)); + + if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); + var stopwatch = new Stopwatch(); + stopwatch.Restart(); + + var (snapApps, snapAppTargets, errorBuildingSnapApps, _) = BuildSnapAppsesFromDirectory(filesystem, snapAppReader, + nuGetPackageSources, workingDirectory, requirePushFeed: false); + + if (!snapApps.Apps.Any() || errorBuildingSnapApps) { - if (restoreOptions == null) throw new ArgumentNullException(nameof(restoreOptions)); - if (filesystem == null) throw new ArgumentNullException(nameof(filesystem)); - if (snapAppReader == null) throw new ArgumentNullException(nameof(snapAppReader)); - if (nuGetPackageSources == null) throw new ArgumentNullException(nameof(nuGetPackageSources)); - if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); - if (snapOs == null) throw new ArgumentNullException(nameof(snapOs)); - if (snapxEmbeddedResources == null) throw new ArgumentNullException(nameof(snapxEmbeddedResources)); - if (coreRunLib == null) throw new ArgumentNullException(nameof(coreRunLib)); - if (snapPack == null) throw new ArgumentNullException(nameof(snapPack)); - if (logger == null) throw new ArgumentNullException(nameof(logger)); - - if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); - var stopwatch = new Stopwatch(); - stopwatch.Restart(); - - var (snapApps, snapAppTargets, errorBuildingSnapApps, _) = BuildSnapAppsesFromDirectory(filesystem, snapAppReader, - nuGetPackageSources, workingDirectory, requirePushFeed: false); - - if (!snapApps.Apps.Any() || errorBuildingSnapApps) - { - return 1; - } + return 1; + } - if (restoreOptions.Id != null) - { - snapAppTargets.RemoveAll(x => - !string.Equals(x.Id, restoreOptions.Id, StringComparison.OrdinalIgnoreCase)); - } + if (restoreOptions.Id != null) + { + snapAppTargets.RemoveAll(x => + !string.Equals(x.Id, restoreOptions.Id, StringComparison.OrdinalIgnoreCase)); + } - if (restoreOptions.Rid != null) - { - snapAppTargets.RemoveAll(x => - !string.Equals(x.Target.Rid, restoreOptions.Rid, StringComparison.OrdinalIgnoreCase)); - } + if (restoreOptions.Rid != null) + { + snapAppTargets.RemoveAll(x => + !string.Equals(x.Target.Rid, restoreOptions.Rid, StringComparison.OrdinalIgnoreCase)); + } - if (!snapAppTargets.Any()) - { - logger.Error($"Unable to restore application {restoreOptions.Id} because it does not exist."); - return 1; - } + if (!snapAppTargets.Any()) + { + logger.Error($"Unable to restore application {restoreOptions.Id} because it does not exist."); + return 1; + } - if (restoreOptions.BuildInstallers) - { - restoreOptions.RestoreStrategyType = SnapPackageManagerRestoreType.Default; - } + if (restoreOptions.BuildInstallers) + { + restoreOptions.RestoreStrategyType = SnapPackageManagerRestoreType.Default; + } - var applicationNames = snapAppTargets.Select(x => x.Id).Distinct().ToList(); - var rids = snapAppTargets.Select(x => x.Target.Rid).Distinct().ToList(); + var applicationNames = snapAppTargets.Select(x => x.Id).Distinct().ToList(); + var rids = snapAppTargets.Select(x => x.Target.Rid).Distinct().ToList(); - logger.Info($"Applications that will be restored: {string.Join(", ", applicationNames)}. Runtime identifiers (RID): {string.Join(", ", rids)}."); + logger.Info($"Applications that will be restored: {string.Join(", ", applicationNames)}. Runtime identifiers (RID): {string.Join(", ", rids)}."); - var releasePackages = new Dictionary(); + var releasePackages = new Dictionary(); - foreach (var snapApp in snapAppTargets) + foreach (var snapApp in snapAppTargets) + { + var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); + var installersDirectory = BuildInstallersDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); + var releasesNupkgAbsolutePath = filesystem.PathCombine(filesystem.DirectoryGetParent(packagesDirectory), snapApp.BuildNugetReleasesFilename()); + + filesystem.DirectoryCreateIfNotExists(packagesDirectory); + filesystem.DirectoryCreateIfNotExists(installersDirectory); + + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Id: {snapApp.Id}."); + logger.Info($"Rid: {snapApp.Target.Rid}"); + logger.Info($"Packages directory: {packagesDirectory}"); + logger.Info($"Restore strategy: {restoreOptions.RestoreStrategyType}"); + logger.Info($"Restore installers: {(restoreOptions.BuildInstallers ? "yes" : "no")}"); + + SnapAppsReleases snapAppsReleases; + PackageSource packageSource; + if (releasePackages.TryGetValue(snapApp.Id, out var cached)) { - var packagesDirectory = BuildPackagesDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); - var installersDirectory = BuildInstallersDirectory(filesystem, workingDirectory, snapApps.Generic, snapApp); - var releasesNupkgAbsolutePath = filesystem.PathCombine(filesystem.DirectoryGetParent(packagesDirectory), snapApp.BuildNugetReleasesFilename()); + snapAppsReleases = cached.snapReleases; + packageSource = cached.packageSource; + } + else + { + logger.Info($"Downloading releases nupkg for application {snapApp.Id}"); + + // ReSharper disable once UseDeconstruction + var uncached = await snapPackageManager.GetSnapsReleasesAsync(snapApp, logger, cancellationToken); + if (uncached.snapAppsReleases == null) + { + logger.Error($"Failed to download releases nupkg for application {snapApp.Id}"); + continue; + } - filesystem.DirectoryCreateIfNotExists(packagesDirectory); - filesystem.DirectoryCreateIfNotExists(installersDirectory); + await using (uncached.releasesMemoryStream) + { + await filesystem.FileWriteAsync(uncached.releasesMemoryStream, releasesNupkgAbsolutePath, cancellationToken); + } + + snapAppsReleases = uncached.snapAppsReleases; + packageSource = uncached.packageSource; + releasePackages.Add(snapApp.Id, (uncached.snapAppsReleases, uncached.packageSource)); + + logger.Info($"Downloaded releases nupkg. Current version: {snapAppsReleases.Version}."); + } + foreach (var snapChannel in snapAppsReleases.GetChannels(snapApp)) + { logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Id: {snapApp.Id}."); - logger.Info($"Rid: {snapApp.Target.Rid}"); - logger.Info($"Packages directory: {packagesDirectory}"); - logger.Info($"Restore strategy: {restoreOptions.RestoreStrategyType}"); - logger.Info($"Restore installers: {(restoreOptions.BuildInstallers ? "yes" : "no")}"); - - SnapAppsReleases snapAppsReleases; - PackageSource packageSource; - if (releasePackages.TryGetValue(snapApp.Id, out var cached)) + logger.Info($"Restoring channel {snapChannel.Name}."); + + var snapAppReleases = snapAppsReleases.GetReleases(snapApp, snapChannel); + if (!snapAppReleases.Any()) { - snapAppsReleases = cached.snapReleases; - packageSource = cached.packageSource; + logger.Info($"Skipping restore for channel {snapChannel.Name} because no releases was found."); + continue; } - else + + var restoreSummary = await snapPackageManager.RestoreAsync(packagesDirectory, snapAppReleases, packageSource, + restoreOptions.RestoreStrategyType, + logger: logger, cancellationToken: cancellationToken, + checksumConcurrency: restoreOptions.RestoreConcurrency, + downloadConcurrency: restoreOptions.DownloadConcurrency); + + if (!restoreSummary.Success || !restoreOptions.BuildInstallers) { - logger.Info($"Downloading releases nupkg for application {snapApp.Id}"); - - // ReSharper disable once UseDeconstruction - var uncached = await snapPackageManager.GetSnapsReleasesAsync(snapApp, logger, cancellationToken); - if (uncached.snapAppsReleases == null) - { - logger.Error($"Failed to download releases nupkg for application {snapApp.Id}"); - continue; - } - - await using (uncached.releasesMemoryStream) - { - await filesystem.FileWriteAsync(uncached.releasesMemoryStream, releasesNupkgAbsolutePath, cancellationToken); - } - - snapAppsReleases = uncached.snapAppsReleases; - packageSource = uncached.packageSource; - releasePackages.Add(snapApp.Id, (uncached.snapAppsReleases, uncached.packageSource)); - - logger.Info($"Downloaded releases nupkg. Current version: {snapAppsReleases.Version}."); + continue; } - foreach (var snapChannel in snapAppsReleases.GetChannels(snapApp)) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Restoring channel {snapChannel.Name}."); - - var snapAppReleases = snapAppsReleases.GetReleases(snapApp, snapChannel); - if (!snapAppReleases.Any()) - { - logger.Info($"Skipping restore for channel {snapChannel.Name} because no releases was found."); - continue; - } - - var restoreSummary = await snapPackageManager.RestoreAsync(packagesDirectory, snapAppReleases, packageSource, - restoreOptions.RestoreStrategyType, - logger: logger, cancellationToken: cancellationToken, - checksumConcurrency: restoreOptions.RestoreConcurrency, - downloadConcurrency: restoreOptions.DownloadConcurrency); - - if (!restoreSummary.Success || !restoreOptions.BuildInstallers) - { - continue; - } - - var mostRecentSnapRelease = snapAppReleases.GetMostRecentRelease(); + var mostRecentSnapRelease = snapAppReleases.GetMostRecentRelease(); - var snapAppInstaller = new SnapApp(snapAppReleases.App) - { - Version = mostRecentSnapRelease.Version - }; + var snapAppInstaller = new SnapApp(snapAppReleases.App) + { + Version = mostRecentSnapRelease.Version + }; - snapAppInstaller.SetCurrentChannel(snapChannel.Name); + snapAppInstaller.SetCurrentChannel(snapChannel.Name); - if (restoreOptions.BuildInstallers - || snapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Web))) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); - - await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, - installersDirectory, null, releasesNupkgAbsolutePath, false, cancellationToken); - } + if (restoreOptions.BuildInstallers + || snapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Web))) + { + logger.Info('-'.Repeat(TerminalBufferWidth)); + + await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, + installersDirectory, null, releasesNupkgAbsolutePath, false, cancellationToken); + } - if (restoreOptions.BuildInstallers - || snapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Offline))) - { - logger.Info('-'.Repeat(TerminalBufferWidth)); + if (restoreOptions.BuildInstallers + || snapApp.Target.Installers.Any(x => x.HasFlag(SnapInstallerType.Offline))) + { + logger.Info('-'.Repeat(TerminalBufferWidth)); - var fullNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, mostRecentSnapRelease.BuildNugetFullFilename()); + var fullNupkgAbsolutePath = filesystem.PathCombine(packagesDirectory, mostRecentSnapRelease.BuildNugetFullFilename()); - await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, - installersDirectory, fullNupkgAbsolutePath, releasesNupkgAbsolutePath, true, cancellationToken); - } - - logger.Info($"Finished restoring channel {snapChannel.Name}."); - logger.Info('-'.Repeat(TerminalBufferWidth)); + await BuildInstallerAsync(logger, snapOs, snapxEmbeddedResources, snapAppWriter, snapAppInstaller, coreRunLib, + installersDirectory, fullNupkgAbsolutePath, releasesNupkgAbsolutePath, true, cancellationToken); } + + logger.Info($"Finished restoring channel {snapChannel.Name}."); + logger.Info('-'.Repeat(TerminalBufferWidth)); } + } - logger.Info('-'.Repeat(TerminalBufferWidth)); - logger.Info($"Restore completed in {stopwatch.Elapsed.TotalSeconds:0.0}s."); + logger.Info('-'.Repeat(TerminalBufferWidth)); + logger.Info($"Restore completed in {stopwatch.Elapsed.TotalSeconds:0.0}s."); - return 0; - } + return 0; } -} +} \ No newline at end of file diff --git a/src/Snapx/Program.cs b/src/Snapx/Program.cs index 0dcc3a8b..04e85831 100644 --- a/src/Snapx/Program.cs +++ b/src/Snapx/Program.cs @@ -162,7 +162,6 @@ static int MainImplAsync([NotNull] string[] args) snapAppWriter, snapCryptoProvider, snapEmbeddedResources, snapBinaryPatcher); var snapExtractor = new SnapExtractor(snapOs.Filesystem, snapPack, snapEmbeddedResources); var snapSpecsReader = new SnapAppReader(); - var snapNetworkTimeProvider = new SnapNetworkTimeProvider("time.cloudflare.com", 123); var snapHttpClient = new SnapHttpClient(new HttpClient()); var nugetServiceCommandPack = new NugetService(snapOs.Filesystem, new NugetLogger(SnapPackLogger)); @@ -187,10 +186,8 @@ static int MainImplAsync([NotNull] string[] args) return MainAsync(args, coreRunLib, snapOs, snapExtractor, snapOs.Filesystem, snapSpecsReader, snapCryptoProvider, - snapPack, snapAppWriter, snapXEmbeddedResources, snapPackageRestorer, snapNetworkTimeProvider, - nugetServiceCommandPack, nugetServiceCommandPromote, nugetServiceCommandDemote, - nugetServiceCommandRestore, nugetServiceNoopLogger, distributedMutexClient, - toolWorkingDirectory, workingDirectory, cts.Token); + snapPack, snapAppWriter, snapXEmbeddedResources, snapPackageRestorer, + nugetServiceCommandPack, nugetServiceCommandPromote, nugetServiceCommandDemote, nugetServiceNoopLogger, distributedMutexClient, workingDirectory, cts.Token); } static async Task OnExitAsync() @@ -220,14 +217,11 @@ static int MainAsync([NotNull] string[] args, [NotNull] ISnapAppWriter snapAppWriter, [NotNull] SnapxEmbeddedResources snapXEmbeddedResources, [NotNull] SnapPackageManager snapPackageManager, - [NotNull] ISnapNetworkTimeProvider snapNetworkTimeProvider, [NotNull] INugetService nugetServiceCommandPack, [NotNull] INugetService nugetServiceCommandPromote, [NotNull] INugetService nugetServiceCommandDemote, - INugetService nugetServiceCommandRestore, [NotNull] INugetService nugetServiceNoopLogger, - [NotNull] IDistributedMutexClient distributedMutexClient, - [NotNull] string toolWorkingDirectory, + [NotNull] IDistributedMutexClient distributedMutexClient, [NotNull] string workingDirectory, CancellationToken cancellationToken) { @@ -242,15 +236,15 @@ static int MainAsync([NotNull] string[] args, if (snapAppWriter == null) throw new ArgumentNullException(nameof(snapAppWriter)); if (snapXEmbeddedResources == null) throw new ArgumentNullException(nameof(snapXEmbeddedResources)); if (snapPackageManager == null) throw new ArgumentNullException(nameof(snapPackageManager)); - if (snapNetworkTimeProvider == null) throw new ArgumentNullException(nameof(snapNetworkTimeProvider)); if (nugetServiceCommandPromote == null) throw new ArgumentNullException(nameof(nugetServiceCommandPromote)); if (nugetServiceNoopLogger == null) throw new ArgumentNullException(nameof(nugetServiceNoopLogger)); if (distributedMutexClient == null) throw new ArgumentNullException(nameof(distributedMutexClient)); - if (toolWorkingDirectory == null) throw new ArgumentNullException(nameof(toolWorkingDirectory)); if (workingDirectory == null) throw new ArgumentNullException(nameof(workingDirectory)); if (args == null) throw new ArgumentNullException(nameof(args)); + var defaultNetworkTimeProvider = new SnapNetworkTimeProvider("time.cloudflare.com", 123); + return Parser .Default .ParseArguments(args) @@ -262,6 +256,9 @@ static int MainAsync([NotNull] string[] args, { return 1; } + + var snapNetworkTimeProvider = opts.NetworkTimeProviderConnectionString.BuildNtpProvider() ?? defaultNetworkTimeProvider; + return TplHelper.RunSync(() => CommandDemoteAsync(opts, snapFilesystem, snapAppReader, snapAppWriter, nuGetPackageSources, nugetServiceCommandDemote, distributedMutexClient, snapPackageManager, snapPack, snapNetworkTimeProvider, snapExtractor, snapOs, snapXEmbeddedResources, coreRunLib, @@ -274,6 +271,9 @@ static int MainAsync([NotNull] string[] args, { return 1; } + + var snapNetworkTimeProvider = opts.NetworkTimeProviderConnectionString.BuildNtpProvider() ?? defaultNetworkTimeProvider; + return TplHelper.RunSync(() => CommandPromoteAsync(opts, snapFilesystem, snapAppReader, snapAppWriter, nuGetPackageSources, nugetServiceCommandPromote, distributedMutexClient, snapPackageManager, snapPack, snapOs.SpecialFolders, @@ -287,6 +287,9 @@ static int MainAsync([NotNull] string[] args, { return 1; } + + var snapNetworkTimeProvider = opts.NetworkTimeProviderConnectionString.BuildNtpProvider() ?? defaultNetworkTimeProvider; + return TplHelper.RunSync(() => CommandPackAsync(opts, snapFilesystem, snapAppReader, snapAppWriter, nuGetPackageSources, snapPack, nugetServiceCommandPack, snapOs, snapXEmbeddedResources, snapExtractor, snapPackageManager, coreRunLib, diff --git a/src/Snapx/Resources/ResourcesTypeRoot.cs b/src/Snapx/Resources/ResourcesTypeRoot.cs index 4d3b491f..e25536a2 100644 --- a/src/Snapx/Resources/ResourcesTypeRoot.cs +++ b/src/Snapx/Resources/ResourcesTypeRoot.cs @@ -1,6 +1,5 @@ -namespace snapx.Resources +namespace snapx.Resources; + +class ResourcesTypeRoot { - class ResourcesTypeRoot - { - } -} +} \ No newline at end of file diff --git a/src/Snapx/Snapx.csproj b/src/Snapx/Snapx.csproj index 37f182b9..12891b1b 100644 --- a/src/Snapx/Snapx.csproj +++ b/src/Snapx/Snapx.csproj @@ -11,8 +11,8 @@ true snapx true - net5.0 - net5.0 + net6.0 + net6.0 true false @@ -31,8 +31,7 @@ - - + diff --git a/src/Vendor/gtest b/src/Vendor/gtest index c6e309b2..ab36804e 160000 --- a/src/Vendor/gtest +++ b/src/Vendor/gtest @@ -1 +1 @@ -Subproject commit c6e309b268d4fb9138bed7d0f56b7709c29f102f +Subproject commit ab36804e42d4cb85b7e7fe9946928597840684db diff --git a/tools/warp-packer-linux-arm64.exe b/tools/warp-packer-linux-arm64.exe index 371038cc..977e9203 100644 --- a/tools/warp-packer-linux-arm64.exe +++ b/tools/warp-packer-linux-arm64.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30b05e2acf4114758f9516bfe0c6456ee43384ede160c3f44c00583cb16ada34 -size 5109936 +oid sha256:99a5e2b25d9487047880c7645e4af3c965ab87b0396cedcc0899e325aa42b8bf +size 5470392 diff --git a/tools/warp-packer-linux-x64.exe b/tools/warp-packer-linux-x64.exe index 4e2cba69..da198411 100755 --- a/tools/warp-packer-linux-x64.exe +++ b/tools/warp-packer-linux-x64.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e76556d48c764c1bfac0f75f343dcd321dcc2dd1bb6008d0d11be51b9fede0de -size 6305176 +oid sha256:e38a78f3bdcd2d915164e334f8b3334848f343b5546c0f3da40bcfbb0794f3ea +size 6921672 diff --git a/tools/warp-packer-macosx-x64.exe b/tools/warp-packer-macosx-x64.exe index 356e3f5a..42e8f187 100644 --- a/tools/warp-packer-macosx-x64.exe +++ b/tools/warp-packer-macosx-x64.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2cc157d27c59b760a665a53adb1238d6f72083a41ece1a1b3f003ab2a6e6086e -size 5090696 +oid sha256:fc95ad0d953406af9d3b4993494f4681de3986ad163918868bc7bc9669ef3854 +size 5512152 diff --git a/tools/warp-packer-win-x64.exe b/tools/warp-packer-win-x64.exe index 77abc096..d674d0af 100644 --- a/tools/warp-packer-win-x64.exe +++ b/tools/warp-packer-win-x64.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b73e2f5105a3005b166355bd2dbd102cd0f81da38facc50655851964a12421b7 -size 5144064 +oid sha256:c8722513422695cbead2121e77ee1b8ccb38d611d2b809c59ab01c0a4bb5090a +size 5493248 diff --git a/tools/warp-packer-win-x86.exe b/tools/warp-packer-win-x86.exe index 7855adf2..3a31ee76 100644 --- a/tools/warp-packer-win-x86.exe +++ b/tools/warp-packer-win-x86.exe @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0a059513b78cae7c35399f50dae4fb6f281e0fd2db3f55e7b84655dde2686e1 -size 5067264 +oid sha256:34b52cb1e93256ef23424e76e7a3828af07c7ea226406e5e9005cddfb50ec7d8 +size 5417472