using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using NugetForUnity.Models;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
using UnityEngine.Networking;
namespace NugetForUnity.Ui
{
///
/// Allows the user to check and perform updates of NuGetForUnity.
///
internal static class NuGetForUnityUpdater
{
private const string UpmPackageName = "com.github-glitchenzo.nugetforunity";
private const string UpmPackageGitUrl = "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity";
private const string GitHubReleasesPageUrl = "https://github.com/GlitchEnzo/NuGetForUnity/releases";
private const string GitHubReleasesApiUrl = "https://api.github.com/repos/GlitchEnzo/NuGetForUnity/releases?per_page=10";
///
/// Opens release notes for the current version.
///
[MenuItem("NuGet/Version " + NugetPreferences.NuGetForUnityVersion + " \uD83D\uDD17", false, 10)]
public static void DisplayVersion()
{
Application.OpenURL($"{GitHubReleasesPageUrl}/tag/v{NugetPreferences.NuGetForUnityVersion}");
}
///
/// Checks/launches the Releases page to update NuGetForUnity with a new version.
///
[MenuItem("NuGet/Check for Updates...", false, 11)]
public static void CheckForUpdates()
{
var request = UnityWebRequest.Get(GitHubReleasesApiUrl);
var operation = request.SendWebRequest();
NugetLogger.LogVerbose("HTTP GET {0}", GitHubReleasesApiUrl);
EditorUtility.DisplayProgressBar("Checking updates", null, 0.0f);
operation.completed += asyncOperation =>
{
try
{
string latestVersion = null;
string latestVersionDownloadUrl = null;
string response = null;
if (string.IsNullOrEmpty(request.error))
{
response = request.downloadHandler.text;
}
if (response != null)
{
latestVersion = GetLatestVersionFromReleasesApi(response, out latestVersionDownloadUrl);
}
EditorUtility.ClearProgressBar();
if (latestVersion == null)
{
EditorUtility.DisplayDialog(
"Unable to Determine Updates",
$"Couldn't find release information at {GitHubReleasesApiUrl}. Error: {request.error}",
"OK");
return;
}
var current = new NugetPackageIdentifier("NuGetForUnity", NugetPreferences.NuGetForUnityVersion);
var latest = new NugetPackageIdentifier("NuGetForUnity", latestVersion);
if (current >= latest)
{
EditorUtility.DisplayDialog(
"No Updates Available",
$"Your version of NuGetForUnity is up to date.\nVersion {NugetPreferences.NuGetForUnityVersion}.",
"OK");
return;
}
// New version is available. Give user options for installing it.
switch (EditorUtility.DisplayDialogComplex(
"Update Available",
$"Current Version: {NugetPreferences.NuGetForUnityVersion}\nLatest Version: {latestVersion}",
"Install Latest",
"Open Releases Page",
"Cancel"))
{
case 0:
_ = new NuGetForUnityUpdateInstaller(latestVersionDownloadUrl);
break;
case 1:
Application.OpenURL(GitHubReleasesPageUrl);
break;
case 2:
break;
}
}
finally
{
request.Dispose();
}
};
}
[CanBeNull]
private static string GetLatestVersionFromReleasesApi([NotNull] string response, [CanBeNull] out string unitypackageDownloadUrl)
{
// JsonUtility doesn't support top level arrays so we wrap it inside a object.
var releases = JsonUtility.FromJson(string.Concat("{ \"list\": ", response, " }"));
if (releases.list is null)
{
Debug.LogWarningFormat("Unable to parse releases response from '{0}', response:\n{1}", GitHubReleasesApiUrl, response);
unitypackageDownloadUrl = null;
return null;
}
foreach (var release in releases.list)
{
if (release.tag_name is null)
{
Debug.LogWarningFormat("Release returned from '{0}' has no 'tag_name', response:\n{1}", GitHubReleasesApiUrl, response);
continue;
}
// skip beta versions e.g. v2.0.0-preview
if (release.tag_name.Contains('-'))
{
continue;
}
unitypackageDownloadUrl = release.assets
?.Find(asset => asset.name != null && asset.name.EndsWith(".unitypackage", StringComparison.OrdinalIgnoreCase))
?.browser_download_url;
return release.tag_name.TrimStart('v');
}
unitypackageDownloadUrl = null;
return null;
}
///
/// Checks if NuGetForUnity is installed using UPM.
/// If installed using UPM we update the package.
/// If not we open the browser to download the .unitypackage.
///
private sealed class NuGetForUnityUpdateInstaller
{
[CanBeNull]
private readonly string latestVersionDownloadUrl;
[NotNull]
private readonly ListRequest upmPackageListRequest;
[CanBeNull]
private AddRequest upmPackageAddRequest;
public NuGetForUnityUpdateInstaller([CanBeNull] string latestVersionDownloadUrl)
{
this.latestVersionDownloadUrl = latestVersionDownloadUrl;
EditorUtility.DisplayProgressBar("NuGetForUnity update ...", null, 0.0f);
// get list of installed packages
upmPackageListRequest = Client.List(true);
EditorApplication.update += HandleListRequest;
}
private void HandleListRequest()
{
if (!upmPackageListRequest.IsCompleted)
{
return;
}
try
{
if (upmPackageListRequest.Status == StatusCode.Success &&
upmPackageListRequest.Result.Any(package => package.name == UpmPackageName))
{
// NuGetForUnity is installed as UPM package so we can just re-add it inside the package-manager to get update (see https://docs.unity3d.com/Manual/upm-git.html)
var packageInfo = upmPackageListRequest.Result.First(package => package.name == UpmPackageName);
if (packageInfo.source == UnityEditor.PackageManager.PackageSource.Git)
{
EditorUtility.DisplayProgressBar("NuGetForUnity update ...", "Installing with UPM (git url)", 0.1f);
upmPackageAddRequest = Client.Add(UpmPackageGitUrl);
}
else
{
EditorUtility.DisplayProgressBar("NuGetForUnity update ...", "Installing with UPM (OpenUPM)", 0.1f);
upmPackageAddRequest = Client.Add(UpmPackageName);
}
EditorApplication.update += HandleAddRequest;
}
else
{
EditorUtility.ClearProgressBar();
Application.OpenURL(string.IsNullOrEmpty(latestVersionDownloadUrl) ? GitHubReleasesPageUrl : latestVersionDownloadUrl);
}
}
finally
{
EditorApplication.update -= HandleListRequest;
}
}
private void HandleAddRequest()
{
if (upmPackageAddRequest != null && !upmPackageAddRequest.IsCompleted)
{
return;
}
EditorUtility.ClearProgressBar();
EditorApplication.update -= HandleAddRequest;
}
}
// ReSharper disable InconsistentNaming
#pragma warning disable 0649
#pragma warning disable SA1307 // Accessible fields should begin with upper-case letter
#pragma warning disable SA1401 // Fields should be private
#pragma warning disable SA1310 // Field names should not contain underscore
#pragma warning disable CA1051 // Do not declare visible instance fields
[Serializable]
private sealed class GitHubReleaseApiRequestList
{
[CanBeNull]
public List list;
}
[Serializable]
private sealed class GitHubReleaseApiRequest
{
[CanBeNull]
public List assets;
[CanBeNull]
public string tag_name;
}
[Serializable]
private sealed class GitHubAsset
{
[CanBeNull]
public string browser_download_url;
[CanBeNull]
public string name;
}
// ReSharper restore InconsistentNaming
#pragma warning restore CA1051 // Do not declare visible instance fields
#pragma warning restore SA1310 // Field names should not contain underscore
#pragma warning restore SA1401 // Fields should be private
#pragma warning restore SA1307 // Accessible fields should begin with upper-case letter
#pragma warning restore 0649
}
}