318 lines
10 KiB
C#
318 lines
10 KiB
C#
// Copyright (c) .NET Foundation and contributors. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using Microsoft.DotNet.ProjectModel.Server.Helpers;
|
|
using Microsoft.DotNet.ProjectModel.Server.Messengers;
|
|
using Microsoft.DotNet.ProjectModel.Server.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
using NuGet.Frameworks;
|
|
|
|
namespace Microsoft.DotNet.ProjectModel.Server
|
|
{
|
|
internal class ProjectManager
|
|
{
|
|
private readonly ILogger _log;
|
|
|
|
private readonly object _processingLock = new object();
|
|
private readonly Queue<Message> _inbox = new Queue<Message>();
|
|
private readonly ProtocolManager _protocolManager;
|
|
|
|
private ConnectionContext _initializedContext;
|
|
|
|
// triggers
|
|
private readonly Trigger<string> _appPath = new Trigger<string>();
|
|
private readonly Trigger<string> _configure = new Trigger<string>();
|
|
private readonly Trigger<int> _refreshDependencies = new Trigger<int>();
|
|
private readonly Trigger<int> _filesChanged = new Trigger<int>();
|
|
|
|
private ProjectSnapshot _local = new ProjectSnapshot();
|
|
private ProjectSnapshot _remote = new ProjectSnapshot();
|
|
|
|
private readonly WorkspaceContext _workspaceContext;
|
|
private int? _contextProtocolVersion;
|
|
|
|
private readonly List<Messenger<ProjectContextSnapshot>> _messengers;
|
|
|
|
private ProjectDiagnosticsMessenger _projectDiagnosticsMessenger;
|
|
private GlobalErrorMessenger _globalErrorMessenger;
|
|
private ProjectInformationMessenger _projectInforamtionMessenger;
|
|
|
|
public ProjectManager(int contextId,
|
|
ILoggerFactory loggerFactory,
|
|
WorkspaceContext workspaceContext,
|
|
ProtocolManager protocolManager)
|
|
{
|
|
Id = contextId;
|
|
_log = loggerFactory.CreateLogger<ProjectManager>();
|
|
_workspaceContext = workspaceContext;
|
|
_protocolManager = protocolManager;
|
|
|
|
_messengers = new List<Messenger<ProjectContextSnapshot>>
|
|
{
|
|
new DependencyDiagnosticsMessenger(Transmit),
|
|
new ReferencesMessenger(Transmit),
|
|
new DependenciesMessenger(Transmit),
|
|
new CompilerOptionsMessenger(Transmit),
|
|
new SourcesMessenger(Transmit)
|
|
};
|
|
|
|
_projectDiagnosticsMessenger = new ProjectDiagnosticsMessenger(Transmit);
|
|
_globalErrorMessenger = new GlobalErrorMessenger(Transmit);
|
|
_projectInforamtionMessenger = new ProjectInformationMessenger(Transmit);
|
|
}
|
|
|
|
public int Id { get; }
|
|
|
|
public string ProjectPath { get { return _appPath.Value; } }
|
|
|
|
public int ProtocolVersion
|
|
{
|
|
get
|
|
{
|
|
if (_contextProtocolVersion.HasValue)
|
|
{
|
|
return _contextProtocolVersion.Value;
|
|
}
|
|
else
|
|
{
|
|
return _protocolManager.CurrentVersion;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnReceive(Message message)
|
|
{
|
|
lock (_inbox)
|
|
{
|
|
_inbox.Enqueue(message);
|
|
}
|
|
|
|
ThreadPool.QueueUserWorkItem(state => ((ProjectManager)state).ProcessLoop(), this);
|
|
}
|
|
|
|
private void Transmit(string messageType, object payload)
|
|
{
|
|
var message = Message.FromPayload(messageType, Id, payload);
|
|
_initializedContext.Transmit(message);
|
|
}
|
|
|
|
private void ProcessLoop()
|
|
{
|
|
if (!Monitor.TryEnter(_processingLock))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
lock (_inbox)
|
|
{
|
|
if (!_inbox.Any())
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
DoProcessLoop();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_log.LogError("A unexpected exception occurred: {0}", ex.ToString());
|
|
|
|
var error = new ErrorMessage
|
|
{
|
|
Message = ex.Message
|
|
};
|
|
|
|
var fileFormatException = ex as FileFormatException;
|
|
if (fileFormatException != null)
|
|
{
|
|
error.Path = fileFormatException.Path;
|
|
error.Line = fileFormatException.Line;
|
|
error.Column = fileFormatException.Column;
|
|
}
|
|
|
|
var message = Message.FromPayload(MessageTypes.Error, Id, error);
|
|
|
|
_initializedContext.Transmit(message);
|
|
_remote.GlobalErrorMessage = error;
|
|
}
|
|
finally
|
|
{
|
|
Monitor.Exit(_processingLock);
|
|
}
|
|
}
|
|
|
|
private void DoProcessLoop()
|
|
{
|
|
while (true)
|
|
{
|
|
DrainInbox();
|
|
|
|
UpdateProject();
|
|
SendOutgingMessages();
|
|
|
|
lock (_inbox)
|
|
{
|
|
if (_inbox.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DrainInbox()
|
|
{
|
|
_log.LogInformation("Begin draining inbox.");
|
|
|
|
while (ProcessMessage()) { }
|
|
|
|
_log.LogInformation("Finish draining inbox.");
|
|
}
|
|
|
|
private bool ProcessMessage()
|
|
{
|
|
Message message;
|
|
|
|
lock (_inbox)
|
|
{
|
|
if (!_inbox.Any())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
message = _inbox.Dequeue();
|
|
Debug.Assert(message != null);
|
|
}
|
|
|
|
_log.LogInformation($"Received {message.MessageType}");
|
|
|
|
switch (message.MessageType)
|
|
{
|
|
case MessageTypes.Initialize:
|
|
Initialize(message);
|
|
break;
|
|
case MessageTypes.ChangeConfiguration:
|
|
// TODO: what if the payload is null or represent empty string?
|
|
_configure.Value = message.Payload.GetValue("Configuration");
|
|
break;
|
|
case MessageTypes.RefreshDependencies:
|
|
case MessageTypes.RestoreComplete:
|
|
_refreshDependencies.Value = 0;
|
|
break;
|
|
case MessageTypes.FilesChanged:
|
|
_filesChanged.Value = 0;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void Initialize(Message message)
|
|
{
|
|
if (_initializedContext != null)
|
|
{
|
|
_log.LogWarning($"Received {message.MessageType} message more than once for {_appPath.Value}");
|
|
return;
|
|
}
|
|
|
|
_initializedContext = message.Sender;
|
|
_appPath.Value = message.Payload.GetValue("ProjectFolder");
|
|
_configure.Value = message.Payload.GetValue("Configuration") ?? "Debug";
|
|
|
|
var version = message.Payload.GetValue<int>("Version");
|
|
if (version != 0 && !_protocolManager.EnvironmentOverridden)
|
|
{
|
|
_contextProtocolVersion = Math.Min(version, _protocolManager.MaxVersion);
|
|
_log.LogInformation($"Set context protocol version to {_contextProtocolVersion.Value}");
|
|
}
|
|
}
|
|
|
|
private bool UpdateProject()
|
|
{
|
|
ProjectSnapshot newSnapshot = null;
|
|
|
|
if (_appPath.WasAssigned || _configure.WasAssigned || _filesChanged.WasAssigned || _refreshDependencies.WasAssigned)
|
|
{
|
|
_appPath.ClearAssigned();
|
|
_configure.ClearAssigned();
|
|
_filesChanged.ClearAssigned();
|
|
_refreshDependencies.ClearAssigned();
|
|
|
|
newSnapshot = ProjectSnapshot.Create(_appPath.Value, _configure.Value, _workspaceContext, _remote.ProjectSearchPaths);
|
|
}
|
|
|
|
if (newSnapshot == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
_local = newSnapshot;
|
|
|
|
return true;
|
|
}
|
|
|
|
private void SendOutgingMessages()
|
|
{
|
|
_projectInforamtionMessenger.UpdateRemote(_local, _remote);
|
|
_projectDiagnosticsMessenger.UpdateRemote(_local, _remote);
|
|
|
|
var unprocessedFrameworks = new HashSet<NuGetFramework>(_remote.ProjectContexts.Keys);
|
|
foreach (var pair in _local.ProjectContexts)
|
|
{
|
|
ProjectContextSnapshot localProjectSnapshot = pair.Value;
|
|
ProjectContextSnapshot remoteProjectSnapshot;
|
|
|
|
if (!_remote.ProjectContexts.TryGetValue(pair.Key, out remoteProjectSnapshot))
|
|
{
|
|
remoteProjectSnapshot = new ProjectContextSnapshot();
|
|
_remote.ProjectContexts[pair.Key] = remoteProjectSnapshot;
|
|
}
|
|
|
|
unprocessedFrameworks.Remove(pair.Key);
|
|
|
|
foreach (var messenger in _messengers)
|
|
{
|
|
messenger.UpdateRemote(localProjectSnapshot,
|
|
remoteProjectSnapshot);
|
|
}
|
|
}
|
|
|
|
// Remove all processed frameworks from the remote view
|
|
foreach (var framework in unprocessedFrameworks)
|
|
{
|
|
_remote.ProjectContexts.Remove(framework);
|
|
}
|
|
|
|
_globalErrorMessenger.UpdateRemote(_local, _remote);
|
|
}
|
|
|
|
private class Trigger<TValue>
|
|
{
|
|
private TValue _value;
|
|
|
|
public bool WasAssigned { get; private set; }
|
|
|
|
public void ClearAssigned()
|
|
{
|
|
WasAssigned = false;
|
|
}
|
|
|
|
public TValue Value
|
|
{
|
|
get { return _value; }
|
|
set
|
|
{
|
|
WasAssigned = true;
|
|
_value = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|