You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

UpdateChecker.cs 7.8 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Net.Http;
  5. using System.Reflection;
  6. using System.Text.Json;
  7. using System.Threading.Tasks;
  8. using System.Windows;
  9. using NLog;
  10. using Shadowsocks.WPF.Localization;
  11. using Shadowsocks.WPF.Models;
  12. using Shadowsocks.WPF.ViewModels;
  13. using Shadowsocks.WPF.Views;
  14. using Splat;
  15. namespace Shadowsocks.WPF.Services
  16. {
  17. public class UpdateChecker : IEnableLogger
  18. {
  19. private readonly Logger logger;
  20. private readonly HttpClient httpClient;
  21. // https://developer.github.com/v3/repos/releases/
  22. private const string UpdateURL = "https://api.github.com/repos/shadowsocks/shadowsocks-windows/releases";
  23. private Window? versionUpdatePromptWindow;
  24. private JsonElement _releaseObject;
  25. public string NewReleaseVersion { get; private set; }
  26. public string NewReleaseZipFilename { get; private set; }
  27. public event EventHandler? CheckUpdateCompleted;
  28. public static readonly string Version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "5.0.0";
  29. private readonly Version _version;
  30. public UpdateChecker()
  31. {
  32. logger = LogManager.GetCurrentClassLogger();
  33. httpClient = Locator.Current.GetService<HttpClient>();
  34. _version = new Version(Version);
  35. NewReleaseVersion = "";
  36. NewReleaseZipFilename = "";
  37. }
  38. /// <summary>
  39. /// Checks for updates and asks the user if updates are found.
  40. /// </summary>
  41. /// <param name="millisecondsDelay">A delay in milliseconds before checking.</param>
  42. /// <returns></returns>
  43. public async Task CheckForVersionUpdate(int millisecondsDelay = 0)
  44. {
  45. // delay
  46. logger.Info($"Waiting for {millisecondsDelay}ms before checking for version update.");
  47. await Task.Delay(millisecondsDelay);
  48. // start
  49. logger.Info($"Checking for version update.");
  50. var appSettings = Locator.Current.GetService<AppSettings>();
  51. try
  52. {
  53. // list releases via API
  54. var releasesListJsonStream = await httpClient.GetStreamAsync(UpdateURL);
  55. // parse
  56. using (JsonDocument jsonDocument = await JsonDocument.ParseAsync(releasesListJsonStream))
  57. {
  58. var releasesList = jsonDocument.RootElement;
  59. foreach (var releaseObject in releasesList.EnumerateArray())
  60. {
  61. var releaseTagName = releaseObject.GetProperty("tag_name").GetString();
  62. var releaseVersion = new Version(releaseTagName ?? "5.0.0");
  63. var releaseIsPrerelease = releaseObject.GetProperty("prerelease").GetBoolean();
  64. if (releaseTagName == appSettings.SkippedUpdateVersion) // finished checking
  65. break;
  66. if (releaseVersion.CompareTo(_version) > 0 &&
  67. (!releaseIsPrerelease || appSettings.VersionUpdateCheckForPrerelease && releaseIsPrerelease)) // selected
  68. {
  69. logger.Info($"Found new version {releaseTagName}.");
  70. _releaseObject = releaseObject;
  71. NewReleaseVersion = releaseTagName ?? "";
  72. AskToUpdate(releaseObject);
  73. return;
  74. }
  75. }
  76. }
  77. logger.Info($"No new versions found.");
  78. CheckUpdateCompleted?.Invoke(this, new EventArgs());
  79. }
  80. catch (Exception e)
  81. {
  82. this.Log().Error(e, "An error occurred while checking for version updates.");
  83. }
  84. }
  85. /// <summary>
  86. /// Opens a window to show the update's information.
  87. /// </summary>
  88. /// <param name="releaseObject">The update release object.</param>
  89. private void AskToUpdate(JsonElement releaseObject)
  90. {
  91. if (versionUpdatePromptWindow == null)
  92. {
  93. var versionUpdatePromptView = new VersionUpdatePromptView()
  94. {
  95. ViewModel = new VersionUpdatePromptViewModel(releaseObject),
  96. };
  97. versionUpdatePromptWindow = new Window()
  98. {
  99. Title = LocalizationProvider.GetLocalizedValue<string>("VersionUpdate"),
  100. Height = 480,
  101. Width = 640,
  102. MinHeight = 480,
  103. MinWidth = 640,
  104. Content = versionUpdatePromptView,
  105. };
  106. versionUpdatePromptWindow.Closed += VersionUpdatePromptWindow_Closed;
  107. versionUpdatePromptWindow.Show();
  108. }
  109. versionUpdatePromptWindow.Activate();
  110. }
  111. private void VersionUpdatePromptWindow_Closed(object? sender, EventArgs e)
  112. {
  113. versionUpdatePromptWindow = null;
  114. }
  115. /// <summary>
  116. /// Downloads the selected update and notifies the user.
  117. /// </summary>
  118. /// <returns></returns>
  119. public async Task DoUpdate()
  120. {
  121. try
  122. {
  123. var assets = _releaseObject.GetProperty("assets");
  124. // download all assets
  125. foreach (var asset in assets.EnumerateArray())
  126. {
  127. var filename = asset.GetProperty("name").GetString();
  128. var browser_download_url = asset.GetProperty("browser_download_url").GetString();
  129. var response = await httpClient.GetAsync(browser_download_url);
  130. if (filename is string)
  131. {
  132. using (var downloadedFileStream = File.Create(Utils.Utilities.GetTempPath(filename)))
  133. await response.Content.CopyToAsync(downloadedFileStream);
  134. logger.Info($"Downloaded {filename}.");
  135. // store .zip filename
  136. if (filename.EndsWith(".zip"))
  137. NewReleaseZipFilename = filename;
  138. }
  139. }
  140. logger.Info("Finished downloading.");
  141. // notify user
  142. CloseVersionUpdatePromptWindow();
  143. Process.Start("explorer.exe", $"/select, \"{Utils.Utilities.GetTempPath(NewReleaseZipFilename)}\"");
  144. }
  145. catch (Exception e)
  146. {
  147. this.Log().Error(e, "An error occurred while downloading the version update.");
  148. }
  149. }
  150. /// <summary>
  151. /// Saves the skipped update version.
  152. /// </summary>
  153. public void SkipUpdate()
  154. {
  155. var appSettings = Locator.Current.GetService<AppSettings>();
  156. if (_releaseObject.TryGetProperty("tag_name", out var tagNameJsonElement) && tagNameJsonElement.GetString() is string version)
  157. {
  158. appSettings.SkippedUpdateVersion = version;
  159. // TODO: signal settings change
  160. logger.Info($"The update {version} has been skipped and will be ignored next time.");
  161. }
  162. CloseVersionUpdatePromptWindow();
  163. }
  164. /// <summary>
  165. /// Closes the update prompt window.
  166. /// </summary>
  167. public void CloseVersionUpdatePromptWindow()
  168. {
  169. if (versionUpdatePromptWindow != null)
  170. {
  171. versionUpdatePromptWindow.Close();
  172. versionUpdatePromptWindow = null;
  173. }
  174. }
  175. }
  176. }