From 8f5102aa2a3599c16824c9e50738fd0ab4bd2e66 Mon Sep 17 00:00:00 2001 From: GreemDev Date: Tue, 3 Jun 2025 18:28:59 -0500 Subject: [PATCH] infra: Add functionality to the CI to upload artifacts to this GitLab and make releases based on all files uploaded. See merge request ryubing/ryujinx!48 --- .github/workflows/canary.yml | 79 +++++++- .github/workflows/debug_release.yml | 33 +-- .github/workflows/release.yml | 82 ++++++-- src/Ryujinx.Common/ReleaseInformation.cs | 52 +---- .../GitLab/GitLabReleaseAssetJsonResponse.cs | 20 ++ .../GitLab/GitLabReleasesJsonResponse.cs | 19 ++ src/Ryujinx/Systems/Updater/Updater.GitHub.cs | 190 ++++++++++++++++++ src/Ryujinx/Systems/Updater/Updater.GitLab.cs | 138 +++++++++++++ src/Ryujinx/Systems/{ => Updater}/Updater.cs | 139 +++---------- src/Ryujinx/UI/Windows/MainWindow.axaml.cs | 2 +- 10 files changed, 558 insertions(+), 196 deletions(-) create mode 100644 src/Ryujinx/Common/Models/GitLab/GitLabReleaseAssetJsonResponse.cs create mode 100644 src/Ryujinx/Common/Models/GitLab/GitLabReleasesJsonResponse.cs create mode 100644 src/Ryujinx/Systems/Updater/Updater.GitHub.cs create mode 100644 src/Ryujinx/Systems/Updater/Updater.GitLab.cs rename src/Ryujinx/Systems/{ => Updater}/Updater.cs (83%) diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index de3dd6892..b0678724a 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -79,7 +79,7 @@ jobs: matrix: platform: - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } - - { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 } + #- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 } - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } steps: @@ -91,6 +91,16 @@ jobs: - name: Overwrite csc problem matcher run: echo "::add-matcher::.github/csc.json" + + - name: Install GitLabCli + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + chmod +x gli + mv gli $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Get version info id: version_info @@ -106,7 +116,7 @@ jobs: sed -r --in-place 's/\%\%RYUJINX_BUILD_GIT_HASH\%\%/${{ steps.version_info.outputs.git_short_hash }}/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs - sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/Ryujinx.desktop + sed -r --in-place '/^Name=Ryujinx$/s/Name=Ryujinx/Name=Ryujinx-Canary/' distribution/linux/Ryujinx.desktop shell: bash - name: Create output dir @@ -123,7 +133,24 @@ jobs: rm libarmeilleure-jitsupport.dylib 7z a ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish popd + + gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe' + + ./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip" shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install GitLabCli + if: matrix.platform.os == 'ubuntu-latest' + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + chmod +x gli + mv gli $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Packing Linux builds if: matrix.platform.os == 'ubuntu-latest' @@ -133,6 +160,8 @@ jobs: chmod +x Ryujinx.sh Ryujinx tar -czvf ../release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish popd + + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz" shell: bash - name: Build AppImage (Linux) @@ -169,7 +198,10 @@ jobs: pushd publish_appimage mv Ryujinx.AppImage ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage mv Ryujinx.AppImage.zsync ../release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync - popd + popd + + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage" + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-canary-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync" shell: bash - name: Pushing new release @@ -214,6 +246,16 @@ jobs: wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 17 + + - name: Install GitLabCli + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + chmod +x gli + mv gli $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install rcodesign run: | @@ -246,6 +288,7 @@ jobs: - name: Publish macOS Ryujinx run: | ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 1 + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=UploadGenericPackage "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|publish_ava/ryujinx-canary-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz" - name: Pushing new release uses: ncipollo/release-action@v1 @@ -260,3 +303,33 @@ jobs: owner: ${{ secrets.RC_OWNER }} repo: ${{ secrets.RC_CANARY_NAME }} token: ${{ secrets.ALT_RELEASE_TOKEN }} + + create_gitlab_release: + name: Create GitLab Release + runs-on: ubuntu-24.04 + needs: + - tag + - macos_release + - release + steps: + - name: Get version info + id: version_info + run: | + echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT + echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT + echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + shell: bash + + - name: Install GitLabCli + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + chmod +x gli + mv gli $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create release + run: | + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/canary --command=CreateReleaseFromGenericPackageFiles "Ryubing-Canary|${{ steps.version_info.outputs.build_version }}|main|Canary ${{ steps.version_info.outputs.build_version }}|**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-${{ steps.version_info.outputs.prev_build_version }}...Canary-${{ steps.version_info.outputs.build_version }})**" diff --git a/.github/workflows/debug_release.yml b/.github/workflows/debug_release.yml index ce97bec7e..b166adb61 100644 --- a/.github/workflows/debug_release.yml +++ b/.github/workflows/debug_release.yml @@ -67,8 +67,21 @@ jobs: gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe' - ./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip" + ./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip" shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install GitLabCli + if: matrix.platform.os == 'ubuntu-latest' + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + chmod +x gli + mv gli $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Packing Linux builds if: matrix.platform.os == 'ubuntu-latest' @@ -79,13 +92,7 @@ jobs: tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish popd - mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' - chmod +x gli - mv gli $HOME/.bin/ - echo "$HOME/.bin" >> $GITHUB_PATH - - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz" + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz" shell: bash - name: Build AppImage (Linux) @@ -124,8 +131,8 @@ jobs: mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync popd - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage" - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync" + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage" + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync" shell: bash macos_release: @@ -183,7 +190,7 @@ jobs: - name: Publish macOS Ryujinx run: | ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0 - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "ryubing|${{ steps.version_info.outputs.build_version }}|publish_ava/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz" + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz" create_gitlab_release: name: Create GitLab Release @@ -192,6 +199,8 @@ jobs: - macos_release - release steps: + - uses: actions/checkout@v4 + - name: Get version info id: version_info run: | @@ -212,4 +221,4 @@ jobs: - name: Create release run: | - gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|test|THIS IS NOT INTENDED FOR END USER USAGE" + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|test|THIS IS NOT INTENDED FOR END USER USAGE" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 85c494dcb..4c443b969 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,19 +24,6 @@ jobs: echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT shell: bash - - - name: Install GitLabCli - run: | - mkdir -p $HOME/.bin - gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' - chmod +x gli - mv gli $HOME/.bin/ - echo "$HOME/.bin" >> $GITHUB_PATH - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create GitLab tag - run: gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateTag "${{ steps.version_info.outputs.build_version }}|master" - name: Create release uses: ncipollo/release-action@v1 @@ -66,7 +53,7 @@ jobs: matrix: platform: - { name: win-x64, os: windows-latest, zip_os_name: win_x64 } - - { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 } + #- { name: win-arm64, os: windows-latest, zip_os_name: win_arm64 } - { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 } - { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 } steps: @@ -109,7 +96,24 @@ jobs: rm libarmeilleure-jitsupport.dylib 7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip ../publish popd + + gh release download -R GreemDev/GLI -O gli.exe -p 'GitLabCli-win_x64.exe' + + ./gli.exe --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip" shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install GitLabCli + if: matrix.platform.os == 'ubuntu-latest' + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + chmod +x gli + mv gli $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Packing Linux builds if: matrix.platform.os == 'ubuntu-latest' @@ -119,7 +123,11 @@ jobs: chmod +x Ryujinx.sh Ryujinx tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz ../publish popd + + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz" shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build AppImage (Linux) if: matrix.platform.os == 'ubuntu-latest' @@ -156,6 +164,9 @@ jobs: mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync popd + + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage" + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync" shell: bash - name: Pushing new release @@ -197,6 +208,16 @@ jobs: wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh sudo ./llvm.sh 17 + + - name: Install GitLabCli + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + chmod +x gli + mv gli $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install rcodesign run: | @@ -227,6 +248,7 @@ jobs: - name: Publish macOS Ryujinx run: | ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release 0 + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=UploadGenericPackage "Ryubing|${{ steps.version_info.outputs.build_version }}|publish/ryujinx-${{ steps.version_info.outputs.build_version }}-macos_universal.app.tar.gz" - name: Pushing new release uses: ncipollo/release-action@v1 @@ -241,3 +263,35 @@ jobs: owner: ${{ secrets.RC_OWNER }} repo: ${{ secrets.RC_STABLE_NAME }} token: ${{ secrets.ALT_RELEASE_TOKEN }} + + create_gitlab_release: + name: Create GitLab Release + runs-on: ubuntu-24.04 + needs: + - tag + - macos_release + - release + steps: + - uses: actions/checkout@v4 + + - name: Get version info + id: version_info + run: | + echo "build_version=${{ env.RYUJINX_BASE_VERSION }}.${{ github.run_number }}" >> $GITHUB_OUTPUT + echo "prev_build_version=${{ env.RYUJINX_BASE_VERSION }}.$((${{ github.run_number }} - 1))" >> $GITHUB_OUTPUT + echo "git_short_hash=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT + shell: bash + + - name: Install GitLabCli + run: | + mkdir -p $HOME/.bin + gh release download -R GreemDev/GLI -O gli -p 'GitLabCli-linux_x64' + chmod +x gli + mv gli $HOME/.bin/ + echo "$HOME/.bin" >> $GITHUB_PATH + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create release + run: | + gli --access-token=${{ secrets.GITLAB_TOKEN }} --project=ryubing/ryujinx --command=CreateReleaseFromGenericPackageFiles "Ryubing|${{ steps.version_info.outputs.build_version }}|${{ steps.version_info.outputs.git_short_hash }}|${{ steps.version_info.outputs.build_version }}|**[Full Changelog](https://git.ryujinx.app/ryubing/ryujinx/-/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }})**" diff --git a/src/Ryujinx.Common/ReleaseInformation.cs b/src/Ryujinx.Common/ReleaseInformation.cs index 66971364c..a5beb1009 100644 --- a/src/Ryujinx.Common/ReleaseInformation.cs +++ b/src/Ryujinx.Common/ReleaseInformation.cs @@ -32,59 +32,11 @@ namespace Ryujinx.Common public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute()?.InformationalVersion; - public static string GetChangelogUrl(Version currentVersion, Version newVersion, ReleaseChannels.Channel releaseChannel) => + public static string GetChangelogUrl(Version currentVersion, Version newVersion) => IsCanaryBuild ? $"https://git.ryujinx.app/ryubing/ryujinx/-/compare/Canary-{currentVersion}...Canary-{newVersion}" - : GetChangelogForVersion(newVersion, releaseChannel); - - public static string GetChangelogForVersion(Version version, ReleaseChannels.Channel releaseChannel) => - $"https://github.com/{releaseChannel}/releases/{version}"; - - public static async Task GetReleaseChannelsAsync(HttpClient httpClient) - { - ReleaseChannelPair releaseChannelPair = await httpClient.GetFromJsonAsync("https://ryujinx.app/api/release-channels", ReleaseChannelPairContext.Default.ReleaseChannelPair); - return new ReleaseChannels(releaseChannelPair); - } + : $"https://git.ryujinx.app/ryubing/ryujinx/-/releases/{newVersion}"; } - public readonly struct ReleaseChannels - { - internal ReleaseChannels(ReleaseChannelPair channelPair) - { - Stable = new Channel(channelPair.Stable); - Canary = new Channel(channelPair.Canary); - } - public readonly Channel Stable; - public readonly Channel Canary; - - public readonly struct Channel - { - public Channel(string raw) - { - string[] parts = raw.Split('/'); - Owner = parts[0]; - Repo = parts[1]; - } - - public readonly string Owner; - public readonly string Repo; - - public override string ToString() => $"{Owner}/{Repo}"; - - public string GetLatestReleaseApiUrl() => - $"https://api.github.com/repos/{ToString()}/releases/latest"; - } - } - - [JsonSerializable(typeof(ReleaseChannelPair))] - partial class ReleaseChannelPairContext : JsonSerializerContext; - - class ReleaseChannelPair - { - [JsonPropertyName("stable")] - public string Stable { get; set; } - [JsonPropertyName("canary")] - public string Canary { get; set; } - } } diff --git a/src/Ryujinx/Common/Models/GitLab/GitLabReleaseAssetJsonResponse.cs b/src/Ryujinx/Common/Models/GitLab/GitLabReleaseAssetJsonResponse.cs new file mode 100644 index 000000000..a5b4bb619 --- /dev/null +++ b/src/Ryujinx/Common/Models/GitLab/GitLabReleaseAssetJsonResponse.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Ava.Common.Models.GitLab +{ + public class GitLabReleaseAssetJsonResponse + { + [JsonPropertyName("links")] + public GitLabReleaseAssetLinkJsonResponse[] Links { get; set; } + + public class GitLabReleaseAssetLinkJsonResponse + { + [JsonPropertyName("id")] + public long Id { get; set; } + [JsonPropertyName("name")] + public string AssetName { get; set; } + [JsonPropertyName("url")] + public string Url { get; set; } + } + } +} diff --git a/src/Ryujinx/Common/Models/GitLab/GitLabReleasesJsonResponse.cs b/src/Ryujinx/Common/Models/GitLab/GitLabReleasesJsonResponse.cs new file mode 100644 index 000000000..8d229a5f1 --- /dev/null +++ b/src/Ryujinx/Common/Models/GitLab/GitLabReleasesJsonResponse.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Ryujinx.Ava.Common.Models.GitLab +{ + public class GitLabReleasesJsonResponse + { + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("tag_name")] + public string TagName { get; set; } + + [JsonPropertyName("assets")] + public GitLabReleaseAssetJsonResponse Assets { get; set; } + } + + [JsonSerializable(typeof(GitLabReleasesJsonResponse), GenerationMode = JsonSourceGenerationMode.Metadata)] + public partial class GitLabReleasesJsonSerializerContext : JsonSerializerContext; +} diff --git a/src/Ryujinx/Systems/Updater/Updater.GitHub.cs b/src/Ryujinx/Systems/Updater/Updater.GitHub.cs new file mode 100644 index 000000000..9d67e5351 --- /dev/null +++ b/src/Ryujinx/Systems/Updater/Updater.GitHub.cs @@ -0,0 +1,190 @@ +using Gommon; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Common.Models.Github; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common; +using Ryujinx.Common.Helper; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using System; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.Systems +{ + internal static partial class Updater + { + private static GitHubReleaseChannels.Channel? _currentGitHubReleaseChannel; + + private static async Task> CheckGitHubVersionAsync(bool showVersionUpToDate = false) + { + if (!Version.TryParse(Program.Version, out Version currentVersion)) + { + Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!"); + + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + + _running = false; + + return default; + } + + Logger.Info?.Print(LogClass.Application, "Checking for updates from GitHub."); + + // Get latest version number from GitHub API + try + { + using HttpClient jsonClient = ConstructHttpClient(); + + if (_currentGitHubReleaseChannel == null) + { + GitHubReleaseChannels releaseChannels = await GitHubReleaseChannels.GetAsync(jsonClient); + + _currentGitHubReleaseChannel = ReleaseInformation.IsCanaryBuild + ? releaseChannels.Canary + : releaseChannels.Stable; + + Logger.Info?.Print(LogClass.Application, $"Loaded GitHub release channel for '{(ReleaseInformation.IsCanaryBuild ? "canary" : "stable")}'"); + + _changelogUrlFormat = _currentGitHubReleaseChannel.Value.UrlFormat; + } + + string fetchedJson = await jsonClient.GetStringAsync(_currentGitHubReleaseChannel.Value.GetLatestReleaseApiUrl()); + GithubReleasesJsonResponse fetched = JsonHelper.Deserialize(fetchedJson, _ghSerializerContext.GithubReleasesJsonResponse); + _buildVer = fetched.TagName; + + foreach (GithubReleaseAssetJsonResponse asset in fetched.Assets) + { + if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt)) + { + _buildUrl = asset.BrowserDownloadUrl; + + if (asset.State != "uploaded") + { + if (showVersionUpToDate) + { + UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + string.Empty); + + if (userResult is UserResult.Ok) + { + OpenHelper.OpenUrl(_changelogUrlFormat.Format(currentVersion)); + } + } + + Logger.Info?.Print(LogClass.Application, "Up to date."); + + _running = false; + + return default; + } + + break; + } + } + + // If build not done, assume no new update are available. + if (_buildUrl is null) + { + if (showVersionUpToDate) + { + UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + string.Empty); + + if (userResult is UserResult.Ok) + { + OpenHelper.OpenUrl(_changelogUrlFormat.Format(currentVersion)); + } + } + + Logger.Info?.Print(LogClass.Application, "Up to date."); + + _running = false; + + return default; + } + } + catch (Exception exception) + { + Logger.Error?.Print(LogClass.Application, exception.Message); + + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); + + _running = false; + + return default; + } + + if (!Version.TryParse(_buildVer, out Version newVersion)) + { + Logger.Error?.Print(LogClass.Application, $"Failed to convert the received {RyujinxApp.FullAppName} version from GitHub!"); + + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + + _running = false; + + return default; + } + + return (currentVersion, newVersion); + } + } + + public readonly struct GitHubReleaseChannels + { + public static async Task GetAsync(HttpClient httpClient) + { + ReleaseChannelPair releaseChannelPair = await httpClient.GetFromJsonAsync("https://ryujinx.app/api/release-channels", ReleaseChannelPairContext.Default.ReleaseChannelPair); + return new GitHubReleaseChannels(releaseChannelPair); + } + + internal GitHubReleaseChannels(ReleaseChannelPair channelPair) + { + Stable = new Channel(channelPair.Stable); + Canary = new Channel(channelPair.Canary); + } + + public readonly Channel Stable; + public readonly Channel Canary; + + public readonly struct Channel + { + public Channel(string raw) + { + string[] parts = raw.Split('/'); + Owner = parts[0]; + Repo = parts[1]; + } + + public readonly string Owner; + public readonly string Repo; + + public string UrlFormat => $"https://github.com/{ToString()}/releases/{{0}}"; + + public override string ToString() => $"{Owner}/{Repo}"; + + public string GetLatestReleaseApiUrl() => + $"https://api.github.com/repos/{ToString()}/releases/latest"; + } + } + + [JsonSerializable(typeof(ReleaseChannelPair))] + partial class ReleaseChannelPairContext : JsonSerializerContext; + + class ReleaseChannelPair + { + [JsonPropertyName("stable")] + public string Stable { get; set; } + [JsonPropertyName("canary")] + public string Canary { get; set; } + } +} diff --git a/src/Ryujinx/Systems/Updater/Updater.GitLab.cs b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs new file mode 100644 index 000000000..6e5e914e0 --- /dev/null +++ b/src/Ryujinx/Systems/Updater/Updater.GitLab.cs @@ -0,0 +1,138 @@ +using Gommon; +using Ryujinx.Ava.Common.Locale; +using Ryujinx.Ava.Common.Models.GitLab; +using Ryujinx.Ava.UI.Helpers; +using Ryujinx.Common; +using Ryujinx.Common.Helper; +using Ryujinx.Common.Logging; +using Ryujinx.Common.Utilities; +using System; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using System.Threading.Tasks; + +namespace Ryujinx.Ava.Systems +{ + internal static partial class Updater + { + private static GitLabReleaseChannels.ChannelType _currentGitLabReleaseChannel; + + private static async Task> CheckGitLabVersionAsync(bool showVersionUpToDate = false) + { + if (!Version.TryParse(Program.Version, out Version currentVersion)) + { + Logger.Error?.Print(LogClass.Application, + $"Failed to convert the current {RyujinxApp.FullAppName} version!"); + + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + + _running = false; + + return default; + } + + Logger.Info?.Print(LogClass.Application, "Checking for updates from https://git.ryujinx.app."); + + // Get latest version number from GitLab API + using HttpClient jsonClient = ConstructHttpClient(); + + // GitLab instance is located in Ukraine. Connection times will vary across the world. + jsonClient.Timeout = TimeSpan.FromSeconds(10); + + if (_currentGitLabReleaseChannel == null) + { + GitLabReleaseChannels releaseChannels = await GitLabReleaseChannels.GetAsync(jsonClient); + + _currentGitLabReleaseChannel = ReleaseInformation.IsCanaryBuild + ? releaseChannels.Canary + : releaseChannels.Stable; + + Logger.Info?.Print(LogClass.Application, $"Loaded GitLab release channel for '{(ReleaseInformation.IsCanaryBuild ? "canary" : "stable")}'"); + + _changelogUrlFormat = _currentGitLabReleaseChannel.UrlFormat; + } + + string fetchedJson = await jsonClient.GetStringAsync(_currentGitLabReleaseChannel.GetLatestReleaseApiUrl()); + GitLabReleasesJsonResponse fetched = JsonHelper.Deserialize(fetchedJson, _glSerializerContext.GitLabReleasesJsonResponse); + + _buildVer = fetched.TagName; + _buildUrl = fetched.Assets.Links + .FirstOrDefault(link => + link.AssetName.StartsWith("ryujinx") && link.AssetName.EndsWith(_platformExt) + )?.Url; + + // If build URL not found, assume no new update are available. + if (_buildUrl is null) + { + if (showVersionUpToDate) + { + UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + string.Empty); + + if (userResult is UserResult.Ok) + { + OpenHelper.OpenUrl(_changelogUrlFormat.Format(currentVersion)); + } + } + + Logger.Info?.Print(LogClass.Application, "Up to date."); + + _running = false; + + return default; + } + + + if (!Version.TryParse(_buildVer, out Version newVersion)) + { + Logger.Error?.Print(LogClass.Application, + $"Failed to convert the received {RyujinxApp.FullAppName} version from GitLab!"); + + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); + + _running = false; + + return default; + } + + return (currentVersion, newVersion); + } + + [JsonSerializable(typeof(GitLabReleaseChannels))] + partial class GitLabReleaseChannelPairContext : JsonSerializerContext; + + public class GitLabReleaseChannels + { + public static async Task GetAsync(HttpClient httpClient) + => await httpClient.GetFromJsonAsync( + "https://git.ryujinx.app/ryubing/ryujinx/-/snippets/1/raw/main/meta.json", + GitLabReleaseChannelPairContext.Default.GitLabReleaseChannels); + + [JsonPropertyName("stable")] public ChannelType Stable { get; set; } + [JsonPropertyName("canary")] public ChannelType Canary { get; set; } + + public class ChannelType + { + [JsonPropertyName("id")] public long Id { get; set; } + + [JsonPropertyName("group")] public string Group { get; set; } + + [JsonPropertyName("project")] public string Project { get; set; } + + public string UrlFormat => $"https://git.ryujinx.app/{ToString()}/-/releases/{{0}}"; + + public override string ToString() => $"{Group}/{Project}"; + + public string GetLatestReleaseApiUrl() => + $"https://git.ryujinx.app/api/v4/{Id}/releases/permalink/latest"; + } + } + } +} diff --git a/src/Ryujinx/Systems/Updater.cs b/src/Ryujinx/Systems/Updater/Updater.cs similarity index 83% rename from src/Ryujinx/Systems/Updater.cs rename to src/Ryujinx/Systems/Updater/Updater.cs index b74b6eaa8..c4e8a8cc6 100644 --- a/src/Ryujinx/Systems/Updater.cs +++ b/src/Ryujinx/Systems/Updater/Updater.cs @@ -6,6 +6,7 @@ using ICSharpCode.SharpZipLib.Tar; using ICSharpCode.SharpZipLib.Zip; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.Common.Models.Github; +using Ryujinx.Ava.Common.Models.GitLab; using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.Utilities; using Ryujinx.Common; @@ -29,13 +30,11 @@ using System.Threading.Tasks; namespace Ryujinx.Ava.Systems { - internal static class Updater + internal static partial class Updater { - private static ReleaseChannels.Channel? _currentReleaseChannel; - - private const string GitHubApiUrl = "https://api.github.com"; - - private static readonly GithubReleasesJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + private static readonly GithubReleasesJsonSerializerContext _ghSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + private static readonly GitLabReleasesJsonSerializerContext _glSerializerContext = new(JsonHelper.GetDefaultSerializerOptions()); + private static readonly string _homeDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update"); @@ -53,123 +52,28 @@ namespace Ryujinx.Ava.Systems private static bool _running; private static readonly string[] _windowsDependencyDirs = []; + + private static string _changelogUrlFormat = null; - public static async Task> CheckVersionAsync(bool showVersionUpToDate = false) + public static async Task> CheckForUpdateAsync(bool showVersionUpToDate = false) { - if (!Version.TryParse(Program.Version, out Version currentVersion)) - { - Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!"); + Optional<(Version, Version)> versionTuple; - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - - _running = false; - - return default; - } - - Logger.Info?.Print(LogClass.Application, "Checking for updates."); - - // Get latest version number from GitHub API try { - using HttpClient jsonClient = ConstructHttpClient(); - - if (_currentReleaseChannel == null) - { - ReleaseChannels releaseChannels = await ReleaseInformation.GetReleaseChannelsAsync(jsonClient); - - _currentReleaseChannel = ReleaseInformation.IsCanaryBuild - ? releaseChannels.Canary - : releaseChannels.Stable; - } - - string fetchedJson = await jsonClient.GetStringAsync(_currentReleaseChannel.Value.GetLatestReleaseApiUrl()); - GithubReleasesJsonResponse fetched = JsonHelper.Deserialize(fetchedJson, _serializerContext.GithubReleasesJsonResponse); - _buildVer = fetched.TagName; - - foreach (GithubReleaseAssetJsonResponse asset in fetched.Assets) - { - if (asset.Name.StartsWith("ryujinx") && asset.Name.EndsWith(_platformExt)) - { - _buildUrl = asset.BrowserDownloadUrl; - - if (asset.State != "uploaded") - { - if (showVersionUpToDate) - { - UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - string.Empty); - - if (userResult is UserResult.Ok) - { - OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value)); - } - } - - Logger.Info?.Print(LogClass.Application, "Up to date."); - - _running = false; - - return default; - } - - break; - } - } - - // If build not done, assume no new update are available. - if (_buildUrl is null) - { - if (showVersionUpToDate) - { - UserResult userResult = await ContentDialogHelper.CreateUpdaterUpToDateInfoDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], - string.Empty); - - if (userResult is UserResult.Ok) - { - OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value)); - } - } - - Logger.Info?.Print(LogClass.Application, "Up to date."); - - _running = false; - - return default; - } + versionTuple = await CheckGitLabVersionAsync(showVersionUpToDate); } - catch (Exception exception) + catch (Exception e) { - Logger.Error?.Print(LogClass.Application, exception.Message); - - await ContentDialogHelper.CreateErrorDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); - - _running = false; - - return default; + Logger.Error?.PrintMsg(LogClass.Application, "Update checking from GitLab failed; falling back to GitHub."); + Logger.Error?.PrintMsg(LogClass.Application, e.Message); + versionTuple = await CheckGitHubVersionAsync(showVersionUpToDate); } - if (!Version.TryParse(_buildVer, out Version newVersion)) - { - Logger.Error?.Print(LogClass.Application, $"Failed to convert the received {RyujinxApp.FullAppName} version from GitHub!"); - - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - - _running = false; - - return default; - } - - return (currentVersion, newVersion); + return versionTuple; } - + + public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) { if (_running) @@ -179,7 +83,7 @@ namespace Ryujinx.Ava.Systems _running = true; - Optional<(Version, Version)> versionTuple = await CheckVersionAsync(showVersionUpToDate); + Optional<(Version, Version)> versionTuple = await CheckForUpdateAsync(showVersionUpToDate); if (_running is false || !versionTuple.HasValue) return; @@ -196,7 +100,7 @@ namespace Ryujinx.Ava.Systems if (userResult is UserResult.Ok) { - OpenHelper.OpenUrl(ReleaseInformation.GetChangelogForVersion(currentVersion, _currentReleaseChannel.Value)); + OpenHelper.OpenUrl(_changelogUrlFormat.Format(currentVersion)); } } @@ -212,6 +116,9 @@ namespace Ryujinx.Ava.Systems try { buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0"); + + // GitLab instance is located in Ukraine. Connection times will vary across the world. + buildSizeClient.Timeout = TimeSpan.FromSeconds(10); HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead); @@ -247,7 +154,7 @@ namespace Ryujinx.Ava.Systems break; // Secondary button maps to no, which in this case is the show changelog button. case UserResult.No: - OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion, _currentReleaseChannel.Value)); + OpenHelper.OpenUrl(ReleaseInformation.GetChangelogUrl(currentVersion, newVersion)); goto RequestUserToUpdate; default: _running = false; diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index a2d7ff657..23a3f26f9 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -419,7 +419,7 @@ namespace Ryujinx.Ava.UI.Windows .Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}")); break; case UpdaterType.CheckInBackground: - if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions)) + if ((await Updater.CheckForUpdateAsync()).TryGet(out (Version Current, Version Incoming) versions)) { Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.ViewModel.UpdateAvailable = versions.Current < versions.Incoming); }