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

216 lines
6.3 KiB

  1. using IPA.Config;
  2. using IPA.Updating.BeatMods;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using UnityEngine;
  10. using static IPA.Updating.BeatMods.Updater;
  11. namespace BSIPA_ModList
  12. {
  13. internal class DownloadObject
  14. {
  15. public enum States
  16. {
  17. ToDownload, Downloading, Installing, Failed, Completed
  18. }
  19. public DependencyObject Mod;
  20. public Sprite Icon;
  21. public States State = States.ToDownload;
  22. public double Progress = 0;
  23. }
  24. internal class DownloadController : MonoBehaviour
  25. {
  26. private static DownloadController _instance;
  27. public static DownloadController Instance
  28. {
  29. get
  30. {
  31. if (_instance == null)
  32. _instance = Create();
  33. return _instance;
  34. }
  35. }
  36. public static DownloadController Create()
  37. {
  38. var inst = new GameObject("BSIPA Modlist Download Controller").AddComponent<DownloadController>();
  39. if (SelfConfig.SelfConfigRef.Value.Updates.AutoCheckUpdates)
  40. inst.StartCoroutine(inst.StartUpdateCheck());
  41. return inst;
  42. }
  43. private IEnumerator StartUpdateCheck()
  44. {
  45. yield return null;
  46. CheckForUpdates();
  47. }
  48. private readonly List<DownloadObject> downloads = new List<DownloadObject>();
  49. private readonly Dictionary<DependencyObject, DownloadObject> lookup = new Dictionary<DependencyObject, DownloadObject>();
  50. internal IReadOnlyList<DownloadObject> Downloads => downloads;
  51. public event Action OnCheckForUpdates;
  52. public event Action<int> OnCheckForUpdatesComplete;
  53. public event Action OnDownloadStateChanged;
  54. public event Action OnDownloaderListChanged;
  55. private enum States
  56. {
  57. Start, Checking, UpdatesFound, Downloading, Done
  58. }
  59. private States _state = States.Start;
  60. private States State
  61. {
  62. get => _state;
  63. set
  64. {
  65. _state = value;
  66. OnDownloadStateChanged?.Invoke();
  67. }
  68. }
  69. public bool CanCheck => State == States.Start || State == States.Done;
  70. public bool CanDownload => State == States.UpdatesFound;
  71. public bool CanReset => State == States.UpdatesFound;
  72. public bool IsChecking => State == States.Checking;
  73. public bool IsDownloading => State == States.Downloading;
  74. public bool IsDone => State == States.Done;
  75. public void Awake() => DontDestroyOnLoad(this);
  76. public void CheckForUpdates()
  77. {
  78. if (!CanCheck)
  79. throw new InvalidOperationException("Invalid state for CheckForUpdates to be called");
  80. State = States.Checking;
  81. OnCheckForUpdates?.Invoke();
  82. Updater.Instance.CheckForUpdates(UpdateCheckComplete);
  83. }
  84. public void ResetCheck(bool resetCache = false)
  85. {
  86. if (!CanReset)
  87. throw new InvalidOperationException("Invalid state for ResetCheck to be called");
  88. Clear();
  89. State = States.Start;
  90. if (resetCache)
  91. ResetRequestCache();
  92. }
  93. private void Clear()
  94. {
  95. downloads.Clear();
  96. lookup.Clear();
  97. OnDownloaderListChanged?.Invoke();
  98. }
  99. private void Add(DownloadObject obj)
  100. {
  101. downloads.Add(obj);
  102. lookup.Add(obj.Mod, obj);
  103. }
  104. private void Remove(DependencyObject obj)
  105. {
  106. downloads.Remove(lookup[obj]);
  107. lookup.Remove(obj);
  108. OnDownloaderListChanged?.Invoke();
  109. }
  110. private void UpdateCheckComplete(List<DependencyObject> found)
  111. {
  112. State = States.UpdatesFound;
  113. OnCheckForUpdatesComplete?.Invoke(found.Count);
  114. foreach (var dep in found)
  115. Add(new DownloadObject
  116. {
  117. Mod = dep,
  118. Icon = Utilities.GetIcon(dep.LocalPluginMeta?.Metadata),
  119. State = DownloadObject.States.ToDownload,
  120. Progress = 0
  121. });
  122. OnDownloaderListChanged?.Invoke();
  123. if (SelfConfig.SelfConfigRef.Value.Updates.AutoUpdate)
  124. StartDownloads();
  125. }
  126. public void StartDownloads()
  127. {
  128. if (!CanDownload)
  129. throw new InvalidOperationException("Invalid state for StartDownloads to be called");
  130. State = States.Downloading;
  131. Updater.Instance.StartDownload(downloads.Select(d => d.Mod), _DownloadStart, _DownloadProgress,
  132. _DownloadFailed, _DownloadFinished, _InstallFailed, _InstallFinished);
  133. if (downloads.Count == 0)
  134. OnAllDownloadsCompleted();
  135. }
  136. private void _DownloadStart(DependencyObject obj)
  137. {
  138. var dl = lookup[obj];
  139. dl.Progress = 0;
  140. dl.State = DownloadObject.States.Downloading;
  141. }
  142. private void _DownloadProgress(DependencyObject obj, long totalBytes, long currentBytes, double progress)
  143. {
  144. lookup[obj].Progress = progress;
  145. }
  146. private void _DownloadFailed(DependencyObject obj, string error)
  147. {
  148. lookup[obj].State = DownloadObject.States.Failed;
  149. }
  150. private void _DownloadFinished(DependencyObject obj)
  151. {
  152. lookup[obj].State = DownloadObject.States.Installing;
  153. }
  154. private void _InstallFailed(DependencyObject obj, Exception error)
  155. {
  156. lookup[obj].State = DownloadObject.States.Failed;
  157. }
  158. private void _InstallFinished(DependencyObject obj, bool didError)
  159. {
  160. if (!didError)
  161. lookup[obj].State = DownloadObject.States.Completed;
  162. StartCoroutine(RemoveModFromList(obj));
  163. }
  164. private IEnumerator RemoveModFromList(DependencyObject obj)
  165. {
  166. yield return new WaitForSeconds(0.25f);
  167. Remove(obj);
  168. if (downloads.Count == 0)
  169. OnAllDownloadsCompleted();
  170. }
  171. private void OnAllDownloadsCompleted()
  172. {
  173. State = States.Done;
  174. }
  175. }
  176. }