using System.Collections.Generic; using GammaJul.ForTea.Core.TemplateProcessing.Services; using GammaJul.ForTea.Core.Tree; using JetBrains.Annotations; using JetBrains.Application.Parts; using JetBrains.Application.Progress; using JetBrains.Application.Threading; using JetBrains.Diagnostics; using JetBrains.ForTea.RiderPlugin.Model; using JetBrains.ForTea.RiderPlugin.TemplateProcessing.Services; using JetBrains.Lifetimes; using JetBrains.ProjectModel; using JetBrains.ProjectModel.ProjectsHost.SolutionHost.Progress; using JetBrains.RdBackend.Common.Features.ProjectModel; using JetBrains.RdBackend.Common.Features.ProjectModel.View; using JetBrains.ReSharper.Feature.Services.Protocol; using JetBrains.ReSharper.Psi; using JetBrains.Rider.Model.Notifications; using JetBrains.Util; namespace JetBrains.ForTea.RiderPlugin.ProtocolAware.Impl { [SolutionComponent(InstantiationEx.LegacyDefault)] public sealed class T4TemplateExecutionManager : IT4TemplateExecutionManager { [NotNull] private IDictionary RunningFiles { get; } [NotNull] private object ExecutionLocker { get; } = new object(); [NotNull] private T4ProtocolModel Model { get; } private Lifetime Lifetime { get; } [NotNull] private ISolution Solution { get; } [NotNull] private IT4ProjectModelTemplateMetadataManager TemplateMetadataManager { get; } [NotNull] private BackgroundProgressManager BackgroundProgressManager { get; } [NotNull] private ProjectModelViewHost ProjectModelViewHost { get; } [NotNull] private NotificationsModel NotificationsModel { get; } [NotNull] private ILogger Logger { get; } public T4TemplateExecutionManager( Lifetime lifetime, [NotNull] ISolution solution, [NotNull] ProjectModelViewHost projectModelViewHost, [NotNull] NotificationsModel notificationsModel, [NotNull] ILogger logger, [NotNull] IT4ProjectModelTemplateMetadataManager templateMetadataManager, [NotNull] BackgroundProgressManager backgroundProgressManager ) { Lifetime = lifetime; Solution = solution; ProjectModelViewHost = projectModelViewHost; NotificationsModel = notificationsModel; Logger = logger; TemplateMetadataManager = templateMetadataManager; BackgroundProgressManager = backgroundProgressManager; Model = solution.GetProtocolSolution().GetT4ProtocolModel(); RunningFiles = new Dictionary(); } public void RememberExecution(IT4File file, bool withProgress) => RememberExecution(file.PhysicalPsiSourceFile.NotNull().GetLocation(), withProgress); public int GetEnvDTEPort(IT4File file) { bool hasInfo = RunningFiles.TryGetValue(file.PhysicalPsiSourceFile.GetLocation(), out var info); if (!hasInfo) return 0; return info.ConnectionManager.Port; } private void RememberExecution([NotNull] VirtualFileSystemPath path, bool withProgress) { var definition = Lifetime.CreateNested(); if (withProgress) { var progress = new ProgressIndicator(definition.Lifetime); IProgressIndicator iProgress = progress; iProgress.Start(1); progress.Advance(); var task = BackgroundProgressBuilder .FromProgressIndicator(progress) .AsIndeterminate() .WithHeader("Executing template") .WithDescription($"{path.Name}") .Build(); Solution.Locks.ExecuteOrQueueEx( definition.Lifetime, "T4 execution progress launching", () => BackgroundProgressManager.AddNewTask(definition.Lifetime, task) ); } RunningFiles[path] = new T4EnvDTEHost(definition, Solution); } public void UpdateTemplateKind(IT4File file) { Logger.Verbose("Updating template kind"); var projectFile = file.PhysicalPsiSourceFile.ToProjectFile().NotNull(); Solution.InvokeUnderTransaction(cookie => TemplateMetadataManager.UpdateTemplateMetadata(cookie, projectFile, T4TemplateKind.Executable)); // Apply the changes, just in case the template kind was different Solution.GetPsiServices().Files.CommitAllDocuments(); } public void Execute(IT4File file) { Logger.Verbose("Trying to execute a file"); lock (ExecutionLocker) { if (IsExecutionRunning(file)) { ShowNotification(); return; } RememberExecution(file, false); } Model.RequestExecution.Start(new T4ExecutionRequest(GetT4FileLocation(file), true)); } public void ExecuteSilently(IT4File file) { Logger.Verbose("Trying to execute a file silently"); lock (ExecutionLocker) { if (IsExecutionRunning(file)) { ShowNotification(); return; } RememberExecution(file, true); } Model.RequestExecution.Start(new T4ExecutionRequest(GetT4FileLocation(file), false)); } public void Debug(IT4File file) { Logger.Verbose("Trying to debug a file"); lock (ExecutionLocker) { if (IsExecutionRunning(file)) { ShowNotification(); return; } RememberExecution(file, false); } Model.RequestDebug.Start(new T4ExecutionRequest(GetT4FileLocation(file), true)); } private bool IsExecutionRunning([NotNull] IT4File file) => IsExecutionRunning(file.PhysicalPsiSourceFile.NotNull()); public bool IsExecutionRunning(IPsiSourceFile file) => RunningFiles.ContainsKey(file.GetLocation()); public void OnExecutionFinished(IT4File file) { lock (ExecutionLocker) { var location = file.PhysicalPsiSourceFile.GetLocation(); var runtime = RunningFiles[location]; runtime.LifetimeDefinition.Terminate(); RunningFiles.Remove(location); } } [NotNull] private T4FileLocation GetT4FileLocation([NotNull] IT4File file) { var sourceFile = file.PhysicalPsiSourceFile.NotNull(); var projectFile = sourceFile.ToProjectFile().NotNull(); int id = ProjectModelViewHost.GetIdByItem(projectFile); return new T4FileLocation(id); } private void ShowNotification() => NotificationsModel.Notification(new NotificationModel( Solution.GetRdProjectId(), "Could not execute T4 file", "Execution is already running", true, RdNotificationEntryType.ERROR, new List() )); } }