using System; using System.Net; using System.Text.RegularExpressions; using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEditor; using PackageInfo = UnityEditor.PackageManager.PackageInfo; namespace MCPForUnity.Editor.Services { /// /// Service for checking package updates from GitHub or Asset Store metadata /// public class PackageUpdateService : IPackageUpdateService { private const int DefaultRequestTimeoutMs = 3000; private const string LastCheckDateKey = EditorPrefKeys.LastUpdateCheck; private const string CachedVersionKey = EditorPrefKeys.LatestKnownVersion; private const string LastBetaCheckDateKey = EditorPrefKeys.LastUpdateCheck + ".beta"; private const string CachedBetaVersionKey = EditorPrefKeys.LatestKnownVersion + ".beta"; private const string LastAssetStoreCheckDateKey = EditorPrefKeys.LastAssetStoreUpdateCheck; private const string CachedAssetStoreVersionKey = EditorPrefKeys.LatestKnownAssetStoreVersion; private const string MainPackageJsonUrl = "https://raw.githubusercontent.com/CoplayDev/unity-mcp/main/MCPForUnity/package.json"; private const string BetaPackageJsonUrl = "https://raw.githubusercontent.com/CoplayDev/unity-mcp/beta/MCPForUnity/package.json"; private const string AssetStoreVersionUrl = "https://gqoqjkkptwfbkwyssmnj.supabase.co/storage/v1/object/public/coplay-images/assetstoreversion.json"; /// public UpdateCheckResult CheckForUpdate(string currentVersion) { bool isGitInstallation = IsGitInstallation(); string gitBranch = isGitInstallation ? GetGitUpdateBranch(currentVersion) : "main"; bool useBetaChannel = isGitInstallation && string.Equals(gitBranch, "beta", StringComparison.OrdinalIgnoreCase); string lastCheckKey = isGitInstallation ? (useBetaChannel ? LastBetaCheckDateKey : LastCheckDateKey) : LastAssetStoreCheckDateKey; string cachedVersionKey = isGitInstallation ? (useBetaChannel ? CachedBetaVersionKey : CachedVersionKey) : CachedAssetStoreVersionKey; string lastCheckDate = EditorPrefs.GetString(lastCheckKey, ""); string cachedLatestVersion = EditorPrefs.GetString(cachedVersionKey, ""); if (lastCheckDate == DateTime.Now.ToString("yyyy-MM-dd") && !string.IsNullOrEmpty(cachedLatestVersion)) { return new UpdateCheckResult { CheckSucceeded = true, LatestVersion = cachedLatestVersion, UpdateAvailable = IsNewerVersion(cachedLatestVersion, currentVersion), Message = "Using cached version check" }; } string latestVersion = isGitInstallation ? FetchLatestVersionFromGitHub(gitBranch) : FetchLatestVersionFromAssetStoreJson(); if (!string.IsNullOrEmpty(latestVersion)) { // Cache the result EditorPrefs.SetString(lastCheckKey, DateTime.Now.ToString("yyyy-MM-dd")); EditorPrefs.SetString(cachedVersionKey, latestVersion); return new UpdateCheckResult { CheckSucceeded = true, LatestVersion = latestVersion, UpdateAvailable = IsNewerVersion(latestVersion, currentVersion), Message = "Successfully checked for updates" }; } return new UpdateCheckResult { CheckSucceeded = false, UpdateAvailable = false, Message = isGitInstallation ? "Failed to check for updates (network issue or offline)" : "Failed to check for Asset Store updates (network issue or offline)" }; } /// public UpdateCheckResult TryGetCachedResult(string currentVersion) { bool isGitInstallation = IsGitInstallation(); string gitBranch = isGitInstallation ? GetGitUpdateBranch(currentVersion) : "main"; bool useBetaChannel = isGitInstallation && string.Equals(gitBranch, "beta", StringComparison.OrdinalIgnoreCase); string lastCheckKey = isGitInstallation ? (useBetaChannel ? LastBetaCheckDateKey : LastCheckDateKey) : LastAssetStoreCheckDateKey; string cachedVersionKey = isGitInstallation ? (useBetaChannel ? CachedBetaVersionKey : CachedVersionKey) : CachedAssetStoreVersionKey; string lastCheckDate = EditorPrefs.GetString(lastCheckKey, ""); string cachedLatestVersion = EditorPrefs.GetString(cachedVersionKey, ""); if (lastCheckDate == DateTime.Now.ToString("yyyy-MM-dd") && !string.IsNullOrEmpty(cachedLatestVersion)) { return new UpdateCheckResult { CheckSucceeded = true, LatestVersion = cachedLatestVersion, UpdateAvailable = IsNewerVersion(cachedLatestVersion, currentVersion), Message = "Using cached version check" }; } return null; } /// public UpdateCheckResult FetchAndCompare(string currentVersion) { bool isGitInstallation = IsGitInstallation(); string gitBranch = isGitInstallation ? GetGitUpdateBranch(currentVersion) : "main"; return FetchAndCompare(currentVersion, isGitInstallation, gitBranch); } /// public UpdateCheckResult FetchAndCompare(string currentVersion, bool isGitInstallation, string gitBranch) { string latestVersion = isGitInstallation ? FetchLatestVersionFromGitHub(gitBranch) : FetchLatestVersionFromAssetStoreJson(); if (!string.IsNullOrEmpty(latestVersion)) { return new UpdateCheckResult { CheckSucceeded = true, LatestVersion = latestVersion, UpdateAvailable = IsNewerVersion(latestVersion, currentVersion), Message = "Successfully checked for updates" }; } return new UpdateCheckResult { CheckSucceeded = false, UpdateAvailable = false, Message = isGitInstallation ? "Failed to check for updates (network issue or offline)" : "Failed to check for Asset Store updates (network issue or offline)" }; } /// public void CacheFetchResult(string currentVersion, string fetchedVersion) { if (string.IsNullOrEmpty(fetchedVersion)) return; bool isGitInstallation = IsGitInstallation(); string gitBranch = isGitInstallation ? GetGitUpdateBranch(currentVersion) : "main"; bool useBetaChannel = isGitInstallation && string.Equals(gitBranch, "beta", StringComparison.OrdinalIgnoreCase); string lastCheckKey = isGitInstallation ? (useBetaChannel ? LastBetaCheckDateKey : LastCheckDateKey) : LastAssetStoreCheckDateKey; string cachedVersionKey = isGitInstallation ? (useBetaChannel ? CachedBetaVersionKey : CachedVersionKey) : CachedAssetStoreVersionKey; EditorPrefs.SetString(lastCheckKey, DateTime.Now.ToString("yyyy-MM-dd")); EditorPrefs.SetString(cachedVersionKey, fetchedVersion); } /// public bool IsNewerVersion(string version1, string version2) { if (!TryParseVersion(version1, out var left) || !TryParseVersion(version2, out var right)) { return false; } return CompareVersions(left, right) > 0; } private static int CompareVersions(ParsedVersion left, ParsedVersion right) { int cmp = left.Major.CompareTo(right.Major); if (cmp != 0) return cmp; cmp = left.Minor.CompareTo(right.Minor); if (cmp != 0) return cmp; cmp = left.Patch.CompareTo(right.Patch); if (cmp != 0) return cmp; // Stable is newer than prerelease when core version matches. if (!left.IsPrerelease && right.IsPrerelease) return 1; if (left.IsPrerelease && !right.IsPrerelease) return -1; if (!left.IsPrerelease && !right.IsPrerelease) return 0; cmp = GetPrereleaseRank(left.PrereleaseLabel).CompareTo(GetPrereleaseRank(right.PrereleaseLabel)); if (cmp != 0) return cmp; cmp = left.PrereleaseNumber.CompareTo(right.PrereleaseNumber); if (cmp != 0) return cmp; return string.Compare(left.PrereleaseLabel, right.PrereleaseLabel, StringComparison.OrdinalIgnoreCase); } private static int GetPrereleaseRank(string label) { if (string.IsNullOrEmpty(label)) { return 0; } switch (label.ToLowerInvariant()) { case "a": case "alpha": return 1; case "b": case "beta": return 2; case "rc": return 3; case "preview": case "pre": return 4; default: return 5; } } private static bool TryParseVersion(string version, out ParsedVersion parsed) { parsed = default; if (string.IsNullOrWhiteSpace(version)) { return false; } string normalized = version.Trim().TrimStart('v', 'V'); var match = Regex.Match( normalized, @"^(?\d+)\.(?\d+)\.(?\d+)(?:-(?