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+)(?:-(?