Skip to content

Notification on Rider package update availability #2223

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private FileSystemPath GetDocumentationRoot()
var contentsPath = UnityInstallationFinder.GetApplicationContentsPath(appPath);
var root = contentsPath.Combine("Documentation");
var englishRoot = root.Combine("en");
if (!englishRoot.ExistsDirectory && root.ExistsDirectory)
if (root.IsAbsolute && !englishRoot.ExistsDirectory && root.ExistsDirectory)
return root.GetChildDirectories().FirstOrDefault(englishRoot).ToNativeFileSystemPath();
return englishRoot.ToNativeFileSystemPath();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ private void AddGeneralSection()

AddBoolOption((UnitySettings s) => s.AllowAutomaticRefreshInUnity,
"Automatically refresh assets in Unity");

AddBoolOption((UnitySettings s) => s.AllowRiderUpdateNotifications,
"Notify when Rider package update is available");
}

private void AddCSharpSection()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using JetBrains.Application;
using JetBrains.Application.changes;
using JetBrains.Application.FileSystemTracker;
using JetBrains.Application.Settings;
using JetBrains.Application.Threading;
using JetBrains.Collections.Viewable;
using JetBrains.Lifetimes;
Expand All @@ -15,9 +13,6 @@
using JetBrains.Rd.Impl;
using JetBrains.RdBackend.Common.Features;
using JetBrains.ReSharper.Plugins.Unity.ProjectModel;
using JetBrains.ReSharper.Plugins.Unity.Settings;
using JetBrains.ReSharper.Psi.Util;
using JetBrains.Rider.Model.Notifications;
using JetBrains.Rider.Model.Unity.BackendUnity;
using JetBrains.Rider.Unity.Editor.NonUnity;
using JetBrains.Util;
Expand All @@ -35,22 +30,17 @@ public class BackendUnityProtocol
private readonly IScheduler myDispatcher;
private readonly IShellLocks myLocks;
private readonly ISolution mySolution;
private readonly UnityVersion myUnityVersion;
private readonly NotificationsModel myNotificationsModel;
private readonly IHostProductInfo myHostProductInfo;
private readonly FrontendBackendHost myFrontendBackendHost;
private readonly IContextBoundSettingsStoreLive myBoundSettingsStore;
private readonly UnityPluginInstaller myPluginInstaller;
private readonly JetHashSet<VirtualFileSystemPath> myPluginInstallations;

private DateTime myLastChangeTime;

public BackendUnityProtocol(Lifetime lifetime, ILogger logger,
BackendUnityHost backendUnityHost, FrontendBackendHost frontendBackendHost,
IScheduler dispatcher, IShellLocks locks, ISolution solution,
IApplicationWideContextBoundSettingStore settingsStore,
UnitySolutionTracker unitySolutionTracker,
UnityVersion unityVersion, NotificationsModel notificationsModel,
IHostProductInfo hostProductInfo, IFileSystemTracker fileSystemTracker)
BackendUnityHost backendUnityHost,
IScheduler dispatcher, IShellLocks locks, ISolution solution,
UnitySolutionTracker unitySolutionTracker,
IFileSystemTracker fileSystemTracker,
UnityPluginInstaller pluginInstaller)
{
myPluginInstallations = new JetHashSet<VirtualFileSystemPath>();

Expand All @@ -60,11 +50,7 @@ public BackendUnityProtocol(Lifetime lifetime, ILogger logger,
myDispatcher = dispatcher;
myLocks = locks;
mySolution = solution;
myUnityVersion = unityVersion;
myNotificationsModel = notificationsModel;
myHostProductInfo = hostProductInfo;
myFrontendBackendHost = frontendBackendHost;
myBoundSettingsStore = settingsStore.BoundSettingsStore;
myPluginInstaller = pluginInstaller;
mySessionLifetimes = new SequentialLifetimes(lifetime);

if (solution.GetData(ProjectModelExtensions.ProtocolSolutionKey) == null)
Expand Down Expand Up @@ -93,7 +79,8 @@ private void SafeExecuteOrQueueEx(string name, Action action)
private void OnProtocolInstanceJsonChange(FileSystemChangeDelta delta)
{
// Connect when protocols.json is updated (AppDomain start/reload in Unity editor)
if (delta.ChangeType != FileSystemChangeType.ADDED && delta.ChangeType != FileSystemChangeType.CHANGED) return;
if (delta.ChangeType != FileSystemChangeType.ADDED &&
delta.ChangeType != FileSystemChangeType.CHANGED) return;
if (!delta.NewPath.ExistsFile) return;
if (delta.NewPath.FileModificationTimeUtc == myLastChangeTime) return;
myLastChangeTime = delta.NewPath.FileModificationTimeUtc;
Expand All @@ -107,27 +94,27 @@ private void CreateProtocol(VirtualFileSystemPath protocolInstancePath)
if (protocolInstance == null)
return;

myLogger.Info($"EditorPlugin protocol port {protocolInstance.Port} for Solution: {protocolInstance.SolutionName}.");
myLogger.Info(
$"EditorPlugin protocol port {protocolInstance.Port} for Solution: {protocolInstance.SolutionName}.");

var thisSessionLifetime = mySessionLifetimes.Next();

if (protocolInstance.ProtocolGuid != ProtocolCompatibility.ProtocolGuid)
{
OnOutOfSync(myLifetime);
OnOutOfSync(thisSessionLifetime);
myLogger.Info("Avoid attempt to create protocol, incompatible.");
return;
}

try
{
var thisSessionLifetime = mySessionLifetimes.Next();
myLogger.Info("Create protocol...");

myLogger.Info("Creating SocketWire with port = {0}", protocolInstance.Port);
var wire = new SocketWire.Client(thisSessionLifetime, myDispatcher, protocolInstance.Port, "UnityClient")
{
BackwardsCompatibleWireFormat = true
};
var wire = new SocketWire.Client(thisSessionLifetime, myDispatcher, protocolInstance.Port,
"UnityClient") { BackwardsCompatibleWireFormat = true };

var protocol = new Rd.Impl.Protocol("UnityEditorPlugin", new Serializers(thisSessionLifetime, null, null),
var protocol = new Rd.Impl.Protocol("UnityEditorPlugin",
new Serializers(thisSessionLifetime, null, null),
new Identities(IdKind.Client), myDispatcher, wire, thisSessionLifetime)
{
ThrowErrorOnOutOfSyncModels = false
Expand Down Expand Up @@ -203,24 +190,7 @@ private void OnOutOfSync(Lifetime lifetime)
// avoid displaying Notification multiple times on each AppDomain.Reload in Unity
myPluginInstallations.Add(mySolution.SolutionFilePath);

var appVersion = myUnityVersion.ActualVersionForSolution.Value;
if (appVersion < new Version(2019, 2))
{
var entry = myBoundSettingsStore.Schema.GetScalarEntry((UnitySettings s) => s.InstallUnity3DRiderPlugin);
var isEnabled = myBoundSettingsStore.GetValueProperty<bool>(lifetime, entry, null).Value;
if (!isEnabled)
{
myFrontendBackendHost.Do(model => model.OnEditorModelOutOfSync());
}
}
else
{
var notification = new NotificationModel("Advanced Unity integration is unavailable",
$"Make sure Rider {myHostProductInfo.VersionMarketingString} is set as the External Editor in Unity preferences.",
true, RdNotificationEntryType.WARN, new List<NotificationHyperlink>());
mySolution.Locks.ExecuteOrQueue(lifetime, "OutOfSyncModels.Notify",
() => myNotificationsModel.Notification(notification));
}
myPluginInstaller.ShowOutOfSyncNotification(lifetime);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using JetBrains.Application.Notifications;
using JetBrains.Application.Settings;
using JetBrains.Application.Threading;
using JetBrains.Collections.Viewable;
using JetBrains.DataFlow;
using JetBrains.Lifetimes;
using JetBrains.ProjectModel;
using JetBrains.ProjectModel.DataContext;
using JetBrains.ReSharper.Plugins.Unity.Packages;
using JetBrains.ReSharper.Plugins.Unity.ProjectModel;
using JetBrains.ReSharper.Plugins.Unity.Rider.Protocol;
using JetBrains.ReSharper.Plugins.Unity.Settings;
using JetBrains.ReSharper.Psi.Util;
using JetBrains.Rider.Backend.Features.Notifications;
using JetBrains.Util;

namespace JetBrains.ReSharper.Plugins.Unity.Rider
{
[SolutionComponent]
public class RiderPackageUpdateAvailabilityChecker
{
private readonly ILogger myLogger;
private readonly ISolution mySolution;
private readonly IShellLocks myShellLocks;
private readonly PackageManager myPackageManager;
private readonly UnityVersion myUnityVersion;
private readonly ISettingsStore mySettingsStore;
private readonly BackendUnityHost myBackendUnityHost;
private readonly RiderNotificationPopupHost myNotificationPopupHost;
private readonly JetHashSet<VirtualFileSystemPath> myNotificationShown;
private readonly IContextBoundSettingsStoreLive myBoundSettingsStore;
private string packageId = "com.unity.ide.rider";

public RiderPackageUpdateAvailabilityChecker(
Lifetime lifetime,
ILogger logger,
ISolution solution,
IShellLocks shellLocks,
PackageManager packageManager,
UnitySolutionTracker unitySolutionTracker,
UnityVersion unityVersion,
IApplicationWideContextBoundSettingStore applicationWideContextBoundSettingStore,
ISettingsStore settingsStore,
BackendUnityHost backendUnityHost,
RiderNotificationPopupHost notificationPopupHost
)
{
myLogger = logger;
mySolution = solution;
myShellLocks = shellLocks;
myPackageManager = packageManager;
myUnityVersion = unityVersion;
mySettingsStore = settingsStore;
myBackendUnityHost = backendUnityHost;
myNotificationPopupHost = notificationPopupHost;
myNotificationShown = new JetHashSet<VirtualFileSystemPath>();
myBoundSettingsStore = applicationWideContextBoundSettingStore.BoundSettingsStore;
unitySolutionTracker.IsUnityProjectFolder.WhenTrue(lifetime, lt =>
{
ShowNotificationIfNeeded(lt, new Version(3, 0, 7));
BindToInstallationSettingChange(lt, new Version(3, 0, 7));
BindToProtocol(lt);
});
}

private void BindToProtocol(Lifetime lt)
{
myBackendUnityHost.BackendUnityModel.ViewNotNull(lt, (l, model) =>
{
model.RiderPackagePotentialUpdateVersion.Advise(l, result =>
{
if (!string.IsNullOrEmpty(result) && Version.TryParse(result, out var resultVersion))
{
ShowNotificationIfNeeded(l, resultVersion);
}
});
});
}

private void BindToInstallationSettingChange(Lifetime lifetime, Version version)
{
var entry = myBoundSettingsStore.Schema.GetScalarEntry((UnitySettings s) =>
s.AllowRiderUpdateNotifications);
myBoundSettingsStore.GetValueProperty<bool>(lifetime, entry, null).Change.Advise_NoAcknowledgement(lifetime,
args =>
{
if (!args.GetNewOrNull()) return;
ShowNotificationIfNeeded(lifetime, version);
});
}

private void ShowNotificationIfNeeded(Lifetime lifetime, Version expectedVersion)
{
if (!myBoundSettingsStore.GetValue((UnitySettings s) => s.AllowRiderUpdateNotifications))
return;

myPackageManager.IsInitialUpdateFinished.WhenTrue(lifetime, lt =>
{
myUnityVersion.ActualVersionForSolution.AdviseNotNull(lt, version =>
{
if (myNotificationShown.Contains(mySolution.SolutionFilePath)) return;

// Version before 2019.2 doesn't have Rider package
// 2019.2.0 - 2019.2.5 : version 1.2.1 is the last one
// 2019.2.6 - present : 3.0.7+
if (version < new Version(2019, 2, 6)) return;

var package = myPackageManager.GetPackageById(packageId);

if (package == null)
{
myNotificationShown.Add(mySolution.SolutionFilePath);
myLogger.Info($"{packageId} is missing.");
var notification = RiderNotification.Create(NotificationSeverity.WARNING,
"JetBrains Rider package in Unity is missing.",
"Make sure JetBrains Rider package is installed in Unity Package Manager."
);
myShellLocks.ExecuteOrQueueEx(lt,
"RiderPackageUpdateAvailabilityChecker.ShowNotificationIfNeeded",
() =>
{
myNotificationPopupHost.ShowNotification(lt, notification);
});
}
else if (package.Source == PackageSource.Registry &&
new Version(package.PackageDetails.Version) < expectedVersion)
{
var notificationLifetime = lt.CreateNested();
myNotificationShown.Add(mySolution.SolutionFilePath);
myLogger.Info($"{packageId} {package.PackageDetails.Version} is older then expected.");
var notification = RiderNotification.Create(NotificationSeverity.INFO,
"Update available - JetBrains Rider package.",
"Check for JetBrains Rider package updates in Unity Package Manager.",
additionalCommands: new[]
{
new UserNotificationCommand("Never show for this solution", () =>
{
mySettingsStore.BindToContextTransient(
ContextRange.ManuallyRestrictWritesToOneContext(mySolution.ToDataContext()))
.SetValue((UnitySettings key) => key.AllowRiderUpdateNotifications, false);
notificationLifetime.Terminate();
})
}
);

myShellLocks.ExecuteOrQueueEx(lt,
"RiderPackageUpdateAvailabilityChecker.ShowNotificationIfNeeded",
() => myNotificationPopupHost.ShowNotification(notificationLifetime.Lifetime, notification));
}
});
});
}
}
}
Loading