Updater.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. using Avalonia.Controls;
  2. using Avalonia.Threading;
  3. using FluentAvalonia.UI.Controls;
  4. using ICSharpCode.SharpZipLib.GZip;
  5. using ICSharpCode.SharpZipLib.Tar;
  6. using ICSharpCode.SharpZipLib.Zip;
  7. using Newtonsoft.Json.Linq;
  8. using Ryujinx.Ava;
  9. using Ryujinx.Ava.Common.Locale;
  10. using Ryujinx.Ava.UI.Controls;
  11. using Ryujinx.Ava.UI.Helpers;
  12. using Ryujinx.Ava.UI.Windows;
  13. using Ryujinx.Common;
  14. using Ryujinx.Common.Logging;
  15. using Ryujinx.Ui.Common.Helper;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Diagnostics;
  19. using System.IO;
  20. using System.Linq;
  21. using System.Net;
  22. using System.Net.Http;
  23. using System.Net.NetworkInformation;
  24. using System.Runtime.CompilerServices;
  25. using System.Runtime.InteropServices;
  26. using System.Text;
  27. using System.Threading;
  28. using System.Threading.Tasks;
  29. namespace Ryujinx.Modules
  30. {
  31. internal static class Updater
  32. {
  33. private const string GitHubApiURL = "https://api.github.com";
  34. internal static bool Running;
  35. private static readonly string HomeDir = AppDomain.CurrentDomain.BaseDirectory;
  36. private static readonly string UpdateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
  37. private static readonly string UpdatePublishDir = Path.Combine(UpdateDir, "publish");
  38. private static readonly int ConnectionCount = 4;
  39. private static string _buildVer;
  40. private static string _platformExt;
  41. private static string _buildUrl;
  42. private static long _buildSize;
  43. private static readonly string[] WindowsDependencyDirs = Array.Empty<string>();
  44. public static bool UpdateSuccessful { get; private set; }
  45. public static async Task BeginParse(MainWindow mainWindow, bool showVersionUpToDate)
  46. {
  47. if (Running)
  48. {
  49. return;
  50. }
  51. Running = true;
  52. mainWindow.ViewModel.CanUpdate = false;
  53. // Detect current platform
  54. if (OperatingSystem.IsMacOS())
  55. {
  56. _platformExt = "osx_x64.zip";
  57. }
  58. else if (OperatingSystem.IsWindows())
  59. {
  60. _platformExt = "win_x64.zip";
  61. }
  62. else if (OperatingSystem.IsLinux())
  63. {
  64. _platformExt = "linux_x64.tar.gz";
  65. }
  66. Version newVersion;
  67. Version currentVersion;
  68. try
  69. {
  70. currentVersion = Version.Parse(Program.Version);
  71. }
  72. catch
  73. {
  74. Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!");
  75. Dispatcher.UIThread.Post(async () =>
  76. {
  77. await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
  78. });
  79. return;
  80. }
  81. // Get latest version number from GitHub API
  82. try
  83. {
  84. using (HttpClient jsonClient = ConstructHttpClient())
  85. {
  86. string buildInfoURL = $"{GitHubApiURL}/repos/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}/releases/latest";
  87. string fetchedJson = await jsonClient.GetStringAsync(buildInfoURL);
  88. JObject jsonRoot = JObject.Parse(fetchedJson);
  89. JToken assets = jsonRoot["assets"];
  90. _buildVer = (string)jsonRoot["name"];
  91. foreach (JToken asset in assets)
  92. {
  93. string assetName = (string)asset["name"];
  94. string assetState = (string)asset["state"];
  95. string downloadURL = (string)asset["browser_download_url"];
  96. if (assetName.StartsWith("test-ava-ryujinx") && assetName.EndsWith(_platformExt))
  97. {
  98. _buildUrl = downloadURL;
  99. if (assetState != "uploaded")
  100. {
  101. if (showVersionUpToDate)
  102. {
  103. Dispatcher.UIThread.Post(async () =>
  104. {
  105. await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
  106. });
  107. }
  108. return;
  109. }
  110. break;
  111. }
  112. }
  113. // If build not done, assume no new update are availaible.
  114. if (_buildUrl == null)
  115. {
  116. if (showVersionUpToDate)
  117. {
  118. Dispatcher.UIThread.Post(async () =>
  119. {
  120. await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
  121. });
  122. }
  123. return;
  124. }
  125. }
  126. }
  127. catch (Exception exception)
  128. {
  129. Logger.Error?.Print(LogClass.Application, exception.Message);
  130. Dispatcher.UIThread.Post(async () =>
  131. {
  132. await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]);
  133. });
  134. return;
  135. }
  136. try
  137. {
  138. newVersion = Version.Parse(_buildVer);
  139. }
  140. catch
  141. {
  142. Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!");
  143. Dispatcher.UIThread.Post(async () =>
  144. {
  145. await ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]);
  146. });
  147. return;
  148. }
  149. if (newVersion <= currentVersion)
  150. {
  151. if (showVersionUpToDate)
  152. {
  153. Dispatcher.UIThread.Post(async () =>
  154. {
  155. await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], "");
  156. });
  157. }
  158. Running = false;
  159. mainWindow.ViewModel.CanUpdate = true;
  160. return;
  161. }
  162. // Fetch build size information to learn chunk sizes.
  163. using (HttpClient buildSizeClient = ConstructHttpClient())
  164. {
  165. try
  166. {
  167. buildSizeClient.DefaultRequestHeaders.Add("Range", "bytes=0-0");
  168. HttpResponseMessage message = await buildSizeClient.GetAsync(new Uri(_buildUrl), HttpCompletionOption.ResponseHeadersRead);
  169. _buildSize = message.Content.Headers.ContentRange.Length.Value;
  170. }
  171. catch (Exception ex)
  172. {
  173. Logger.Warning?.Print(LogClass.Application, ex.Message);
  174. Logger.Warning?.Print(LogClass.Application, "Couldn't determine build size for update, using single-threaded updater");
  175. _buildSize = -1;
  176. }
  177. }
  178. Dispatcher.UIThread.Post(async () =>
  179. {
  180. // Show a message asking the user if they want to update
  181. var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
  182. LocaleManager.Instance[LocaleKeys.RyujinxUpdaterMessage],
  183. $"{Program.Version} -> {newVersion}");
  184. if (shouldUpdate)
  185. {
  186. UpdateRyujinx(mainWindow, _buildUrl);
  187. }
  188. });
  189. }
  190. private static HttpClient ConstructHttpClient()
  191. {
  192. HttpClient result = new HttpClient();
  193. // Required by GitHub to interract with APIs.
  194. result.DefaultRequestHeaders.Add("User-Agent", "Ryujinx-Updater/1.0.0");
  195. return result;
  196. }
  197. public static async void UpdateRyujinx(Window parent, string downloadUrl)
  198. {
  199. UpdateSuccessful = false;
  200. // Empty update dir, although it shouldn't ever have anything inside it
  201. if (Directory.Exists(UpdateDir))
  202. {
  203. Directory.Delete(UpdateDir, true);
  204. }
  205. Directory.CreateDirectory(UpdateDir);
  206. string updateFile = Path.Combine(UpdateDir, "update.bin");
  207. var taskDialog = new TaskDialog()
  208. {
  209. Header = LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
  210. SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterDownloading],
  211. IconSource = new SymbolIconSource { Symbol = Symbol.Download },
  212. Buttons = { },
  213. ShowProgressBar = true
  214. };
  215. taskDialog.XamlRoot = parent;
  216. taskDialog.Opened += (s, e) =>
  217. {
  218. if (_buildSize >= 0)
  219. {
  220. DoUpdateWithMultipleThreads(taskDialog, downloadUrl, updateFile);
  221. }
  222. else
  223. {
  224. DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
  225. }
  226. };
  227. await taskDialog.ShowAsync(true);
  228. if (UpdateSuccessful)
  229. {
  230. var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
  231. LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
  232. LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
  233. if (shouldRestart)
  234. {
  235. string ryuName = Path.GetFileName(Environment.ProcessPath);
  236. string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
  237. if (!Path.Exists(ryuExe))
  238. {
  239. ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
  240. }
  241. SetFileExecutable(ryuExe);
  242. Process.Start(ryuExe, CommandLineState.Arguments);
  243. Environment.Exit(0);
  244. }
  245. }
  246. }
  247. private static void DoUpdateWithMultipleThreads(TaskDialog taskDialog, string downloadUrl, string updateFile)
  248. {
  249. // Multi-Threaded Updater
  250. long chunkSize = _buildSize / ConnectionCount;
  251. long remainderChunk = _buildSize % ConnectionCount;
  252. int completedRequests = 0;
  253. int totalProgressPercentage = 0;
  254. int[] progressPercentage = new int[ConnectionCount];
  255. List<byte[]> list = new List<byte[]>(ConnectionCount);
  256. List<WebClient> webClients = new List<WebClient>(ConnectionCount);
  257. for (int i = 0; i < ConnectionCount; i++)
  258. {
  259. list.Add(Array.Empty<byte>());
  260. }
  261. for (int i = 0; i < ConnectionCount; i++)
  262. {
  263. #pragma warning disable SYSLIB0014
  264. // TODO: WebClient is obsolete and need to be replaced with a more complex logic using HttpClient.
  265. using (WebClient client = new WebClient())
  266. #pragma warning restore SYSLIB0014
  267. {
  268. webClients.Add(client);
  269. if (i == ConnectionCount - 1)
  270. {
  271. client.Headers.Add("Range", $"bytes={chunkSize * i}-{(chunkSize * (i + 1) - 1) + remainderChunk}");
  272. }
  273. else
  274. {
  275. client.Headers.Add("Range", $"bytes={chunkSize * i}-{chunkSize * (i + 1) - 1}");
  276. }
  277. client.DownloadProgressChanged += (_, args) =>
  278. {
  279. int index = (int)args.UserState;
  280. Interlocked.Add(ref totalProgressPercentage, -1 * progressPercentage[index]);
  281. Interlocked.Exchange(ref progressPercentage[index], args.ProgressPercentage);
  282. Interlocked.Add(ref totalProgressPercentage, args.ProgressPercentage);
  283. taskDialog.SetProgressBarState(totalProgressPercentage / ConnectionCount, TaskDialogProgressState.Normal);
  284. };
  285. client.DownloadDataCompleted += (_, args) =>
  286. {
  287. int index = (int)args.UserState;
  288. if (args.Cancelled)
  289. {
  290. webClients[index].Dispose();
  291. taskDialog.Hide();
  292. return;
  293. }
  294. list[index] = args.Result;
  295. Interlocked.Increment(ref completedRequests);
  296. if (Equals(completedRequests, ConnectionCount))
  297. {
  298. byte[] mergedFileBytes = new byte[_buildSize];
  299. for (int connectionIndex = 0, destinationOffset = 0; connectionIndex < ConnectionCount; connectionIndex++)
  300. {
  301. Array.Copy(list[connectionIndex], 0, mergedFileBytes, destinationOffset, list[connectionIndex].Length);
  302. destinationOffset += list[connectionIndex].Length;
  303. }
  304. File.WriteAllBytes(updateFile, mergedFileBytes);
  305. try
  306. {
  307. InstallUpdate(taskDialog, updateFile);
  308. }
  309. catch (Exception e)
  310. {
  311. Logger.Warning?.Print(LogClass.Application, e.Message);
  312. Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
  313. DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
  314. return;
  315. }
  316. }
  317. };
  318. try
  319. {
  320. client.DownloadDataAsync(new Uri(downloadUrl), i);
  321. }
  322. catch (WebException ex)
  323. {
  324. Logger.Warning?.Print(LogClass.Application, ex.Message);
  325. Logger.Warning?.Print(LogClass.Application, "Multi-Threaded update failed, falling back to single-threaded updater.");
  326. for (int j = 0; j < webClients.Count; j++)
  327. {
  328. webClients[j].CancelAsync();
  329. }
  330. DoUpdateWithSingleThread(taskDialog, downloadUrl, updateFile);
  331. return;
  332. }
  333. }
  334. }
  335. }
  336. private static void DoUpdateWithSingleThreadWorker(TaskDialog taskDialog, string downloadUrl, string updateFile)
  337. {
  338. using (HttpClient client = new HttpClient())
  339. {
  340. // We do not want to timeout while downloading
  341. client.Timeout = TimeSpan.FromDays(1);
  342. using (HttpResponseMessage response = client.GetAsync(downloadUrl, HttpCompletionOption.ResponseHeadersRead).Result)
  343. using (Stream remoteFileStream = response.Content.ReadAsStreamAsync().Result)
  344. {
  345. using (Stream updateFileStream = File.Open(updateFile, FileMode.Create))
  346. {
  347. long totalBytes = response.Content.Headers.ContentLength.Value;
  348. long byteWritten = 0;
  349. byte[] buffer = new byte[32 * 1024];
  350. while (true)
  351. {
  352. int readSize = remoteFileStream.Read(buffer);
  353. if (readSize == 0)
  354. {
  355. break;
  356. }
  357. byteWritten += readSize;
  358. taskDialog.SetProgressBarState(GetPercentage(byteWritten, totalBytes), TaskDialogProgressState.Normal);
  359. updateFileStream.Write(buffer, 0, readSize);
  360. }
  361. }
  362. }
  363. InstallUpdate(taskDialog, updateFile);
  364. }
  365. }
  366. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  367. private static double GetPercentage(double value, double max)
  368. {
  369. return max == 0 ? 0 : value / max * 100;
  370. }
  371. private static void DoUpdateWithSingleThread(TaskDialog taskDialog, string downloadUrl, string updateFile)
  372. {
  373. Thread worker = new Thread(() => DoUpdateWithSingleThreadWorker(taskDialog, downloadUrl, updateFile));
  374. worker.Name = "Updater.SingleThreadWorker";
  375. worker.Start();
  376. }
  377. private static void SetFileExecutable(string path)
  378. {
  379. const UnixFileMode ExecutableFileMode = UnixFileMode.UserExecute |
  380. UnixFileMode.UserWrite |
  381. UnixFileMode.UserRead |
  382. UnixFileMode.GroupRead |
  383. UnixFileMode.GroupWrite |
  384. UnixFileMode.OtherRead |
  385. UnixFileMode.OtherWrite;
  386. if (!OperatingSystem.IsWindows() && File.Exists(path))
  387. {
  388. File.SetUnixFileMode(path, ExecutableFileMode);
  389. }
  390. }
  391. private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
  392. {
  393. // Extract Update
  394. taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
  395. taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
  396. if (OperatingSystem.IsLinux())
  397. {
  398. using (Stream inStream = File.OpenRead(updateFile))
  399. using (Stream gzipStream = new GZipInputStream(inStream))
  400. using (TarInputStream tarStream = new TarInputStream(gzipStream, Encoding.ASCII))
  401. {
  402. await Task.Run(() =>
  403. {
  404. TarEntry tarEntry;
  405. while ((tarEntry = tarStream.GetNextEntry()) != null)
  406. {
  407. if (tarEntry.IsDirectory) continue;
  408. string outPath = Path.Combine(UpdateDir, tarEntry.Name);
  409. Directory.CreateDirectory(Path.GetDirectoryName(outPath));
  410. using (FileStream outStream = File.OpenWrite(outPath))
  411. {
  412. tarStream.CopyEntryContents(outStream);
  413. }
  414. File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
  415. TarEntry entry = tarEntry;
  416. Dispatcher.UIThread.Post(() =>
  417. {
  418. taskDialog.SetProgressBarState(GetPercentage(entry.Size, inStream.Length), TaskDialogProgressState.Normal);
  419. });
  420. }
  421. });
  422. taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
  423. }
  424. }
  425. else
  426. {
  427. using (Stream inStream = File.OpenRead(updateFile))
  428. using (ZipFile zipFile = new ZipFile(inStream))
  429. {
  430. await Task.Run(() =>
  431. {
  432. double count = 0;
  433. foreach (ZipEntry zipEntry in zipFile)
  434. {
  435. count++;
  436. if (zipEntry.IsDirectory) continue;
  437. string outPath = Path.Combine(UpdateDir, zipEntry.Name);
  438. Directory.CreateDirectory(Path.GetDirectoryName(outPath));
  439. using (Stream zipStream = zipFile.GetInputStream(zipEntry))
  440. using (FileStream outStream = File.OpenWrite(outPath))
  441. {
  442. zipStream.CopyTo(outStream);
  443. }
  444. File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
  445. Dispatcher.UIThread.Post(() =>
  446. {
  447. taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
  448. });
  449. }
  450. });
  451. }
  452. }
  453. // Delete downloaded zip
  454. File.Delete(updateFile);
  455. List<string> allFiles = EnumerateFilesToDelete().ToList();
  456. taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
  457. taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
  458. // Replace old files
  459. await Task.Run(() =>
  460. {
  461. double count = 0;
  462. foreach (string file in allFiles)
  463. {
  464. count++;
  465. try
  466. {
  467. File.Move(file, file + ".ryuold");
  468. Dispatcher.UIThread.Post(() =>
  469. {
  470. taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
  471. });
  472. }
  473. catch
  474. {
  475. Logger.Warning?.Print(LogClass.Application, string.Format(LocaleManager.Instance[LocaleKeys.UpdaterRenameFailed], file));
  476. }
  477. }
  478. Dispatcher.UIThread.Post(() =>
  479. {
  480. taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
  481. taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
  482. });
  483. MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
  484. });
  485. Directory.Delete(UpdateDir, true);
  486. SetFileExecutable(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx"));
  487. UpdateSuccessful = true;
  488. taskDialog.Hide();
  489. }
  490. #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  491. public static bool CanUpdate(bool showWarnings, StyleableWindow parent)
  492. {
  493. #if !DISABLE_UPDATER
  494. if (RuntimeInformation.OSArchitecture != Architecture.X64)
  495. {
  496. if (showWarnings)
  497. {
  498. ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage],
  499. LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]);
  500. }
  501. return false;
  502. }
  503. if (!NetworkInterface.GetIsNetworkAvailable())
  504. {
  505. if (showWarnings)
  506. {
  507. ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage],
  508. LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]);
  509. }
  510. return false;
  511. }
  512. if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
  513. {
  514. if (showWarnings)
  515. {
  516. ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage],
  517. LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
  518. }
  519. return false;
  520. }
  521. return true;
  522. #else
  523. if (showWarnings)
  524. {
  525. if (ReleaseInformation.IsFlatHubBuild())
  526. {
  527. ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]);
  528. }
  529. else
  530. {
  531. ContentDialogHelper.CreateWarningDialog(LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]);
  532. }
  533. }
  534. return false;
  535. #endif
  536. }
  537. #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
  538. // NOTE: This method should always reflect the latest build layout.s
  539. private static IEnumerable<string> EnumerateFilesToDelete()
  540. {
  541. var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
  542. if (OperatingSystem.IsWindows())
  543. {
  544. foreach (string dir in WindowsDependencyDirs)
  545. {
  546. string dirPath = Path.Combine(HomeDir, dir);
  547. if (Directory.Exists(dirPath))
  548. {
  549. files = files.Concat(Directory.EnumerateFiles(dirPath, "*", SearchOption.AllDirectories));
  550. }
  551. }
  552. }
  553. return files;
  554. }
  555. private static void MoveAllFilesOver(string root, string dest, TaskDialog taskDialog)
  556. {
  557. var total = Directory.GetFiles(root, "*", SearchOption.AllDirectories).Length;
  558. foreach (string directory in Directory.GetDirectories(root))
  559. {
  560. string dirName = Path.GetFileName(directory);
  561. if (!Directory.Exists(Path.Combine(dest, dirName)))
  562. {
  563. Directory.CreateDirectory(Path.Combine(dest, dirName));
  564. }
  565. MoveAllFilesOver(directory, Path.Combine(dest, dirName), taskDialog);
  566. }
  567. double count = 0;
  568. foreach (string file in Directory.GetFiles(root))
  569. {
  570. count++;
  571. File.Move(file, Path.Combine(dest, Path.GetFileName(file)), true);
  572. Dispatcher.UIThread.InvokeAsync(() =>
  573. {
  574. taskDialog.SetProgressBarState(GetPercentage(count, total), TaskDialogProgressState.Normal);
  575. });
  576. }
  577. }
  578. public static void CleanupUpdate()
  579. {
  580. foreach (string file in Directory.GetFiles(HomeDir, "*.ryuold", SearchOption.AllDirectories))
  581. {
  582. File.Delete(file);
  583. }
  584. }
  585. }
  586. }