UpdateViewModel.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. using System.Collections.Generic;
  2. using System.ComponentModel;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading.Tasks;
  7. using Avalonia;
  8. using Avalonia.Controls.ApplicationLifetimes;
  9. using PixiEditor.AvaloniaUI.Helpers;
  10. using PixiEditor.AvaloniaUI.Models.Commands.Attributes.Commands;
  11. using PixiEditor.AvaloniaUI.Models.Dialogs;
  12. using PixiEditor.AvaloniaUI.Views.Dialogs;
  13. using PixiEditor.Extensions.Common.Localization;
  14. using PixiEditor.Extensions.CommonApi.UserPreferences;
  15. using PixiEditor.Platform;
  16. using PixiEditor.UpdateModule;
  17. namespace PixiEditor.AvaloniaUI.ViewModels.SubViewModels;
  18. internal class UpdateViewModel : SubViewModel<ViewModelMain>
  19. {
  20. private bool updateReadyToInstall = false;
  21. public UpdateChecker UpdateChecker { get; set; }
  22. public List<UpdateChannel> UpdateChannels { get; } = new List<UpdateChannel>();
  23. private string versionText;
  24. public string VersionText
  25. {
  26. get => versionText;
  27. set
  28. {
  29. versionText = value;
  30. OnPropertyChanged(nameof(VersionText));
  31. }
  32. }
  33. public bool UpdateReadyToInstall
  34. {
  35. get => updateReadyToInstall;
  36. set
  37. {
  38. updateReadyToInstall = value;
  39. OnPropertyChanged(nameof(UpdateReadyToInstall));
  40. if (value)
  41. {
  42. VersionText = new LocalizedString("TO_INSTALL_UPDATE", UpdateChecker.LatestReleaseInfo.TagName); // Button shows "Restart" before this text
  43. }
  44. }
  45. }
  46. public UpdateViewModel(ViewModelMain owner)
  47. : base(owner)
  48. {
  49. Owner.OnStartupEvent += Owner_OnStartupEvent;
  50. IPreferences.Current.AddCallback<string>("UpdateChannel", val =>
  51. {
  52. string prevChannel = UpdateChecker.Channel.ApiUrl;
  53. UpdateChecker.Channel = GetUpdateChannel(val);
  54. if (prevChannel != UpdateChecker.Channel.ApiUrl)
  55. {
  56. ConditionalUPDATE();
  57. }
  58. });
  59. InitUpdateChecker();
  60. }
  61. public async Task<bool> CheckForUpdate()
  62. {
  63. bool updateAvailable = await UpdateChecker.CheckUpdateAvailable();
  64. bool updateCompatible = await UpdateChecker.IsUpdateCompatible();
  65. bool updateFileDoesNotExists = !File.Exists(
  66. Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.zip"));
  67. bool updateExeDoesNotExists = !File.Exists(
  68. Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.exe"));
  69. if (updateAvailable && updateFileDoesNotExists && updateExeDoesNotExists)
  70. {
  71. UpdateReadyToInstall = false;
  72. VersionText = new LocalizedString("DOWNLOADING_UPDATE");
  73. try
  74. {
  75. if (updateCompatible)
  76. {
  77. await UpdateDownloader.DownloadReleaseZip(UpdateChecker.LatestReleaseInfo);
  78. }
  79. else
  80. {
  81. await UpdateDownloader.DownloadInstaller(UpdateChecker.LatestReleaseInfo);
  82. }
  83. UpdateReadyToInstall = true;
  84. }
  85. catch (IOException ex)
  86. {
  87. NoticeDialog.Show("FAILED_DOWNLOADING_TITLE", "FAILED_DOWNLOADING");
  88. return false;
  89. }
  90. catch(TaskCanceledException ex)
  91. {
  92. return false;
  93. }
  94. return true;
  95. }
  96. return false;
  97. }
  98. private void AskToInstall()
  99. {
  100. #if RELEASE || DEVRELEASE
  101. if (IPreferences.Current.GetPreference("CheckUpdatesOnStartup", true))
  102. {
  103. string dir = AppDomain.CurrentDomain.BaseDirectory;
  104. UpdateDownloader.CreateTempDirectory();
  105. if(UpdateChecker.LatestReleaseInfo == null || string.IsNullOrEmpty(UpdateChecker.LatestReleaseInfo.TagName)) return;
  106. bool updateFileExists = File.Exists(
  107. Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.zip"));
  108. string exePath = Path.Join(UpdateDownloader.DownloadLocation,
  109. $"update-{UpdateChecker.LatestReleaseInfo.TagName}.exe");
  110. bool updateExeExists = File.Exists(exePath);
  111. if (updateExeExists && !UpdateChecker.VersionDifferent(UpdateChecker.LatestReleaseInfo.TagName, UpdateChecker.CurrentVersionTag))
  112. {
  113. File.Delete(exePath);
  114. updateExeExists = false;
  115. }
  116. string updaterPath = Path.Join(dir, "PixiEditor.UpdateInstaller.exe");
  117. if (updateFileExists || updateExeExists)
  118. {
  119. ViewModelMain.Current.UpdateSubViewModel.UpdateReadyToInstall = true;
  120. var result = ConfirmationDialog.Show("UPDATE_READY", "NEW_UPDATE");
  121. result.Wait();
  122. if (result.Result == ConfirmationType.Yes)
  123. {
  124. if (updateFileExists && File.Exists(updaterPath))
  125. {
  126. InstallHeadless(updaterPath);
  127. }
  128. else if (updateExeExists)
  129. {
  130. OpenExeInstaller(exePath);
  131. }
  132. }
  133. }
  134. }
  135. #endif
  136. }
  137. private static void InstallHeadless(string updaterPath)
  138. {
  139. try
  140. {
  141. ProcessHelper.RunAsAdmin(updaterPath);
  142. Shutdown();
  143. }
  144. catch (Win32Exception)
  145. {
  146. NoticeDialog.Show(
  147. "COULD_NOT_UPDATE_WITHOUT_ADMIN",
  148. "INSUFFICIENT_PERMISSIONS");
  149. }
  150. }
  151. private static void OpenExeInstaller(string updateExeFile)
  152. {
  153. bool alreadyUpdated = VersionHelpers.GetCurrentAssemblyVersion().ToString() ==
  154. updateExeFile.Split('-')[1].Split(".exe")[0];
  155. if (!alreadyUpdated)
  156. {
  157. RestartToUpdate(updateExeFile);
  158. }
  159. else
  160. {
  161. File.Delete(updateExeFile);
  162. }
  163. }
  164. private static void RestartToUpdate(string updateExeFile)
  165. {
  166. Process.Start(updateExeFile);
  167. Shutdown();
  168. }
  169. private static void Shutdown()
  170. {
  171. if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  172. desktop.Shutdown();
  173. }
  174. [Command.Internal("PixiEditor.Restart")]
  175. public static void RestartApplication()
  176. {
  177. try
  178. {
  179. ProcessHelper.RunAsAdmin(Path.Join(AppDomain.CurrentDomain.BaseDirectory, "PixiEditor.UpdateInstaller.exe"));
  180. Shutdown();
  181. }
  182. catch (Win32Exception)
  183. {
  184. NoticeDialog.Show("COULD_NOT_UPDATE_WITHOUT_ADMIN", "INSUFFICIENT_PERMISSIONS");
  185. }
  186. }
  187. private void Owner_OnStartupEvent(object sender, EventArgs e)
  188. {
  189. ConditionalUPDATE();
  190. }
  191. [Conditional("UPDATE")]
  192. private async void ConditionalUPDATE()
  193. {
  194. if (IPreferences.Current.GetPreference("CheckUpdatesOnStartup", true))
  195. {
  196. try
  197. {
  198. await CheckForUpdate();
  199. }
  200. catch (System.Net.Http.HttpRequestException)
  201. {
  202. NoticeDialog.Show("COULD_NOT_CHECK_FOR_UPDATES", "UPDATE_CHECK_FAILED");
  203. }
  204. catch (Exception e)
  205. {
  206. CrashHelper.SendExceptionInfoToWebhookAsync(e);
  207. NoticeDialog.Show("COULD_NOT_CHECK_FOR_UPDATES", "UPDATE_CHECK_FAILED");
  208. }
  209. AskToInstall();
  210. }
  211. }
  212. private void InitUpdateChecker()
  213. {
  214. #if UPDATE
  215. UpdateChannels.Add(new UpdateChannel("Release", "PixiEditor", "PixiEditor"));
  216. UpdateChannels.Add(new UpdateChannel("Development", "PixiEditor", "PixiEditor-development-channel"));
  217. #else
  218. string platformName = IPlatform.Current.Name;
  219. UpdateChannels.Add(new UpdateChannel(platformName, "", ""));
  220. #endif
  221. string updateChannel = IPreferences.Current.GetPreference<string>("UpdateChannel");
  222. string version = VersionHelpers.GetCurrentAssemblyVersionString();
  223. UpdateChecker = new UpdateChecker(version, GetUpdateChannel(updateChannel));
  224. VersionText = new LocalizedString("VERSION", version);
  225. }
  226. private UpdateChannel GetUpdateChannel(string channelName)
  227. {
  228. UpdateChannel selectedChannel = UpdateChannels.FirstOrDefault(x => x.Name == channelName, UpdateChannels[0]);
  229. return selectedChannel;
  230. }
  231. }