OpenVRPackageInstaller.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. #if (UNITY_EDITOR && UNITY_2019_1_OR_NEWER)
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using Unity.XR.OpenVR.SimpleJSON;
  6. using UnityEditor;
  7. using UnityEditor.PackageManager;
  8. using UnityEditor.PackageManager.Requests;
  9. using UnityEditor.PackageManager.UI;
  10. using UnityEngine;
  11. using UnityEngine.Networking;
  12. namespace Unity.XR.OpenVR
  13. {
  14. [InitializeOnLoad]
  15. public class OpenVRPackageInstaller : ScriptableObject
  16. {
  17. private const string valveOpenVRPackageString = "com.valvesoftware.unity.openvr";
  18. private static ListRequest listRequest;
  19. private static AddRequest addRequest;
  20. private static System.Diagnostics.Stopwatch packageTime = new System.Diagnostics.Stopwatch();
  21. private const float estimatedTimeToInstall = 90; // in seconds
  22. private const string updaterKeyTemplate = "com.valvesoftware.unity.openvr.updateState.{0}";
  23. private static string updaterKey
  24. {
  25. get { return string.Format(updaterKeyTemplate, Application.productName); }
  26. }
  27. private static UpdateStates updateState
  28. {
  29. get { return _updateState; }
  30. set
  31. {
  32. #if VALVE_DEBUG
  33. Debug.Log("[DEBUG] Update State: " + value.ToString());
  34. #endif
  35. _updateState = value;
  36. EditorPrefs.SetInt(updaterKey, (int)value);
  37. }
  38. }
  39. private static UpdateStates _updateState = UpdateStates.Idle;
  40. private static double runningSeconds
  41. {
  42. get
  43. {
  44. if (packageTime.IsRunning == false)
  45. packageTime.Start();
  46. return packageTime.Elapsed.TotalSeconds;
  47. }
  48. }
  49. private static bool forced = false;
  50. public static void Start(bool force = false)
  51. {
  52. EditorApplication.update -= Update;
  53. EditorApplication.update += Update;
  54. if (force)
  55. {
  56. RemoveScopedRegistry();
  57. }
  58. }
  59. static OpenVRPackageInstaller()
  60. {
  61. #if OPENVR_XR_API //if we're updating, go ahead and just start
  62. Start();
  63. #endif
  64. }
  65. /// <summary>
  66. /// State Machine
  67. /// Idle: Start from last known state. If none is known, ask user if they want to install, if yes goto remove scoped registry step
  68. /// WaitingOnExistingCheck:
  69. /// RemoveScopedRegistry: Remove the scoped registry entry if it exists
  70. /// WaitingForAdd: if the add request has been nulled or completed successfully, request a list of packages for confirmation
  71. /// WaitingForAddConfirmation: enumerate the packages and verify the add succeeded. If it failed, try again.
  72. /// If it succeeded request removal of this script
  73. /// RemoveSelf: delete the key that we've been using to maintain state. Delete this script and the containing folder if it's empty.
  74. /// </summary>
  75. private static void Update()
  76. {
  77. switch (updateState)
  78. {
  79. case UpdateStates.Idle:
  80. if (EditorPrefs.HasKey(updaterKey))
  81. {
  82. _updateState = (UpdateStates)EditorPrefs.GetInt(updaterKey);
  83. packageTime.Start();
  84. }
  85. else
  86. {
  87. RequestExisting();
  88. }
  89. break;
  90. case UpdateStates.WaitingOnExistingCheck:
  91. if (listRequest == null)
  92. {
  93. //the list request got nulled for some reason. Request it again.
  94. RequestExisting();
  95. }
  96. else if (listRequest != null && listRequest.IsCompleted)
  97. {
  98. if (listRequest.Error != null || listRequest.Status == UnityEditor.PackageManager.StatusCode.Failure)
  99. {
  100. DisplayErrorAndStop("Error while checking for an existing OpenVR package.", listRequest);
  101. }
  102. else
  103. {
  104. if (listRequest.Result.Any(package => package.name == valveOpenVRPackageString))
  105. {
  106. var existingPackage = listRequest.Result.FirstOrDefault(package => package.name == valveOpenVRPackageString);
  107. string latestTarball = GetLatestTarballVersion();
  108. if (latestTarball != null && latestTarball.CompareTo(existingPackage.version) == 1)
  109. {
  110. //we have a tarball higher than the currently installed version
  111. string upgradeString = string.Format("This SteamVR Unity Plugin has a newer version of the Unity XR OpenVR package than you have installed. Would you like to upgrade?\n\nCurrent: {0}\nUpgrade: {1} (recommended)", existingPackage.version, latestTarball);
  112. bool upgrade = UnityEditor.EditorUtility.DisplayDialog("OpenVR XR Updater", upgradeString, "Upgrade", "Cancel");
  113. if (upgrade)
  114. RemoveScopedRegistry();
  115. else
  116. {
  117. bool delete = UnityEditor.EditorUtility.DisplayDialog("OpenVR XR Updater", "Would you like to remove this updater script so we don't ask again?", "Remove updater", "Keep");
  118. if (delete)
  119. {
  120. Stop();
  121. return;
  122. }
  123. else
  124. {
  125. GentleStop();
  126. return;
  127. }
  128. }
  129. }
  130. }
  131. else
  132. {
  133. #if UNITY_2020_1_OR_NEWER
  134. RemoveScopedRegistry(); //just install if we're on 2020 and they don't have the package
  135. return;
  136. #else
  137. //they don't have the package yet. Ask if they want to install (only for 2019)
  138. bool blankInstall = UnityEditor.EditorUtility.DisplayDialog("OpenVR XR Installer", "The SteamVR Unity Plugin can be used with the legacy Unity VR API (Unity 5.4 - 2019) or with the Unity XR API (2019+). Would you like to install OpenVR for Unity XR?", "Install", "Cancel");
  139. if (blankInstall)
  140. RemoveScopedRegistry();
  141. else
  142. {
  143. bool delete = UnityEditor.EditorUtility.DisplayDialog("OpenVR XR Installer", "Would you like to remove this installer script so we don't ask again?", "Remove installer", "Keep");
  144. if (delete)
  145. {
  146. Stop();
  147. return;
  148. }
  149. else
  150. {
  151. GentleStop();
  152. return;
  153. }
  154. }
  155. #endif
  156. }
  157. }
  158. }
  159. break;
  160. case UpdateStates.WaitingForAdd:
  161. if (addRequest == null)
  162. {
  163. //the add request got nulled for some reason. Request an add confirmation
  164. RequestAddConfirmation();
  165. }
  166. else if (addRequest != null && addRequest.IsCompleted)
  167. {
  168. if (addRequest.Error != null || addRequest.Status == UnityEditor.PackageManager.StatusCode.Failure)
  169. {
  170. DisplayErrorAndStop("Error adding new version of OpenVR package.", addRequest);
  171. }
  172. else
  173. {
  174. //verify that the package has been added (then stop)
  175. RequestAddConfirmation();
  176. }
  177. }
  178. else
  179. {
  180. if (packageTime.Elapsed.TotalSeconds > estimatedTimeToInstall)
  181. DisplayErrorAndStop("Error while trying to add package.", addRequest);
  182. else
  183. DisplayProgressBar();
  184. }
  185. break;
  186. case UpdateStates.WaitingForAddConfirmation:
  187. if (listRequest == null)
  188. {
  189. //the list request got nulled for some reason. Request it again.
  190. RequestAddConfirmation();
  191. }
  192. else if (listRequest != null && listRequest.IsCompleted)
  193. {
  194. if (listRequest.Error != null || listRequest.Status == UnityEditor.PackageManager.StatusCode.Failure)
  195. {
  196. DisplayErrorAndStop("Error while confirming the OpenVR package has been added.", listRequest);
  197. }
  198. else
  199. {
  200. if (listRequest.Result.Any(package => package.name == valveOpenVRPackageString))
  201. {
  202. updateState = UpdateStates.RemoveSelf;
  203. UnityEditor.EditorUtility.DisplayDialog("OpenVR Unity XR Installer", "OpenVR Unity XR successfully installed.\n\nA restart of the Unity Editor may be necessary.", "Ok");
  204. }
  205. else
  206. {
  207. //try to add again if it's not there and we don't know why
  208. RequestAdd();
  209. }
  210. }
  211. }
  212. else
  213. {
  214. if (runningSeconds > estimatedTimeToInstall)
  215. {
  216. DisplayErrorAndStop("Error while confirming the OpenVR package has been added.", listRequest);
  217. }
  218. else
  219. DisplayProgressBar();
  220. }
  221. break;
  222. case UpdateStates.RemoveSelf:
  223. EditorPrefs.DeleteKey(updaterKey);
  224. EditorUtility.ClearProgressBar();
  225. EditorApplication.update -= Update;
  226. #if VALVE_SKIP_DELETE
  227. Debug.Log("[DEBUG] skipping script deletion. Complete.");
  228. return;
  229. #endif
  230. var script = MonoScript.FromScriptableObject(OpenVRPackageInstaller.CreateInstance<OpenVRPackageInstaller>());
  231. var path = AssetDatabase.GetAssetPath(script);
  232. FileInfo updaterScript = new FileInfo(path); updaterScript.IsReadOnly = false;
  233. FileInfo updaterScriptMeta = new FileInfo(path + ".meta");
  234. FileInfo simpleJSONScript = new FileInfo(Path.Combine(updaterScript.Directory.FullName, "OpenVRSimpleJSON.cs"));
  235. FileInfo simpleJSONScriptMeta = new FileInfo(Path.Combine(updaterScript.Directory.FullName, "OpenVRSimpleJSON.cs.meta"));
  236. updaterScript.IsReadOnly = false;
  237. updaterScriptMeta.IsReadOnly = false;
  238. simpleJSONScript.IsReadOnly = false;
  239. simpleJSONScriptMeta.IsReadOnly = false;
  240. updaterScriptMeta.Delete();
  241. if (updaterScriptMeta.Exists)
  242. {
  243. DisplayErrorAndStop("Error while removing package installer script. Please delete manually.", listRequest);
  244. return;
  245. }
  246. simpleJSONScript.Delete();
  247. simpleJSONScriptMeta.Delete();
  248. updaterScript.Delete();
  249. AssetDatabase.Refresh();
  250. break;
  251. }
  252. }
  253. private static string GetLatestTarballVersion()
  254. {
  255. FileInfo[] files;
  256. FileInfo latest = GetAvailableTarballs(out files);
  257. if (latest == null)
  258. return null;
  259. return GetTarballVersion(latest);
  260. }
  261. private static FileInfo GetAvailableTarballs(out FileInfo[] packages)
  262. {
  263. var installerScript = MonoScript.FromScriptableObject(OpenVRPackageInstaller.CreateInstance<OpenVRPackageInstaller>());
  264. var scriptPath = AssetDatabase.GetAssetPath(installerScript);
  265. FileInfo thisScript = new FileInfo(scriptPath);
  266. packages = thisScript.Directory.GetFiles("*.tgz");
  267. if (packages.Length > 0)
  268. {
  269. if (packages.Length > 1)
  270. {
  271. var descending = packages.OrderByDescending(file => file.Name);
  272. var latest = descending.First();
  273. packages = descending.Where(file => file != latest).ToArray();
  274. return latest;
  275. }
  276. var onlyPackage = packages[0];
  277. packages = new FileInfo[0];
  278. return onlyPackage;
  279. }
  280. else
  281. return null;
  282. }
  283. private static string GetTarballVersion(FileInfo file)
  284. {
  285. int startIndex = file.Name.IndexOf('-') + 1;
  286. int endIndex = file.Name.IndexOf(".tgz");
  287. int len = endIndex - startIndex;
  288. return file.Name.Substring(startIndex, len);
  289. }
  290. private const string packageManifestPath = "Packages/manifest.json";
  291. private const string scopedRegistryKey = "scopedRegistries";
  292. private const string npmRegistryName = "Valve";
  293. //load packages.json
  294. //check for existing scoped registries
  295. //check for our scoped registry
  296. //if no to either then add it
  297. //save file
  298. //reload
  299. private static void RemoveScopedRegistry()
  300. {
  301. updateState = UpdateStates.RemoveOldRegistry;
  302. packageTime.Start();
  303. if (File.Exists(packageManifestPath) == false)
  304. {
  305. Debug.LogWarning("[OpenVR Installer] Could not find package manifest at: " + packageManifestPath);
  306. RequestAdd();
  307. return;
  308. }
  309. string jsonText = File.ReadAllText(packageManifestPath);
  310. JSONNode manifest = JSON.Parse(jsonText);
  311. if (manifest.HasKey(scopedRegistryKey) == true)
  312. {
  313. if (manifest[scopedRegistryKey].HasKey(npmRegistryName))
  314. {
  315. manifest[scopedRegistryKey].Remove(npmRegistryName);
  316. File.WriteAllText(packageManifestPath, manifest.ToString(2));
  317. Debug.Log("[OpenVR Installer] Removed Valve entry from scoped registry.");
  318. }
  319. }
  320. RequestAdd();
  321. }
  322. private static void RequestAdd()
  323. {
  324. updateState = UpdateStates.WaitingForAdd;
  325. FileInfo[] oldFiles;
  326. FileInfo latest = GetAvailableTarballs(out oldFiles);
  327. if (latest != null)
  328. {
  329. if (oldFiles.Length > 0)
  330. {
  331. var oldFilesNames = oldFiles.Select(file => file.Name);
  332. string oldFilesString = string.Join("\n", oldFilesNames);
  333. bool delete = UnityEditor.EditorUtility.DisplayDialog("OpenVR XR Installer", "Would you like to delete the old OpenVR packages?\n\n" + oldFilesString, "Delete old files", "Keep");
  334. if (delete)
  335. {
  336. foreach (FileInfo file in oldFiles)
  337. {
  338. FileInfo meta = new FileInfo(file.FullName + ".meta");
  339. if (meta.Exists)
  340. {
  341. meta.IsReadOnly = false;
  342. meta.Delete();
  343. }
  344. if (file.Exists)
  345. {
  346. file.IsReadOnly = false;
  347. file.Delete();
  348. }
  349. }
  350. }
  351. }
  352. string packagePath = latest.FullName;
  353. if (packagePath != null)
  354. {
  355. string packageAbsolute = packagePath.Replace("\\", "/");
  356. string packageRelative = packageAbsolute.Substring(packageAbsolute.IndexOf("/Assets/"));
  357. string packageURI = System.Uri.EscapeUriString(packageRelative);
  358. addRequest = UnityEditor.PackageManager.Client.Add("file:.." + packageURI);
  359. }
  360. else
  361. {
  362. updateState = UpdateStates.RemoveSelf;
  363. }
  364. }
  365. }
  366. private static void RequestAddConfirmation()
  367. {
  368. updateState = UpdateStates.WaitingForAddConfirmation;
  369. listRequest = Client.List(true, true);
  370. }
  371. private static void RequestExisting()
  372. {
  373. updateState = UpdateStates.WaitingOnExistingCheck;
  374. listRequest = Client.List(true, true);
  375. }
  376. private static string dialogText = "Installing OpenVR Unity XR package from local storage using Unity Package Manager...";
  377. private static void DisplayProgressBar()
  378. {
  379. bool cancel = UnityEditor.EditorUtility.DisplayCancelableProgressBar("SteamVR", dialogText, (float)packageTime.Elapsed.TotalSeconds / estimatedTimeToInstall);
  380. if (cancel)
  381. Stop();
  382. }
  383. private static void DisplayErrorAndStop(string stepInfo, Request request)
  384. {
  385. string error = "";
  386. if (request != null)
  387. error = request.Error.message;
  388. string errorMessage = string.Format("{0}:\n\t{1}\n\nPlease manually reinstall the package through the package manager.", stepInfo, error);
  389. UnityEngine.Debug.LogError(errorMessage);
  390. Stop();
  391. UnityEditor.EditorUtility.DisplayDialog("OpenVR Error", errorMessage, "Ok");
  392. }
  393. private static void Stop()
  394. {
  395. updateState = UpdateStates.RemoveSelf;
  396. }
  397. private static void GentleStop()
  398. {
  399. EditorApplication.update -= Update;
  400. }
  401. private enum UpdateStates
  402. {
  403. Idle,
  404. WaitingOnExistingCheck,
  405. RemoveOldRegistry,
  406. WaitingForAdd,
  407. WaitingForAddConfirmation,
  408. RemoveSelf,
  409. }
  410. }
  411. }
  412. #endif