Updater.cs 27 KB

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