From ed8e81a52ca1bd06631953a708dbf09b533edcf3 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 22 Aug 2024 15:52:06 +0900 Subject: [PATCH 1/5] chore: rename old updater to CheckForUpdateOld --- Editor/AssetDescription.cs | 2 +- Editor/CheckForUpdate.cs | 7 +++---- Editor/OptimizerPlugin.cs | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Editor/AssetDescription.cs b/Editor/AssetDescription.cs index c740bfd47..e3754d265 100644 --- a/Editor/AssetDescription.cs +++ b/Editor/AssetDescription.cs @@ -64,7 +64,7 @@ public override void OnInspectorGUI() EditorGUILayout.LabelField(AAOL10N.Tr("AssetDescription:Description"), EditorStyles.wordWrappedLabel); if (GUILayout.Button(AAOL10N.Tr("AssetDescription:OpenDocs"))) { - var baseUrl = CheckForUpdate.IsBeta ? "https://vpm.anatawa12.com/avatar-optimizer/beta/" : "https://vpm.anatawa12.com/avatar-optimizer/"; + var baseUrl = CheckForUpdateOld.IsBeta ? "https://vpm.anatawa12.com/avatar-optimizer/beta/" : "https://vpm.anatawa12.com/avatar-optimizer/"; var isJapanese = LanguagePrefs.Language == "ja"; baseUrl += isJapanese ? "ja/" : "en/"; baseUrl += "developers/asset-description/"; diff --git a/Editor/CheckForUpdate.cs b/Editor/CheckForUpdate.cs index e321b79b5..8382b8f13 100644 --- a/Editor/CheckForUpdate.cs +++ b/Editor/CheckForUpdate.cs @@ -11,9 +11,9 @@ namespace Anatawa12.AvatarOptimizer { [InitializeOnLoad] - internal static class CheckForUpdate + internal static class CheckForUpdateOld { - static CheckForUpdate() + static CheckForUpdateOld() { EditorApplication.delayCall += DoCheckForUpdate; } @@ -137,7 +137,6 @@ static async Task GetLatestVersion(bool beta, string version) { // we successfully fetched latest version! EditorPrefs.SetString(latestVersionKey, fetchedLatestVersion); - EditorPrefs.SetString(checkedWithKey, CurrentVersionName); EditorPrefs.SetString(updatedAtKey, DateTime.UtcNow.ToString("O")); return fetchedLatestVersion; } @@ -161,4 +160,4 @@ class PackageJson public string version; } } -} \ No newline at end of file +} diff --git a/Editor/OptimizerPlugin.cs b/Editor/OptimizerPlugin.cs index 3e300f31c..3b0ea2443 100644 --- a/Editor/OptimizerPlugin.cs +++ b/Editor/OptimizerPlugin.cs @@ -21,9 +21,9 @@ protected override void Configure() { // we skip check for update var components = ctx.AvatarRootObject.GetComponentInChildren(true); - if (components && CheckForUpdate.OutOfDate) + if (components && CheckForUpdateOld.OutOfDate) BuildLog.LogInfo("CheckForUpdate:out-of-date", - CheckForUpdate.LatestVersionName, CheckForUpdate.CurrentVersionName); + CheckForUpdateOld.LatestVersionName, CheckForUpdateOld.CurrentVersionName); }) .Then.Run(Processors.UnusedBonesByReferencesToolEarlyProcessor.Instance) .Then.Run("Early: MakeChildren", From ca5c6d722da05c8725782ef9f90ecd532c30f833 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 22 Aug 2024 18:24:48 +0900 Subject: [PATCH 2/5] feat: new check for update system for unity version difference --- Editor/AssetDescription.cs | 2 +- Editor/CheckForUpdate.cs | 163 ------------------ Editor/CheckForUpdate.cs.meta | 3 - Editor/CheckForUpdate.meta | 3 + Editor/CheckForUpdate/CheckForUpdate.cs | 108 ++++++++++++ Editor/CheckForUpdate/CheckForUpdate.cs.meta | 3 + .../CheckForUpdate/CheckForUpdateContext.cs | 102 +++++++++++ .../CheckForUpdateContext.cs.meta | 3 + Editor/CheckForUpdate/Latest2TextFile.cs | 45 +++++ Editor/CheckForUpdate/Latest2TextFile.cs.meta | 3 + Editor/CheckForUpdate/MenuItems.cs | 45 +++++ Editor/CheckForUpdate/MenuItems.cs.meta | 3 + Editor/CheckForUpdate/UnityVersion.cs | 50 ++++++ Editor/CheckForUpdate/UnityVersion.cs.meta | 3 + Editor/CheckForUpdate/Version.cs | 105 +++++++++++ Editor/CheckForUpdate/Version.cs.meta | 3 + Editor/OptimizerPlugin.cs | 4 +- Test~/Basic/CheckForUpdateLatest2TextFile.cs | 16 ++ .../CheckForUpdateLatest2TextFile.cs.meta | 3 + Test~/Basic/CheckForUpdateVersion.cs | 74 ++++++++ Test~/Basic/CheckForUpdateVersion.cs.meta | 3 + 21 files changed, 575 insertions(+), 169 deletions(-) delete mode 100644 Editor/CheckForUpdate.cs delete mode 100644 Editor/CheckForUpdate.cs.meta create mode 100644 Editor/CheckForUpdate.meta create mode 100644 Editor/CheckForUpdate/CheckForUpdate.cs create mode 100644 Editor/CheckForUpdate/CheckForUpdate.cs.meta create mode 100644 Editor/CheckForUpdate/CheckForUpdateContext.cs create mode 100644 Editor/CheckForUpdate/CheckForUpdateContext.cs.meta create mode 100644 Editor/CheckForUpdate/Latest2TextFile.cs create mode 100644 Editor/CheckForUpdate/Latest2TextFile.cs.meta create mode 100644 Editor/CheckForUpdate/MenuItems.cs create mode 100644 Editor/CheckForUpdate/MenuItems.cs.meta create mode 100644 Editor/CheckForUpdate/UnityVersion.cs create mode 100644 Editor/CheckForUpdate/UnityVersion.cs.meta create mode 100644 Editor/CheckForUpdate/Version.cs create mode 100644 Editor/CheckForUpdate/Version.cs.meta create mode 100644 Test~/Basic/CheckForUpdateLatest2TextFile.cs create mode 100644 Test~/Basic/CheckForUpdateLatest2TextFile.cs.meta create mode 100644 Test~/Basic/CheckForUpdateVersion.cs create mode 100644 Test~/Basic/CheckForUpdateVersion.cs.meta diff --git a/Editor/AssetDescription.cs b/Editor/AssetDescription.cs index e3754d265..1de9536c3 100644 --- a/Editor/AssetDescription.cs +++ b/Editor/AssetDescription.cs @@ -64,7 +64,7 @@ public override void OnInspectorGUI() EditorGUILayout.LabelField(AAOL10N.Tr("AssetDescription:Description"), EditorStyles.wordWrappedLabel); if (GUILayout.Button(AAOL10N.Tr("AssetDescription:OpenDocs"))) { - var baseUrl = CheckForUpdateOld.IsBeta ? "https://vpm.anatawa12.com/avatar-optimizer/beta/" : "https://vpm.anatawa12.com/avatar-optimizer/"; + var baseUrl = CheckForUpdate.Checker.IsBeta ? "https://vpm.anatawa12.com/avatar-optimizer/beta/" : "https://vpm.anatawa12.com/avatar-optimizer/"; var isJapanese = LanguagePrefs.Language == "ja"; baseUrl += isJapanese ? "ja/" : "en/"; baseUrl += "developers/asset-description/"; diff --git a/Editor/CheckForUpdate.cs b/Editor/CheckForUpdate.cs deleted file mode 100644 index 8382b8f13..000000000 --- a/Editor/CheckForUpdate.cs +++ /dev/null @@ -1,163 +0,0 @@ -using System; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using JetBrains.Annotations; -using UnityEditor; -using UnityEngine; - -namespace Anatawa12.AvatarOptimizer -{ - [InitializeOnLoad] - internal static class CheckForUpdateOld - { - static CheckForUpdateOld() - { - EditorApplication.delayCall += DoCheckForUpdate; - } - - public static bool OutOfDate - { - get => SessionState.GetBool("com.anatawa12.avatar-optimizer.out-of-date", false); - private set => SessionState.SetBool("com.anatawa12.avatar-optimizer.out-of-date", value); - } - - public static string LatestVersionName - { - get => SessionState.GetString("com.anatawa12.avatar-optimizer.latest", ""); - private set => SessionState.SetString("com.anatawa12.avatar-optimizer.latest", value); - } - - public static string CurrentVersionName - { - get => SessionState.GetString("com.anatawa12.avatar-optimizer.current", ""); - private set => SessionState.SetString("com.anatawa12.avatar-optimizer.current", value); - } - - public static bool IsBeta => CurrentVersionName.Contains("-"); - - static async void DoCheckForUpdate() - { - var currentVersion = GetCurrentVersion(); - if (currentVersion == null) - { - Debug.LogError("AAO CheckForUpdate: Failed to get current version"); - return; - } - - CurrentVersionName = currentVersion; - - var isBeta = currentVersion.Contains("-"); - var latestVersion = await GetLatestVersion(isBeta, currentVersion); - LatestVersionName = latestVersion; - - var outOf = OutOfDate = latestVersion != currentVersion; - if (outOf) - { - Debug.Log("AAO CheckForUpdate: Out of date detected! " + - $"current version: {currentVersion}, latest version: {latestVersion}"); - } - } - - [CanBeNull] - static string GetCurrentVersion() - { - try - { - var packageJson = - JsonUtility.FromJson( - File.ReadAllText("Packages/com.anatawa12.avatar-optimizer/package.json")); - if (packageJson.version == null) return null; - if (!VersionRegex.IsMatch(packageJson.version)) return null; - return packageJson.version; - } - catch (Exception e) - { - Debug.LogException(e); - return null; - } - } - - [ItemCanBeNull] - static async Task GetLatestVersion(bool beta, string version) - { - var latestVersionUrl = - beta - ? "https://vpm.anatawa12.com/avatar-optimizer/beta/latest.txt" - : "https://vpm.anatawa12.com/avatar-optimizer/latest.txt"; - - var keyPrefix = - beta - ? "com.anatawa12.avatar-optimizer.beta.latest" - : "com.anatawa12.avatar-optimizer.latest"; - var updatedAtKey = $"{keyPrefix}.updated"; - var checkedWithKey = $"{keyPrefix}.checked-with"; - var latestVersionKey = $"{keyPrefix}.value"; - - // fetch cached version - var cachedVersion = EditorPrefs.GetString(latestVersionKey); - if (!VersionRegex.IsMatch(cachedVersion)) - cachedVersion = null; - - if (cachedVersion != null - && EditorPrefs.GetString(checkedWithKey, "") == CurrentVersionName - && DateTime.TryParse(EditorPrefs.GetString(updatedAtKey, ""), out var updatedAt) - && updatedAt >= DateTime.UtcNow - TimeSpan.FromHours(1)) - { - // it looks cached version is not out of date - return cachedVersion; - } - - // out of date or invalid cached version - - string fetchedLatestVersion = null; - try - { - using (var client = new HttpClient()) - { - client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", - $"AvatarOptimizer-UpdateCheck/{version}"); - var response = await client.GetAsync(latestVersionUrl); - var responseText = await response.Content.ReadAsStringAsync(); - if (response.StatusCode != HttpStatusCode.OK) - throw new Exception($"Non OK response from remote: {response.StatusCode}\n\n{responseText}"); - responseText = responseText.Trim(); - if (VersionRegex.IsMatch(responseText)) - fetchedLatestVersion = responseText; - } - } - catch (Exception e) - { - Debug.LogException(e); - } - - if (fetchedLatestVersion != null) - { - // we successfully fetched latest version! - EditorPrefs.SetString(latestVersionKey, fetchedLatestVersion); - EditorPrefs.SetString(updatedAtKey, DateTime.UtcNow.ToString("O")); - return fetchedLatestVersion; - } - - if (cachedVersion != null) - { - Debug.Log("AAO CheckForUpdate: Failed to fetch latest version. fall back to outdated version"); - // we failed to update cached version so fallback to cached one - return cachedVersion; - } - - return null; - } - - private static readonly Regex VersionRegex = new Regex( - @"\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?"); - - [Serializable] - class PackageJson - { - public string version; - } - } -} diff --git a/Editor/CheckForUpdate.cs.meta b/Editor/CheckForUpdate.cs.meta deleted file mode 100644 index caa9ec3d4..000000000 --- a/Editor/CheckForUpdate.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 5bc56234f1374b569bc25f0b1350a41e -timeCreated: 1696906299 \ No newline at end of file diff --git a/Editor/CheckForUpdate.meta b/Editor/CheckForUpdate.meta new file mode 100644 index 000000000..0e1d3db52 --- /dev/null +++ b/Editor/CheckForUpdate.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: da31e40c31d44048a536821831bd7dcf +timeCreated: 1724308385 \ No newline at end of file diff --git a/Editor/CheckForUpdate/CheckForUpdate.cs b/Editor/CheckForUpdate/CheckForUpdate.cs new file mode 100644 index 000000000..f6c374da1 --- /dev/null +++ b/Editor/CheckForUpdate/CheckForUpdate.cs @@ -0,0 +1,108 @@ +using System; +using System.IO; +using UnityEditor; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.CheckForUpdate +{ + [InitializeOnLoad] + internal static class Checker + { + static Checker() + { + EditorApplication.delayCall += DoCheckForUpdate; + } + + public static bool OutOfDate + { + get => SessionState.GetBool("com.anatawa12.avatar-optimizer.out-of-date", false); + private set => SessionState.SetBool("com.anatawa12.avatar-optimizer.out-of-date", value); + } + + public static string LatestVersionName + { + get => SessionState.GetString("com.anatawa12.avatar-optimizer.latest", ""); + private set => SessionState.SetString("com.anatawa12.avatar-optimizer.latest", value); + } + + public static string CurrentVersionName + { + get => SessionState.GetString("com.anatawa12.avatar-optimizer.current", ""); + private set => SessionState.SetString("com.anatawa12.avatar-optimizer.current", value); + } + + public static bool IsBeta => CurrentVersionName.Contains("-"); + + static async void DoCheckForUpdate() + { + if (!MenuItems.CheckForUpdateEnabled) + { + // if disabled, do nothing + OutOfDate = false; + return; + } + + if (!(GetCurrentVersion() is Version currentVersion)) + { + Debug.LogError("Avatar Optimizer CheckForUpdate: Failed to get current version"); + return; + } + + CurrentVersionName = currentVersion.ToString(); + + var isBeta = currentVersion.IsPrerelease || MenuItems.ForceBetaChannel; + if (!UnityVersion.TryParse(Application.unityVersion, out var unityVersion)) + { + Debug.LogError("Avatar Optimizer CheckForUpdate: Failed to get unity version"); + return; + } + + var ctx = new CheckForUpdateContext(isBeta, currentVersion, unityVersion); + + if (await ctx.GetLatestVersion() is Version latestVersion) + { + // there is known latest version + if (currentVersion < latestVersion) + { + OutOfDate = true; + LatestVersionName = latestVersion.ToString(); + + Debug.Log("AAO CheckForUpdate: Out of date detected! " + + $"current version: {currentVersion}, latest version: {latestVersion}"); + } + else + { + OutOfDate = false; + } + } + else + { + OutOfDate = false; + } + } + + static Version? GetCurrentVersion() + { + try + { + var packageJson = + JsonUtility.FromJson( + File.ReadAllText("Packages/com.anatawa12.avatar-optimizer/package.json")); + if (packageJson.version == null) return null; + if (!Version.TryParse(packageJson.version, out var version)) return null; + return version; + } + catch (Exception e) + { + Debug.LogException(e); + return null; + } + } + + [Serializable] + class PackageJson + { + public string version; + } + } +} diff --git a/Editor/CheckForUpdate/CheckForUpdate.cs.meta b/Editor/CheckForUpdate/CheckForUpdate.cs.meta new file mode 100644 index 000000000..b02932835 --- /dev/null +++ b/Editor/CheckForUpdate/CheckForUpdate.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4d5c853d0e384657a5273f6c0a55e923 +timeCreated: 1724309343 \ No newline at end of file diff --git a/Editor/CheckForUpdate/CheckForUpdateContext.cs b/Editor/CheckForUpdate/CheckForUpdateContext.cs new file mode 100644 index 000000000..05d3fbbe1 --- /dev/null +++ b/Editor/CheckForUpdate/CheckForUpdateContext.cs @@ -0,0 +1,102 @@ +using System; +using System.Globalization; +using System.Net; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using JetBrains.Annotations; +using UnityEditor; +using UnityEngine; + +namespace Anatawa12.AvatarOptimizer.CheckForUpdate +{ + /// + /// This is a context for check for update. + /// + internal struct CheckForUpdateContext + { + public readonly bool Beta; + public readonly Version CurrentVersion; + public readonly UnityVersion UnityVersion; + + public CheckForUpdateContext(bool beta, Version currentVersion, UnityVersion unityVersion) : this() + { + Beta = beta; + CurrentVersion = currentVersion; + UnityVersion = unityVersion; + } + + const string KeyPrefix = "com.anatawa12.avatar-optimizer.check-for-update.v2"; + private string Channel => Beta ? "beta" : "stable"; + private string ValueKey => $"{KeyPrefix}.{UnityVersion}.{Channel}.value"; + private string UpdatedAtKey => $"{KeyPrefix}.{UnityVersion}.{Channel}.updated"; + + private string LatestVersionUrl => Beta + ? "https://vpm.anatawa12.com/avatar-optimizer/beta/latest2.txt" + : "https://vpm.anatawa12.com/avatar-optimizer/latest2.txt"; + + public async Task GetLatestVersion() + { + // first, try cache + var (cachedLatest, cacheOutdated) = TryGetVersionFromCache(); + + if (cachedLatest is Version cacheLoaded && !cacheOutdated) return cacheLoaded; + + // then try fetch + var latestInfo = await FetchLatest2(); + if (latestInfo != null) + { + var latestFromRemote = latestInfo.LatestFor(UnityVersion); + if (latestFromRemote != null) + { + // if success, save to cache and return + EditorPrefs.SetString(ValueKey, latestFromRemote.ToString()); + EditorPrefs.SetString(UpdatedAtKey, DateTime.Now.ToString(CultureInfo.InvariantCulture)); + return latestFromRemote; + } + } + + // if cache fails, use cache + return cachedLatest; + } + + private (Version? loaded, bool outdated) TryGetVersionFromCache() + { + var cachedLatest = EditorPrefs.GetString(ValueKey, ""); + var updatedAt = EditorPrefs.GetString(UpdatedAtKey, ""); + + if (!Version.TryParse(cachedLatest, out var version)) return (null, true); + if (!DateTime.TryParse(updatedAt, out var updated)) return (null, true); + + var outDate = DateTime.Now - updated > TimeSpan.FromHours(1); + return (version, outDate); + + } + + [ItemCanBeNull] + private async Task FetchLatest2() + { + // latest version:unity version + string responseText; + try + { + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", + $"AvatarOptimizer-UpdateCheck/{CurrentVersion}"); + var response = await client.GetAsync(LatestVersionUrl); + responseText = await response.Content.ReadAsStringAsync(); + if (response.StatusCode != HttpStatusCode.OK) + throw new Exception( + $"Non OK response from remote: {response.StatusCode}\n\n{responseText}"); + return Latest2TextFile.Parse(responseText); + } + } + catch (Exception e) + { + Debug.LogException(e); + return null; + } + } + } +} diff --git a/Editor/CheckForUpdate/CheckForUpdateContext.cs.meta b/Editor/CheckForUpdate/CheckForUpdateContext.cs.meta new file mode 100644 index 000000000..4e3f14ec9 --- /dev/null +++ b/Editor/CheckForUpdate/CheckForUpdateContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3c3291977cef4dfaa96ab1c948e878d0 +timeCreated: 1724309566 \ No newline at end of file diff --git a/Editor/CheckForUpdate/Latest2TextFile.cs b/Editor/CheckForUpdate/Latest2TextFile.cs new file mode 100644 index 000000000..80f984cd7 --- /dev/null +++ b/Editor/CheckForUpdate/Latest2TextFile.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace Anatawa12.AvatarOptimizer.CheckForUpdate +{ + internal class Latest2TextFile + { + private (Version latest, UnityVersion minUnityVersion)[] _lines; + + public Latest2TextFile((Version latest, UnityVersion minUnityVersion)[] lines) => _lines = lines; + + public static Latest2TextFile Parse(string text) + { + var result = new List<(Version latest, UnityVersion minUnityVersion)>(); + foreach (var lineRaw in text.Split('\n')) + { + var line = lineRaw.Trim(); + if (line == "") break; + + var parts = line.Split(':'); + if (parts.Length < 2) continue; // invalid + + if (!Version.TryParse(parts[0], out var version)) continue; + if (!UnityVersion.TryParse(parts[1], out var unityVersion)) continue; + + result.Add((version, unityVersion)); + } + + var parsedLines = result.ToArray(); + + // sort to the newest first + Array.Sort(parsedLines, (a, b) => -a.latest.CompareTo(b.latest)); + + return new Latest2TextFile(parsedLines); + } + + public Version? LatestFor(UnityVersion unityVersion) + { + foreach (var (latest, minUnityVersion) in _lines) + if (minUnityVersion <= unityVersion) return latest; + + return null; + } + } +} diff --git a/Editor/CheckForUpdate/Latest2TextFile.cs.meta b/Editor/CheckForUpdate/Latest2TextFile.cs.meta new file mode 100644 index 000000000..7d5b781af --- /dev/null +++ b/Editor/CheckForUpdate/Latest2TextFile.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 99bd24ebfa6d4978b8cde83db1e2697e +timeCreated: 1724310247 \ No newline at end of file diff --git a/Editor/CheckForUpdate/MenuItems.cs b/Editor/CheckForUpdate/MenuItems.cs new file mode 100644 index 000000000..06fe8125b --- /dev/null +++ b/Editor/CheckForUpdate/MenuItems.cs @@ -0,0 +1,45 @@ +using UnityEditor; + +namespace Anatawa12.AvatarOptimizer.CheckForUpdate +{ + [InitializeOnLoad] + internal class MenuItems + { + private const string ToggleCheckForUpdate = "Tools/Avatar Optimizer/Check for Update/Enabled"; + private const string ForceCheckForBetaRelease = "Tools/Avatar Optimizer/Check for Update/Check for Prerelease"; + private const string ToggleSettingName = "com.anatawa12.avatar-optimizer.check-for-update.enabled"; + private const string BetaSettingName = "com.anatawa12.avatar-optimizer.check-for-update.beta"; + + public static bool CheckForUpdateEnabled + { + get => EditorPrefs.GetBool(ToggleSettingName, true); + private set => EditorPrefs.SetBool(ToggleSettingName, value); + } + + public static bool ForceBetaChannel + { + get => EditorPrefs.GetBool(BetaSettingName, false); + private set => EditorPrefs.SetBool(BetaSettingName, value); + } + + [MenuItem(ToggleCheckForUpdate)] + private static void ToggleGenerateOnPlay() + { + CheckForUpdateEnabled = !CheckForUpdateEnabled; + Menu.SetChecked(ToggleCheckForUpdate, CheckForUpdateEnabled); + } + + [MenuItem(ForceCheckForBetaRelease)] + private static void ToggleBetaChannel() + { + ForceBetaChannel = !ForceBetaChannel; + Menu.SetChecked(ForceCheckForBetaRelease, ForceBetaChannel); + } + + static MenuItems() + { + EditorApplication.delayCall += () => Menu.SetChecked(ToggleCheckForUpdate, CheckForUpdateEnabled); + EditorApplication.delayCall += () => Menu.SetChecked(ForceCheckForBetaRelease, ForceBetaChannel); + } + } +} diff --git a/Editor/CheckForUpdate/MenuItems.cs.meta b/Editor/CheckForUpdate/MenuItems.cs.meta new file mode 100644 index 000000000..b1d22e908 --- /dev/null +++ b/Editor/CheckForUpdate/MenuItems.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5cd95a9625774b028daa477031ff6111 +timeCreated: 1724315123 \ No newline at end of file diff --git a/Editor/CheckForUpdate/UnityVersion.cs b/Editor/CheckForUpdate/UnityVersion.cs new file mode 100644 index 000000000..d2b1069c4 --- /dev/null +++ b/Editor/CheckForUpdate/UnityVersion.cs @@ -0,0 +1,50 @@ +using System; +using System.Text.RegularExpressions; + +namespace Anatawa12.AvatarOptimizer.CheckForUpdate +{ + internal struct UnityVersion : IEquatable, IComparable + { + public readonly int Major; + public readonly int Minor; + // minior, channel, increment are ignored + + public UnityVersion(int major, int minor) => (Major, Minor) = (major, minor); + + public bool Equals(UnityVersion other) => Major == other.Major && Minor == other.Minor; + public override bool Equals(object obj) => obj is UnityVersion other && Equals(other); + public override int GetHashCode() => HashCode.Combine(Major, Minor); + public static bool operator ==(UnityVersion left, UnityVersion right) => left.Equals(right); + public static bool operator !=(UnityVersion left, UnityVersion right) => !left.Equals(right); + + public int CompareTo(UnityVersion other) + { + var majorComparison = Major.CompareTo(other.Major); + if (majorComparison != 0) return majorComparison; + return Minor.CompareTo(other.Minor); + } + + public static bool operator <(UnityVersion left, UnityVersion right) => left.CompareTo(right) < 0; + public static bool operator >(UnityVersion left, UnityVersion right) => left.CompareTo(right) > 0; + public static bool operator <=(UnityVersion left, UnityVersion right) => left.CompareTo(right) <= 0; + public static bool operator >=(UnityVersion left, UnityVersion right) => left.CompareTo(right) >= 0; + + public override string ToString() => $"{Major}.{Minor}"; + + private static readonly Regex VersionRegex = new Regex(@"^(\d+)\.(\d+)(?:\..*)?$"); + + public static bool TryParse(string version, out UnityVersion result) + { + result = default; + + var match = VersionRegex.Match(version); + if (!match.Success) return false; + + if (!int.TryParse(match.Groups[1].Value, out var major)) return false; + if (!int.TryParse(match.Groups[2].Value, out var minor)) return false; + + result = new UnityVersion(major, minor); + return true; + } + } +} diff --git a/Editor/CheckForUpdate/UnityVersion.cs.meta b/Editor/CheckForUpdate/UnityVersion.cs.meta new file mode 100644 index 000000000..5befffb54 --- /dev/null +++ b/Editor/CheckForUpdate/UnityVersion.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7fce03d90ef04b808cb91e7ece1cf93f +timeCreated: 1724309728 \ No newline at end of file diff --git a/Editor/CheckForUpdate/Version.cs b/Editor/CheckForUpdate/Version.cs new file mode 100644 index 000000000..9d1a5a692 --- /dev/null +++ b/Editor/CheckForUpdate/Version.cs @@ -0,0 +1,105 @@ +using System; +using System.Linq; +using System.Text.RegularExpressions; + +namespace Anatawa12.AvatarOptimizer.CheckForUpdate +{ + internal struct Version : IComparable, IEquatable + { + public readonly int Major; + public readonly int Minor; + public readonly int Patch; + public readonly string Pre; + + public Version(int major, int minor, int patch) => (Major, Minor, Patch, Pre) = (major, minor, patch, ""); + + internal Version(int major, int minor, int patch, string pre) => + (Major, Minor, Patch, Pre) = (major, minor, patch, pre); + + public bool IsPrerelease => Pre != ""; + + public int CompareTo(Version other) + { + var majorComparison = Major.CompareTo(other.Major); + if (majorComparison != 0) return majorComparison; + var minorComparison = Minor.CompareTo(other.Minor); + if (minorComparison != 0) return minorComparison; + var patchComparison = Patch.CompareTo(other.Patch); + if (patchComparison != 0) return patchComparison; + // likely: in most case, prerelease is empty + if (Pre == "" && other.Pre == "") return 0; + + // if one of them is empty, the other is greater + if (Pre == "" && other.Pre != "") return 1; + if (Pre != "" && other.Pre == "") return -1; + + // compare pre-release now + var thisSplit = Pre.Split('.'); + var otherSplit = other.Pre.Split('.'); + + foreach (var (a, b) in thisSplit.Zip(otherSplit, (a, b) => (a, b))) + { + if (a == b) continue; + var aIsInt = int.TryParse(a, out var aInt); + var bIsInt = int.TryParse(b, out var bInt); + if (aIsInt && bIsInt) return aInt.CompareTo(bInt); + if (!aIsInt && bIsInt) return 1; + if (aIsInt && !bIsInt) return -1; + return string.Compare(a, b, StringComparison.Ordinal); + } + + if (thisSplit.Length < otherSplit.Length) return -1; + if (thisSplit.Length > otherSplit.Length) return 1; + + return 0; + } + + + private static readonly Regex VersionRegex = + new Regex(@"^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$"); + + public static bool TryParse(string version, out Version result) + { + result = default; + + var match = VersionRegex.Match(version); + if (!match.Success) return false; + + + if (!int.TryParse(match.Groups[1].Value, out var major)) return false; + if (!int.TryParse(match.Groups[2].Value, out var minor)) return false; + if (!int.TryParse(match.Groups[3].Value, out var patch)) return false; + + var pre = match.Groups[4].Value; + _ = match.Groups[5].Value; + + if (!(pre.Length == 0 || pre.Split('.').All(x => x != ""))) return false; // invalid pre-release + // we won't check for build-meta for now + + result = new Version(major, minor, patch, pre); + return true; + } + + public static Version Parse(string version) + { + if (!TryParse(version, out var result)) + throw new ArgumentException("invalid version format", nameof(version)); + return result; + } + + public bool Equals(Version other) => + Major == other.Major && Minor == other.Minor && Patch == other.Patch && Pre == other.Pre; + + public override bool Equals(object obj) => obj is Version other && Equals(other); + public override int GetHashCode() => HashCode.Combine(Major, Minor, Patch); + public static bool operator ==(Version left, Version right) => left.Equals(right); + public static bool operator !=(Version left, Version right) => !left.Equals(right); + public static bool operator <(Version left, Version right) => left.CompareTo(right) < 0; + public static bool operator >(Version left, Version right) => left.CompareTo(right) > 0; + public static bool operator <=(Version left, Version right) => left.CompareTo(right) <= 0; + public static bool operator >=(Version left, Version right) => left.CompareTo(right) >= 0; + + public override string ToString() => + Pre == "" ? $"{Major}.{Minor}.{Patch}" : $"{Major}.{Minor}.{Patch}-{Pre}"; + } +} diff --git a/Editor/CheckForUpdate/Version.cs.meta b/Editor/CheckForUpdate/Version.cs.meta new file mode 100644 index 000000000..16c2a51b6 --- /dev/null +++ b/Editor/CheckForUpdate/Version.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0528ee78d0004c07b693976335f67765 +timeCreated: 1724308393 \ No newline at end of file diff --git a/Editor/OptimizerPlugin.cs b/Editor/OptimizerPlugin.cs index 3b0ea2443..1f8d7eb86 100644 --- a/Editor/OptimizerPlugin.cs +++ b/Editor/OptimizerPlugin.cs @@ -21,9 +21,9 @@ protected override void Configure() { // we skip check for update var components = ctx.AvatarRootObject.GetComponentInChildren(true); - if (components && CheckForUpdateOld.OutOfDate) + if (components && CheckForUpdate.Checker.OutOfDate && CheckForUpdate.MenuItems.CheckForUpdateEnabled) BuildLog.LogInfo("CheckForUpdate:out-of-date", - CheckForUpdateOld.LatestVersionName, CheckForUpdateOld.CurrentVersionName); + CheckForUpdate.Checker.LatestVersionName, CheckForUpdate.Checker.CurrentVersionName); }) .Then.Run(Processors.UnusedBonesByReferencesToolEarlyProcessor.Instance) .Then.Run("Early: MakeChildren", diff --git a/Test~/Basic/CheckForUpdateLatest2TextFile.cs b/Test~/Basic/CheckForUpdateLatest2TextFile.cs new file mode 100644 index 000000000..e3044405b --- /dev/null +++ b/Test~/Basic/CheckForUpdateLatest2TextFile.cs @@ -0,0 +1,16 @@ +using Anatawa12.AvatarOptimizer.CheckForUpdate; +using NUnit.Framework; + +namespace Anatawa12.AvatarOptimizer.Test +{ + public class CheckForUpdateLatest2TextFile + { + [Test] + public static void Parse() + { + Assert.That(Latest2TextFile.Parse("1.7.0:2019.4\n1.8.0:2022.3\n").LatestFor(new UnityVersion(2019, 4)), Is.EqualTo(new Version(1, 7, 0))); + Assert.That(Latest2TextFile.Parse("1.7.0:2019.4\n1.8.0:2022.3\n").LatestFor(new UnityVersion(2022, 2)), Is.EqualTo(new Version(1, 7, 0))); + Assert.That(Latest2TextFile.Parse("1.7.0:2019.4\n1.8.0:2022.3\n").LatestFor(new UnityVersion(2022, 3)), Is.EqualTo(new Version(1, 8, 0))); + } + } +} diff --git a/Test~/Basic/CheckForUpdateLatest2TextFile.cs.meta b/Test~/Basic/CheckForUpdateLatest2TextFile.cs.meta new file mode 100644 index 000000000..e9e727a67 --- /dev/null +++ b/Test~/Basic/CheckForUpdateLatest2TextFile.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 694b335b1763413da9dd215cdf0c127c +timeCreated: 1724311069 \ No newline at end of file diff --git a/Test~/Basic/CheckForUpdateVersion.cs b/Test~/Basic/CheckForUpdateVersion.cs new file mode 100644 index 000000000..6a74319da --- /dev/null +++ b/Test~/Basic/CheckForUpdateVersion.cs @@ -0,0 +1,74 @@ +using Anatawa12.AvatarOptimizer.CheckForUpdate; +using NUnit.Framework; + +namespace Anatawa12.AvatarOptimizer.Test +{ + public class CheckForUpdateVersion + { + [Test] + public static void Parse() + { + Assert.That(Version.Parse("1.0.0"), Is.EqualTo(new Version(1, 0, 0))); + Assert.That(Version.Parse("1.0.0-alpha"), Is.EqualTo(new Version(1, 0, 0, "alpha"))); + Assert.That(Version.Parse("1.0.0+build"), Is.EqualTo(new Version(1, 0, 0, ""))); + Assert.That(Version.Parse("1.0.0-alpha+build"), Is.EqualTo(new Version(1, 0, 0, "alpha"))); + Assert.That(Version.Parse("1.0.0-alpha.1"), Is.EqualTo(new Version(1, 0, 0, "alpha.1"))); + Assert.That(Version.Parse("1.0.0-alpha.1+build"), Is.EqualTo(new Version(1, 0, 0, "alpha.1"))); + + // invalid versions should return false for TryParse + Assert.That(Version.TryParse("1", out _), Is.False); + Assert.That(Version.TryParse("1.0", out _), Is.False); + Assert.That(Version.TryParse("1.0.0-beta..1", out _), Is.False); + Assert.That(Version.TryParse("1.0.0-alpha.1+build+build", out _), Is.False); + } + + [Test] + public static void ToString() + { + Assert.That(new Version(1, 0, 0).ToString(), Is.EqualTo("1.0.0")); + Assert.That(new Version(1, 0, 0, "alpha").ToString(), Is.EqualTo("1.0.0-alpha")); + Assert.That(new Version(1, 0, 0, "alpha.1").ToString(), Is.EqualTo("1.0.0-alpha.1")); + } + + [Test] + public static void Compare() + { + Assert.That(new Version(1, 0, 0).CompareTo(new Version(1, 0, 0)), Is.Zero); + Assert.That(new Version(1, 0, 0).CompareTo(new Version(1, 0, 1)), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0).CompareTo(new Version(1, 1, 0)), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0).CompareTo(new Version(2, 0, 0)), Is.LessThan(0)); + + // prereleases are before the normal version, but after previous normal version + Assert.That(new Version(1, 0, 0, "alpha").CompareTo(new Version(1, 0, 0)), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "alpha").CompareTo(new Version(0, 99, 99)), Is.GreaterThan(0)); + + // compare Prereleases + Assert.That(new Version(1, 0, 0, "alpha").CompareTo(new Version(1, 0, 0, "alpha")), Is.Zero); + Assert.That(new Version(1, 0, 0, "alpha").CompareTo(new Version(1, 0, 0, "alpha.1")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "alpha.1").CompareTo(new Version(1, 0, 0, "alpha")), Is.GreaterThan(0)); + Assert.That(new Version(1, 0, 0, "alpha.1").CompareTo(new Version(1, 0, 0, "alpha.1")), Is.Zero); + Assert.That(new Version(1, 0, 0, "alpha.1").CompareTo(new Version(1, 0, 0, "alpha.2")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "alpha.2").CompareTo(new Version(1, 0, 0, "alpha.1")), Is.GreaterThan(0)); + Assert.That(new Version(1, 0, 0, "alpha.2").CompareTo(new Version(1, 0, 0, "alpha.2")), Is.Zero); + Assert.That(new Version(1, 0, 0, "alpha.2").CompareTo(new Version(1, 0, 0, "alpha.10")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "alpha.10").CompareTo(new Version(1, 0, 0, "alpha.2")), Is.GreaterThan(0)); + Assert.That(new Version(1, 0, 0, "alpha.10").CompareTo(new Version(1, 0, 0, "alpha.10")), Is.Zero); + Assert.That(new Version(1, 0, 0, "alpha").CompareTo(new Version(1, 0, 0, "beta")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "beta").CompareTo(new Version(1, 0, 0, "alpha")), Is.GreaterThan(0)); + Assert.That(new Version(1, 0, 0, "beta").CompareTo(new Version(1, 0, 0, "beta")), Is.Zero); + Assert.That(new Version(1, 0, 0, "beta").CompareTo(new Version(1, 0, 0, "beta.1")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "beta.1").CompareTo(new Version(1, 0, 0, "beta")), Is.GreaterThan(0)); + Assert.That(new Version(1, 0, 0, "beta.1").CompareTo(new Version(1, 0, 0, "beta.1")), Is.Zero); + + // copied example from semver spec + // 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 + Assert.That(new Version(1, 0, 0, "alpha").CompareTo(new Version(1, 0, 0, "alpha.1")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "alpha.1").CompareTo(new Version(1, 0, 0, "alpha.beta")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "alpha.beta").CompareTo(new Version(1, 0, 0, "beta")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "beta").CompareTo(new Version(1, 0, 0, "beta.2")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "beta.2").CompareTo(new Version(1, 0, 0, "beta.11")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "beta.11").CompareTo(new Version(1, 0, 0, "rc.1")), Is.LessThan(0)); + Assert.That(new Version(1, 0, 0, "rc.1").CompareTo(new Version(1, 0, 0)), Is.LessThan(0)); + } + } +} diff --git a/Test~/Basic/CheckForUpdateVersion.cs.meta b/Test~/Basic/CheckForUpdateVersion.cs.meta new file mode 100644 index 000000000..ae9ea037e --- /dev/null +++ b/Test~/Basic/CheckForUpdateVersion.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9c248957b4274b9fb15fd59848e10f3f +timeCreated: 1724308569 \ No newline at end of file From 5daa283bf121204185dcfc9befd5cce4753519ee Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 22 Aug 2024 20:27:51 +0900 Subject: [PATCH 3/5] build: create latest2.txt --- .docs/build-latest-txt.mjs | 30 ++++++++++++++++++++++++++++++ .docs/build.sh | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100755 .docs/build-latest-txt.mjs diff --git a/.docs/build-latest-txt.mjs b/.docs/build-latest-txt.mjs new file mode 100755 index 000000000..d561e6a6e --- /dev/null +++ b/.docs/build-latest-txt.mjs @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +import { writeFileSync } from 'node:fs'; + +const baseUrl = process.argv[2]; +const newVersion = process.argv[3]; +const requiredUnity = "2019.4"; + +const supports2019 = requiredUnity.startsWith("2019"); + +if (supports2019) { + writeFileSync("static/latest.txt", newVersion); +} + +const existingLatest2Txt = await fetch(`${baseUrl}/latest2.txt`); +if (!existingLatest2Txt.ok) throw new Error(`Failed to fetch latest2.txt: ${existingLatest2Txt.statusText}`); +const existingLatest2 = await existingLatest2Txt.text(); + +const parsed = existingLatest2.trimEnd().split("\n").map(x => x.split(':')); + +const sameUnityIndex = parsed.findIndex(x => x[1] === requiredUnity); +if (sameUnityIndex !== -1) { + parsed[sameUnityIndex][0] = newVersion; +} else { + parsed.push([newVersion, requiredUnity]); +} + +const newLatest2 = parsed.map(x => x.join(':') + '\n').join(''); + +writeFileSync("static/latest2.txt", newLatest2); diff --git a/.docs/build.sh b/.docs/build.sh index 85dd2f682..a802262a3 100755 --- a/.docs/build.sh +++ b/.docs/build.sh @@ -6,6 +6,6 @@ BASE_URL="$1" LATEST_VERSION="$2" mkdir -p static -echo "$LATEST_VERSION" > static/latest.txt +./build-latest-txt.mjs "$BASE_URL" "$LATEST_VERSION" hugo --minify --baseURL "$BASE_URL" From e9ccb9b2686e7c29bafdfaaa4ab99f0b3e27eeda Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 22 Aug 2024 20:30:02 +0900 Subject: [PATCH 4/5] docs(changelog): Rewritten Check for Update system --- CHANGELOG-PRERELEASE.md | 1 + CHANGELOG.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG-PRERELEASE.md b/CHANGELOG-PRERELEASE.md index d557b235d..fa61526c2 100644 --- a/CHANGELOG-PRERELEASE.md +++ b/CHANGELOG-PRERELEASE.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog]. ### Added ### Changed +- Rewritten Check for Update system `#1151` ### Deprecated diff --git a/CHANGELOG.md b/CHANGELOG.md index 15ee822e8..28a960697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog]. ### Added ### Changed +- Rewritten Check for Update system `#1151` ### Deprecated From fd8c09723b57e8f3bf53e7a19eb67f357be54b2d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 22 Aug 2024 20:35:09 +0900 Subject: [PATCH 5/5] fix: unity 2019 compatibility --- Editor/CheckForUpdate/UnityVersion.cs | 2 +- Editor/CheckForUpdate/Version.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Editor/CheckForUpdate/UnityVersion.cs b/Editor/CheckForUpdate/UnityVersion.cs index d2b1069c4..dfc75c5a1 100644 --- a/Editor/CheckForUpdate/UnityVersion.cs +++ b/Editor/CheckForUpdate/UnityVersion.cs @@ -13,7 +13,7 @@ internal struct UnityVersion : IEquatable, IComparable Major == other.Major && Minor == other.Minor; public override bool Equals(object obj) => obj is UnityVersion other && Equals(other); - public override int GetHashCode() => HashCode.Combine(Major, Minor); + public override int GetHashCode() => unchecked((Major * 397) ^ Minor); public static bool operator ==(UnityVersion left, UnityVersion right) => left.Equals(right); public static bool operator !=(UnityVersion left, UnityVersion right) => !left.Equals(right); diff --git a/Editor/CheckForUpdate/Version.cs b/Editor/CheckForUpdate/Version.cs index 9d1a5a692..7af3c7b8f 100644 --- a/Editor/CheckForUpdate/Version.cs +++ b/Editor/CheckForUpdate/Version.cs @@ -91,7 +91,19 @@ public bool Equals(Version other) => Major == other.Major && Minor == other.Minor && Patch == other.Patch && Pre == other.Pre; public override bool Equals(object obj) => obj is Version other && Equals(other); - public override int GetHashCode() => HashCode.Combine(Major, Minor, Patch); + + public override int GetHashCode() + { + unchecked + { + var hashCode = Major; + hashCode = (hashCode * 397) ^ Minor; + hashCode = (hashCode * 397) ^ Patch; + hashCode = (hashCode * 397) ^ Pre.GetHashCode(); + return hashCode; + } + } + public static bool operator ==(Version left, Version right) => left.Equals(right); public static bool operator !=(Version left, Version right) => !left.Equals(right); public static bool operator <(Version left, Version right) => left.CompareTo(right) < 0;