UpdateChecker.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. using System;
  2. using System.Globalization;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Net.Http;
  6. using System.Text.Json;
  7. using System.Threading.Tasks;
  8. namespace PixiEditor.UpdateModule;
  9. public class UpdateChecker
  10. {
  11. public UpdateChecker(string currentVersionTag, UpdateChannel channel)
  12. {
  13. CurrentVersionTag = currentVersionTag;
  14. Channel = channel;
  15. }
  16. public ReleaseInfo LatestReleaseInfo { get; private set; }
  17. private UpdateChannel _channel;
  18. public UpdateChannel Channel
  19. {
  20. get => _channel;
  21. set
  22. {
  23. bool changed = _channel != value;
  24. if (changed)
  25. {
  26. _channel = value;
  27. LatestReleaseInfo = null;
  28. }
  29. }
  30. }
  31. public string CurrentVersionTag { get; }
  32. /// <summary>
  33. /// Compares version strings and returns true if newVer > originalVer.
  34. /// </summary>
  35. /// <param name="originalVer">Version to compare.</param>
  36. /// <param name="newVer">Version to compare with.</param>
  37. /// <returns>True if semantic version is higher.</returns>
  38. public static bool VersionDifferent(string originalVer, string newVer)
  39. {
  40. return ExtractVersionString(originalVer) != ExtractVersionString(newVer);
  41. }
  42. /// <summary>
  43. /// Checks if originalVer is smaller than newVer
  44. /// </summary>
  45. /// <param name="originalVer">Version on the left side of the equation</param>
  46. /// <param name="newVer">Version to compare to</param>
  47. /// <returns>True if originalVer is smaller than newVer.</returns>
  48. public static bool VersionSmaller(string originalVer, string newVer)
  49. {
  50. string normalizedOriginal = ExtractVersionString(originalVer);
  51. string normalizedNew = ExtractVersionString(newVer);
  52. if (normalizedOriginal == normalizedNew) return false;
  53. bool parsed = TryParseToFloatVersion(normalizedOriginal, out float orgFloat);
  54. if (!parsed) throw new Exception($"Couldn't parse version {originalVer} to float.");
  55. parsed = TryParseToFloatVersion(normalizedNew, out float newFloat);
  56. if (!parsed) throw new Exception($"Couldn't parse version {newVer} to float.");
  57. return orgFloat < newFloat;
  58. }
  59. private static bool TryParseToFloatVersion(string normalizedString, out float ver)
  60. {
  61. if (string.IsNullOrEmpty(normalizedString))
  62. {
  63. ver = 0;
  64. return false;
  65. }
  66. return float.TryParse(normalizedString.Replace(".", string.Empty).Insert(1, "."), NumberStyles.Any, CultureInfo.InvariantCulture, out ver);
  67. }
  68. public async Task<bool> CheckUpdateAvailable()
  69. {
  70. LatestReleaseInfo = await GetLatestReleaseInfoAsync(Channel.ApiUrl);
  71. return CheckUpdateAvailable(LatestReleaseInfo);
  72. }
  73. public bool CheckUpdateAvailable(ReleaseInfo latestRelease)
  74. {
  75. if (latestRelease == null || string.IsNullOrEmpty(latestRelease.TagName)) return false;
  76. if (CurrentVersionTag == null) return false;
  77. return latestRelease.WasDataFetchSuccessful && VersionDifferent(CurrentVersionTag, latestRelease.TagName);
  78. }
  79. public bool IsUpdateCompatible(string[] incompatibleVersions)
  80. {
  81. return !incompatibleVersions.Select(x => x.Trim()).Contains(ExtractVersionString(CurrentVersionTag));
  82. }
  83. public async Task<bool> IsUpdateCompatible()
  84. {
  85. string[] incompatibleVersions = await GetUpdateIncompatibleVersionsAsync(LatestReleaseInfo.TagName);
  86. bool isDowngrading = VersionSmaller(LatestReleaseInfo.TagName, CurrentVersionTag);
  87. return IsUpdateCompatible(incompatibleVersions) && !isDowngrading; // Incompatible.json doesn't support backwards compatibility, thus downgrading always means update is not compatble
  88. }
  89. public async Task<string[]> GetUpdateIncompatibleVersionsAsync(string tag)
  90. {
  91. using (HttpClient client = new HttpClient())
  92. {
  93. client.DefaultRequestHeaders.Add("User-Agent", "PixiEditor");
  94. HttpResponseMessage response = await client.GetAsync(string.Format(Channel.IncompatibleFileApiUrl, tag));
  95. if (response.StatusCode == HttpStatusCode.OK)
  96. {
  97. string content = await response.Content.ReadAsStringAsync();
  98. return JsonSerializer.Deserialize<string[]>(content);
  99. }
  100. }
  101. return Array.Empty<string>();
  102. }
  103. private static async Task<ReleaseInfo> GetLatestReleaseInfoAsync(string apiUrl)
  104. {
  105. using (HttpClient client = new HttpClient())
  106. {
  107. client.DefaultRequestHeaders.Add("User-Agent", "PixiEditor");
  108. HttpResponseMessage response = await client.GetAsync(apiUrl);
  109. if (response.StatusCode == HttpStatusCode.OK)
  110. {
  111. string content = await response.Content.ReadAsStringAsync();
  112. return JsonSerializer.Deserialize<ReleaseInfo>(content);
  113. }
  114. }
  115. return new ReleaseInfo(false);
  116. }
  117. private static string ExtractVersionString(string versionString)
  118. {
  119. if (string.IsNullOrEmpty(versionString)) return string.Empty;
  120. for (int i = 0; i < versionString.Length; i++)
  121. {
  122. if (!char.IsDigit(versionString[i]) && versionString[i] != '.')
  123. {
  124. return versionString[..i];
  125. }
  126. }
  127. return versionString;
  128. }
  129. }