using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MonoDevelop.Core;
using MonoDevelop.Ide;
using MonoDevelop.Projects;
namespace MonoDevelop.ConnectedServices
{
///
/// Base class for implementing IConnectedService. It stores state in a .json file that is created in a sub-folder of the project
///
public abstract class ConnectedService : IConnectedService
{
///
/// Empty array of IConnectedService
///
public static readonly IConnectedService[] Empty = new IConnectedService[0];
string solutionPadDisplayName;
Status status = (Status)(-1);
///
/// Initializes a new instance of the class.
///
protected ConnectedService (DotNetProject project)
{
this.Project = project;
this.Dependencies = ConnectedServiceDependency.Empty;
this.Sections = ConfigurationSection.Empty;
this.DependenciesSection = new DependenciesSection (this);
}
///
/// Gets the Id of the service
///
public string Id { get; protected set; }
///
/// Gets the display name of the service to show to the user in the solution pad
///
public string DisplayName { get; protected set; }
///
/// Gets the display name of the service to show to the user in the solution pad
///
public string SolutionPadDisplayName {
get {
if (this.solutionPadDisplayName != null)
return solutionPadDisplayName;
return this.DisplayName;
}
set {
solutionPadDisplayName = value;
}
}
///
/// Gets the description of the service to display to the user in the services gallery.
///
public string Description { get; protected set; }
///
/// Gets the description of the service to display to the user in the service details tab.
///
public string DetailsDescription { get; protected set; }
///
/// Gets a description of the supported platforms. This is largely just informational as the service provider decides
/// whether a project is supported or not.
///
public string SupportedPlatforms { get; protected set; }
///
/// Gets the project that this service instance is attached to
///
public DotNetProject Project { get; private set; }
///
/// Gets the icon to display in the services gallery.
///
public Xwt.Drawing.Image GalleryIcon { get; protected set; }
///
/// Gets the current status of the service.
///
public Status Status {
get {
if ((int)status == -1)
status = this.GetIsAddedToProject() ? Status.Added : Status.NotAdded;
return status;
}
}
///
/// Gets the dependencies that will be added to the project
///
public ImmutableArray Dependencies { get; protected set; }
///
/// Gets the dependencies section to be displayed before the configuration section
///
public IConfigurationSection DependenciesSection { get; protected set; }
///
/// Gets a value indicating whether ALL the depenencies are installed.
///
public bool AreDependenciesInstalled {
get {
return this.Dependencies.All (x => x.Status == Status.Added);
}
}
///
/// Gets the array of sections to be displayed to the user after the dependencies section.
///
public ImmutableArray Sections { get; protected set; }
///
/// Occurs when the status of the service has changed.
///
public event EventHandler StatusChanged;
///
/// Adds the service to the project
///
public async Task AddToProject ()
{
if (this.GetIsAddedToProject()) {
LoggingService.LogWarning ("Skipping adding of the service, it has already been added");
return true;
}
this.ChangeStatus (Status.Adding);
try {
await this.AddDependencies (CancellationToken.None).ConfigureAwait (false);
await this.OnAddToProject ().ConfigureAwait (false);
await this.StoreAddedState ().ConfigureAwait (false);
this.ChangeStatus (this.GetIsAddedToProject() ? Status.Added : Status.NotAdded);
return true;
} catch (Exception ex) {
LoggingService.LogError ("An error occurred while adding the service to the project", ex);
this.ChangeStatus (Status.NotAdded, ex);
return false;
}
}
///
/// Removes the service from the project
///
public async Task RemoveFromProject ()
{
if (!this.GetIsAddedToProject()) {
LoggingService.LogWarning ("Skipping removing of the service, it is not added to the project");
return true;
}
this.ChangeStatus (Status.Removing);
// try to remove the dependencies first, these may sometimes fail if one of the packages is or has dependencies
// that other packages are dependent upon. A common one might be Json.Net for instance
// we need to allow for this and continue on afterwards.
var dependenciesFailed = false;
try {
await this.RemoveDependencies (CancellationToken.None).ConfigureAwait (false);
} catch (Exception) {
LoggingService.LogError ("An error occurred while removing the service dependencies from the project");
dependenciesFailed = true;
}
// right, now remove the service
try {
await this.OnRemoveFromProject ().ConfigureAwait (false);
await this.RemoveAddedState ().ConfigureAwait (false);
this.ChangeStatus (Status.NotAdded);
if (dependenciesFailed) {
// TODO: notify the user about the dependencies that failed, we might already have this from the package console and that might be enough for now
}
return true;
} catch (Exception ex) {
LoggingService.LogError ("An error occurred while removing the service from the project", ex);
this.ChangeStatus (Status.Added, ex);
return false;
}
}
///
/// Determines if the service has been added to the project.
///
protected virtual bool GetIsAddedToProject()
{
return false;
}
///
/// Performs the logic of removing the service from the project. This is called after the dependencies have been removed.
///
protected virtual Task OnRemoveFromProject()
{
return Task.FromResult (true);
}
///
/// Performs the logic of adding the service to the project. This is called after the dependencies have been added.
///
protected virtual Task OnAddToProject ()
{
return Task.FromResult (true);
}
///
/// Adds the dependencies to the project
///
protected virtual async Task AddDependencies(CancellationToken token)
{
return await DependenciesSection.AddToProject (token);
}
///
/// Removes the dependencies from the project
///
protected virtual async Task RemoveDependencies (CancellationToken token)
{
// ask all the dependencies to add themselves to the project
// we'll do them one at a time in case there are interdependencies between them
try {
// we are going to short circuit package dependencies though and uninstall them in one go
var packages = this.Dependencies.Reverse ().OfType ().Cast ().ToList ();
foreach (var dependency in this.Dependencies.Reverse ()) {
if (packages.Contains (dependency)) {
continue;
}
await dependency.RemoveFromProject (token).ConfigureAwait (false);
}
await this.Project.RemovePackageDependencies (packages).ConfigureAwait (false);
} catch (Exception ex) {
LoggingService.LogError ("Could not remove dependency", ex);
throw;
}
}
///
/// Stores some state that the service has been added to the project.
///
protected virtual Task StoreAddedState()
{
return IdeApp.ProjectOperations.SaveAsync (this.Project);
}
///
/// Stores some state that the service has been added to the project.
///
protected virtual Task RemoveAddedState ()
{
return IdeApp.ProjectOperations.SaveAsync (this.Project);
}
///
/// Changes the status of the service and notifies subscribers
///
void ChangeStatus(Status newStatus, Exception error = null)
{
var oldStatus = this.Status;
this.status = newStatus;
this.NotifyStatusChanged (newStatus, oldStatus, error);
}
///
/// Notifies subscribers that the service status has changed
///
void NotifyStatusChanged(Status newStatus, Status oldStatus, Exception error = null)
{
var handler = this.StatusChanged;
if (handler != null) {
handler (this, new StatusChangedEventArgs (newStatus, oldStatus, error));
}
}
}
}