2021년 4학년 1학기 기업연계프로젝트2 컴퓨터소프트웨어공학과 <원광투어팀> 팀장 : 송유진 팀원 : 김나영, 이경희, 한유진
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.

589 lines
22 KiB

5 years ago
  1. //========= Copyright 2016-2020, HTC Corporation. All rights reserved. ===========
  2. #pragma warning disable 0649
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text.RegularExpressions;
  8. using UnityEditor;
  9. using UnityEngine;
  10. using System.Reflection;
  11. #if UNITY_5_4_OR_NEWER
  12. using UnityEngine.Networking;
  13. #else
  14. using UnityWebRequest = UnityEngine.WWW;
  15. #endif
  16. namespace HTC.UnityPlugin.Vive
  17. {
  18. [InitializeOnLoad]
  19. public class VIUVersionCheck : EditorWindow
  20. {
  21. [Serializable]
  22. private struct RepoInfo
  23. {
  24. public string tag_name;
  25. public string body;
  26. }
  27. public interface IPropSetting
  28. {
  29. bool SkipCheck();
  30. void UpdateCurrentValue();
  31. bool IsIgnored();
  32. bool IsUsingRecommendedValue();
  33. bool DoDrawRecommend(); // return true if setting accepted
  34. void AcceptRecommendValue();
  35. void DoIgnore();
  36. void DeleteIgnore();
  37. }
  38. public class RecommendedSetting<T> : IPropSetting
  39. {
  40. private const string fmtTitle = "{0} (current = {1})";
  41. private const string fmtRecommendBtn = "Use recommended ({0})";
  42. private const string fmtRecommendBtnWithPosefix = "Use recommended ({0}) - {1}";
  43. private string m_settingTitle;
  44. private string m_settingTrimedTitle;
  45. private string ignoreKey { get { return m_settingTrimedTitle; } }
  46. public string settingTitle { get { return m_settingTitle; } set { m_settingTitle = value; m_settingTrimedTitle = value.Replace(" ", ""); } }
  47. public string recommendBtnPostfix = string.Empty;
  48. public string toolTip = string.Empty;
  49. public Func<bool> skipCheckFunc = null;
  50. public Func<T> recommendedValueFunc = null;
  51. public Func<T> currentValueFunc = null;
  52. public Action<T> setValueFunc = null;
  53. public T currentValue = default(T);
  54. public T recommendedValue = default(T);
  55. public T GetRecommended() { return recommendedValueFunc == null ? recommendedValue : recommendedValueFunc(); }
  56. public bool SkipCheck() { return skipCheckFunc == null ? false : skipCheckFunc(); }
  57. public bool IsIgnored() { return VIUProjectSettings.HasIgnoreKey(ignoreKey); }
  58. public bool IsUsingRecommendedValue() { return EqualityComparer<T>.Default.Equals(currentValue, GetRecommended()); }
  59. public void UpdateCurrentValue() { currentValue = currentValueFunc(); }
  60. public bool DoDrawRecommend()
  61. {
  62. GUILayout.Label(new GUIContent(string.Format(fmtTitle, settingTitle, currentValue), toolTip));
  63. GUILayout.BeginHorizontal();
  64. bool recommendBtnClicked;
  65. if (string.IsNullOrEmpty(recommendBtnPostfix))
  66. {
  67. recommendBtnClicked = GUILayout.Button(new GUIContent(string.Format(fmtRecommendBtn, GetRecommended()), toolTip));
  68. }
  69. else
  70. {
  71. recommendBtnClicked = GUILayout.Button(new GUIContent(string.Format(fmtRecommendBtnWithPosefix, GetRecommended(), recommendBtnPostfix), toolTip));
  72. }
  73. if (recommendBtnClicked)
  74. {
  75. AcceptRecommendValue();
  76. }
  77. GUILayout.FlexibleSpace();
  78. if (GUILayout.Button(new GUIContent("Ignore", toolTip)))
  79. {
  80. DoIgnore();
  81. }
  82. GUILayout.EndHorizontal();
  83. return recommendBtnClicked;
  84. }
  85. public void AcceptRecommendValue()
  86. {
  87. setValueFunc(GetRecommended());
  88. }
  89. public void DoIgnore()
  90. {
  91. VIUProjectSettings.AddIgnoreKey(ignoreKey);
  92. }
  93. public void DeleteIgnore()
  94. {
  95. VIUProjectSettings.RemoveIgnoreKey(ignoreKey);
  96. }
  97. }
  98. public abstract class RecommendedSettingCollection : List<IPropSetting> { }
  99. public const string lastestVersionUrl = "https://api.github.com/repos/ViveSoftware/ViveInputUtility-Unity/releases/latest";
  100. public const string pluginUrl = "https://github.com/ViveSoftware/ViveInputUtility-Unity/releases";
  101. public const double versionCheckIntervalMinutes = 30.0;
  102. private const string nextVersionCheckTimeKey = "ViveInputUtility.LastVersionCheckTime";
  103. private const string fmtIgnoreUpdateKey = "DoNotShowUpdate.v{0}";
  104. private static string ignoreThisVersionKey;
  105. private static bool completeCheckVersionFlow = false;
  106. private static UnityWebRequest webReq;
  107. private static RepoInfo latestRepoInfo;
  108. private static System.Version latestVersion;
  109. private static Vector2 releaseNoteScrollPosition;
  110. private static Vector2 settingScrollPosition;
  111. private static bool showNewVersion;
  112. private static bool toggleSkipThisVersion = false;
  113. private static VIUVersionCheck windowInstance;
  114. private static List<IPropSetting> s_settings;
  115. private static bool editorUpdateRegistered;
  116. private Texture2D viuLogo;
  117. /// <summary>
  118. /// Count of settings that are ignored
  119. /// </summary>
  120. public static int ignoredSettingsCount { get; private set; }
  121. /// <summary>
  122. /// Count of settings that are not using recommended value
  123. /// </summary>
  124. public static int shouldNotifiedSettingsCount { get; private set; }
  125. /// <summary>
  126. /// Count of settings that are not ignored and not using recommended value
  127. /// </summary>
  128. public static int notifiedSettingsCount { get; private set; }
  129. public static bool recommendedWindowOpened { get { return windowInstance != null; } }
  130. static VIUVersionCheck()
  131. {
  132. editorUpdateRegistered = true;
  133. EditorApplication.update += CheckVersionAndSettings;
  134. #if UNITY_2017_2_OR_NEWER
  135. EditorApplication.playModeStateChanged += (mode) =>
  136. {
  137. if (mode == PlayModeStateChange.EnteredEditMode && !editorUpdateRegistered)
  138. {
  139. #else
  140. EditorApplication.playmodeStateChanged += () =>
  141. {
  142. if (!EditorApplication.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode && !editorUpdateRegistered)
  143. {
  144. #endif
  145. editorUpdateRegistered = true;
  146. EditorApplication.update += CheckVersionAndSettings;
  147. }
  148. };
  149. }
  150. public static void AddRecommendedSetting<T>(RecommendedSetting<T> setting)
  151. {
  152. InitializeSettins();
  153. s_settings.Add(setting);
  154. }
  155. private static void InitializeSettins()
  156. {
  157. if (s_settings != null) { return; }
  158. s_settings = new List<IPropSetting>();
  159. foreach (var type in Assembly.GetAssembly(typeof(RecommendedSettingCollection)).GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(RecommendedSettingCollection))))
  160. {
  161. s_settings.AddRange((RecommendedSettingCollection)Activator.CreateInstance(type));
  162. }
  163. }
  164. private static void VersionCheckLog(string msg)
  165. {
  166. #if VIU_PRINT_FETCH_VERSION_LOG
  167. using (var outputFile = new StreamWriter("VIUVersionCheck.log", true))
  168. {
  169. outputFile.WriteLine(DateTime.Now.ToString() + " - " + msg + ". Stop fetching until " + UtcDateTimeFromStr(EditorPrefs.GetString(nextVersionCheckTimeKey)).ToLocalTime().ToString());
  170. }
  171. #endif
  172. }
  173. // check vive input utility version on github
  174. private static void CheckVersionAndSettings()
  175. {
  176. if (Application.isPlaying)
  177. {
  178. EditorApplication.update -= CheckVersionAndSettings;
  179. editorUpdateRegistered = false;
  180. return;
  181. }
  182. InitializeSettins();
  183. // fetch new version info from github release site
  184. if (!completeCheckVersionFlow && VIUSettings.autoCheckNewVIUVersion)
  185. {
  186. if (webReq == null) // web request not running
  187. {
  188. if (EditorPrefs.HasKey(nextVersionCheckTimeKey) && DateTime.UtcNow < UtcDateTimeFromStr(EditorPrefs.GetString(nextVersionCheckTimeKey)))
  189. {
  190. VersionCheckLog("Skipped");
  191. completeCheckVersionFlow = true;
  192. return;
  193. }
  194. webReq = GetUnityWebRequestAndSend(lastestVersionUrl);
  195. }
  196. if (!webReq.isDone)
  197. {
  198. return;
  199. }
  200. // On Windows, PlaterSetting is stored at \HKEY_CURRENT_USER\Software\Unity Technologies\Unity Editor 5.x
  201. EditorPrefs.SetString(nextVersionCheckTimeKey, UtcDateTimeToStr(DateTime.UtcNow.AddMinutes(versionCheckIntervalMinutes)));
  202. if (UrlSuccess(webReq))
  203. {
  204. var json = GetWebText(webReq);
  205. if (!string.IsNullOrEmpty(json))
  206. {
  207. latestRepoInfo = JsonUtility.FromJson<RepoInfo>(json);
  208. VersionCheckLog("Fetched");
  209. }
  210. }
  211. // parse latestVersion and ignoreThisVersionKey
  212. if (!string.IsNullOrEmpty(latestRepoInfo.tag_name))
  213. {
  214. try
  215. {
  216. latestVersion = new System.Version(Regex.Replace(latestRepoInfo.tag_name, "[^0-9\\.]", string.Empty));
  217. ignoreThisVersionKey = string.Format(fmtIgnoreUpdateKey, latestVersion.ToString());
  218. }
  219. catch
  220. {
  221. latestVersion = default(System.Version);
  222. ignoreThisVersionKey = string.Empty;
  223. }
  224. }
  225. webReq.Dispose();
  226. webReq = null;
  227. completeCheckVersionFlow = true;
  228. }
  229. VIUSettingsEditor.PackageManagerHelper.PreparePackageList();
  230. if (VIUSettingsEditor.PackageManagerHelper.isPreparingList) { return; }
  231. showNewVersion = !string.IsNullOrEmpty(ignoreThisVersionKey) && !VIUProjectSettings.HasIgnoreKey(ignoreThisVersionKey) && latestVersion > VIUVersion.current;
  232. UpdateIgnoredNotifiedSettingsCount(false);
  233. if (showNewVersion || notifiedSettingsCount > 0)
  234. {
  235. TryOpenRecommendedSettingWindow();
  236. }
  237. EditorApplication.update -= CheckVersionAndSettings;
  238. editorUpdateRegistered = false;
  239. }
  240. public static bool UpdateIgnoredNotifiedSettingsCount(bool drawNotifiedPrompt)
  241. {
  242. InitializeSettins();
  243. ignoredSettingsCount = 0;
  244. shouldNotifiedSettingsCount = 0;
  245. notifiedSettingsCount = 0;
  246. var hasSettingsAccepted = false;
  247. foreach (var setting in s_settings)
  248. {
  249. if (setting.SkipCheck()) { continue; }
  250. setting.UpdateCurrentValue();
  251. var isIgnored = setting.IsIgnored();
  252. if (isIgnored) { ++ignoredSettingsCount; }
  253. if (setting.IsUsingRecommendedValue()) { continue; }
  254. else { ++shouldNotifiedSettingsCount; }
  255. if (!isIgnored)
  256. {
  257. ++notifiedSettingsCount;
  258. if (drawNotifiedPrompt)
  259. {
  260. if (notifiedSettingsCount == 1)
  261. {
  262. EditorGUILayout.HelpBox("Recommended project settings:", MessageType.Warning);
  263. settingScrollPosition = GUILayout.BeginScrollView(settingScrollPosition, GUILayout.ExpandHeight(true));
  264. }
  265. hasSettingsAccepted |= setting.DoDrawRecommend();
  266. }
  267. }
  268. }
  269. return hasSettingsAccepted;
  270. }
  271. // Open recommended setting window (with possible new version prompt)
  272. // won't do any thing if the window is already opened
  273. public static void TryOpenRecommendedSettingWindow()
  274. {
  275. if (recommendedWindowOpened) { return; }
  276. windowInstance = GetWindow<VIUVersionCheck>(true, "Vive Input Utility");
  277. windowInstance.minSize = new Vector2(240f, 550f);
  278. var rect = windowInstance.position;
  279. windowInstance.position = new Rect(Mathf.Max(rect.x, 50f), Mathf.Max(rect.y, 50f), rect.width, 200f + (showNewVersion ? 700f : 400f));
  280. }
  281. private static DateTime UtcDateTimeFromStr(string str)
  282. {
  283. var utcTicks = default(long);
  284. if (string.IsNullOrEmpty(str) || !long.TryParse(str, out utcTicks)) { return DateTime.MinValue; }
  285. return new DateTime(utcTicks, DateTimeKind.Utc);
  286. }
  287. private static string UtcDateTimeToStr(DateTime utcDateTime)
  288. {
  289. return utcDateTime.Ticks.ToString();
  290. }
  291. private static UnityWebRequest GetUnityWebRequestAndSend(string url)
  292. {
  293. var webReq = new UnityWebRequest(url);
  294. #if UNITY_2017_2_OR_NEWER
  295. webReq.SendWebRequest();
  296. #elif UNITY_5_4_OR_NEWER
  297. webReq.Send();
  298. #endif
  299. return webReq;
  300. }
  301. private static string GetWebText(UnityWebRequest wr)
  302. {
  303. #if UNITY_5_4_OR_NEWER
  304. return wr != null && wr.downloadHandler != null ? wr.downloadHandler.text : string.Empty;
  305. #else
  306. return wr != null ? wr.text : string.Empty;
  307. #endif
  308. }
  309. private static bool TryGetWebHeaderValue(UnityWebRequest wr, string headerKey, out string headerValue)
  310. {
  311. #if UNITY_5_4_OR_NEWER
  312. headerValue = wr.GetResponseHeader(headerKey);
  313. return string.IsNullOrEmpty(headerValue);
  314. #else
  315. if (wr.responseHeaders == null) { headerValue = string.Empty; return false; }
  316. return wr.responseHeaders.TryGetValue(headerKey, out headerValue);
  317. #endif
  318. }
  319. private static bool UrlSuccess(UnityWebRequest wr)
  320. {
  321. try
  322. {
  323. if (wr == null) { return false; }
  324. if (!string.IsNullOrEmpty(wr.error))
  325. {
  326. // API rate limit exceeded, see https://developer.github.com/v3/#rate-limiting
  327. Debug.Log("url:" + wr.url);
  328. Debug.Log("error:" + wr.error);
  329. Debug.Log(GetWebText(wr));
  330. string responseHeader;
  331. if (TryGetWebHeaderValue(wr, "X-RateLimit-Limit", out responseHeader))
  332. {
  333. Debug.Log("X-RateLimit-Limit:" + responseHeader);
  334. }
  335. if (TryGetWebHeaderValue(wr, "X-RateLimit-Remaining", out responseHeader))
  336. {
  337. Debug.Log("X-RateLimit-Remaining:" + responseHeader);
  338. }
  339. if (TryGetWebHeaderValue(wr, "X-RateLimit-Reset", out responseHeader))
  340. {
  341. Debug.Log("X-RateLimit-Reset:" + TimeZone.CurrentTimeZone.ToLocalTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(double.Parse(responseHeader))).ToString());
  342. }
  343. VersionCheckLog("Failed. Rate limit exceeded");
  344. return false;
  345. }
  346. if (Regex.IsMatch(GetWebText(wr), "404 not found", RegexOptions.IgnoreCase))
  347. {
  348. Debug.Log("url:" + wr.url);
  349. Debug.Log("error:" + wr.error);
  350. Debug.Log(GetWebText(wr));
  351. VersionCheckLog("Failed. 404 not found");
  352. return false;
  353. }
  354. }
  355. catch (Exception e)
  356. {
  357. Debug.LogWarning(e);
  358. VersionCheckLog("Failed. " + e.ToString());
  359. return false;
  360. }
  361. return true;
  362. }
  363. private string GetResourcePath()
  364. {
  365. var ms = MonoScript.FromScriptableObject(this);
  366. var path = AssetDatabase.GetAssetPath(ms);
  367. path = Path.GetDirectoryName(path);
  368. return path.Substring(0, path.Length - "Scripts/Editor".Length) + "Textures/";
  369. }
  370. public void OnGUI()
  371. {
  372. #if UNITY_2017_1_OR_NEWER
  373. if (EditorApplication.isCompiling)
  374. {
  375. EditorGUILayout.LabelField("Compiling...");
  376. return;
  377. }
  378. #endif
  379. if (viuLogo == null)
  380. {
  381. var currentDir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(MonoScript.FromScriptableObject(this)));
  382. var texturePath = currentDir.Substring(0, currentDir.Length - "Scripts/Editor".Length) + "Textures/VIU_logo.png";
  383. viuLogo = AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath);
  384. }
  385. if (viuLogo != null)
  386. {
  387. GUI.DrawTexture(GUILayoutUtility.GetRect(position.width, 124, GUI.skin.box), viuLogo, ScaleMode.ScaleToFit);
  388. }
  389. if (showNewVersion)
  390. {
  391. EditorGUILayout.HelpBox("New version available:", MessageType.Warning);
  392. GUILayout.Label("Current version: " + VIUVersion.current);
  393. GUILayout.Label("New version: " + latestVersion);
  394. if (!string.IsNullOrEmpty(latestRepoInfo.body))
  395. {
  396. GUILayout.Label("Release notes:");
  397. releaseNoteScrollPosition = GUILayout.BeginScrollView(releaseNoteScrollPosition, GUILayout.Height(250f));
  398. EditorGUILayout.HelpBox(latestRepoInfo.body, MessageType.None);
  399. GUILayout.EndScrollView();
  400. }
  401. GUILayout.BeginHorizontal();
  402. {
  403. if (GUILayout.Button(new GUIContent("Get Latest Version", "Goto " + pluginUrl)))
  404. {
  405. Application.OpenURL(pluginUrl);
  406. }
  407. GUILayout.FlexibleSpace();
  408. toggleSkipThisVersion = GUILayout.Toggle(toggleSkipThisVersion, "Do not prompt for this version again.");
  409. }
  410. GUILayout.EndHorizontal();
  411. }
  412. var hasSettingsAccepted = UpdateIgnoredNotifiedSettingsCount(true);
  413. if (notifiedSettingsCount > 0)
  414. {
  415. GUILayout.EndScrollView();
  416. if (ignoredSettingsCount > 0)
  417. {
  418. if (GUILayout.Button("Clear All Ignores(" + ignoredSettingsCount + ")"))
  419. {
  420. foreach (var setting in s_settings) { setting.DeleteIgnore(); }
  421. }
  422. }
  423. GUILayout.BeginHorizontal();
  424. {
  425. if (GUILayout.Button("Accept All(" + notifiedSettingsCount + ")"))
  426. {
  427. for (int i = 10; i >= 0 && notifiedSettingsCount > 0; --i)
  428. {
  429. foreach (var setting in s_settings) { if (!setting.SkipCheck() && !setting.IsIgnored() && !setting.IsUsingRecommendedValue()) { setting.AcceptRecommendValue(); } }
  430. VIUSettingsEditor.ApplySDKChanges();
  431. UpdateIgnoredNotifiedSettingsCount(false);
  432. }
  433. hasSettingsAccepted = true;
  434. }
  435. if (GUILayout.Button("Ignore All(" + notifiedSettingsCount + ")"))
  436. {
  437. foreach (var setting in s_settings) { if (!setting.SkipCheck() && !setting.IsIgnored() && !setting.IsUsingRecommendedValue()) { setting.DoIgnore(); } }
  438. }
  439. }
  440. GUILayout.EndHorizontal();
  441. }
  442. else if (shouldNotifiedSettingsCount > 0)
  443. {
  444. EditorGUILayout.HelpBox("Some recommended settings ignored.", MessageType.Warning);
  445. GUILayout.FlexibleSpace();
  446. if (GUILayout.Button("Clear All Ignores(" + ignoredSettingsCount + ")"))
  447. {
  448. foreach (var setting in s_settings) { setting.DeleteIgnore(); }
  449. }
  450. }
  451. else
  452. {
  453. EditorGUILayout.HelpBox("All recommended settings applied.", MessageType.Info);
  454. GUILayout.FlexibleSpace();
  455. }
  456. VIUSettingsEditor.ApplySDKChanges();
  457. if (VIUProjectSettings.hasChanged)
  458. {
  459. // save ignore keys
  460. VIUProjectSettings.Save();
  461. }
  462. if (GUILayout.Button("Close"))
  463. {
  464. Close();
  465. }
  466. if (hasSettingsAccepted)
  467. {
  468. VRModuleManagement.VRModuleManagerEditor.UpdateScriptingDefineSymbols();
  469. }
  470. }
  471. private void OnDestroy()
  472. {
  473. if (viuLogo != null)
  474. {
  475. viuLogo = null;
  476. }
  477. if (showNewVersion && toggleSkipThisVersion && !string.IsNullOrEmpty(ignoreThisVersionKey))
  478. {
  479. showNewVersion = false;
  480. VIUProjectSettings.AddIgnoreKey(ignoreThisVersionKey);
  481. VIUProjectSettings.Save();
  482. }
  483. if (windowInstance == this)
  484. {
  485. windowInstance = null;
  486. }
  487. }
  488. }
  489. }