initial source commit
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.Repository;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.CertificateProvider
|
||||
{
|
||||
[PluginFeatureCategory(DisplayName = "Certificate Providers")]
|
||||
public abstract class CertificateProviderFeature : PluginFeature
|
||||
{
|
||||
// Certificate Plugin Requirements
|
||||
public abstract string CertificateProviderId { get; }
|
||||
public abstract Tuple<DeviceCertificate, List<string>> AllocateCertificate(DiscoDataContext dbContext, Device Device);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Logging.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
namespace Disco.Services.Plugins.Features.CertificateProvider
|
||||
{
|
||||
public class CertificateProvidersLog : LogBase
|
||||
{
|
||||
public enum EventTypeIds
|
||||
{
|
||||
RetrievalStarting = 10,
|
||||
RetrievalProgress,
|
||||
RetrievalFinished,
|
||||
RetrievalWarning = 15,
|
||||
RetrievalError,
|
||||
RetrievalCertificateStarting = 20,
|
||||
RetrievalCertificateFinished = 22,
|
||||
RetrievalCertificateWarning = 25,
|
||||
RetrievalCertificateError,
|
||||
Allocated = 40,
|
||||
AllocationFailed = 50
|
||||
}
|
||||
private const int _ModuleId = 60;
|
||||
private static bool _IsCertificateRetrievalProcessing;
|
||||
private static string _CertificateRetrievalStatus;
|
||||
private static int _CertificateRetrievalProgress;
|
||||
public static CertificateProvidersLog Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return (CertificateProvidersLog)LogContext.LogModules[60];
|
||||
}
|
||||
}
|
||||
public static bool IsCertificateRetrievalProcessing
|
||||
{
|
||||
get
|
||||
{
|
||||
return CertificateProvidersLog._IsCertificateRetrievalProcessing;
|
||||
}
|
||||
}
|
||||
public override string ModuleDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Certificate Providers";
|
||||
}
|
||||
}
|
||||
public override int ModuleId
|
||||
{
|
||||
get
|
||||
{
|
||||
return 60;
|
||||
}
|
||||
}
|
||||
public override string ModuleName
|
||||
{
|
||||
get
|
||||
{
|
||||
return "CertificateProviders";
|
||||
}
|
||||
}
|
||||
[System.Diagnostics.DebuggerNonUserCode]
|
||||
public CertificateProvidersLog()
|
||||
{
|
||||
}
|
||||
private static void Log(CertificateProvidersLog.EventTypeIds EventTypeId, params object[] Args)
|
||||
{
|
||||
CertificateProvidersLog.Current.Log((int)EventTypeId, Args);
|
||||
}
|
||||
public static void LogRetrievalStarting(int CertificateCount, int CertificateIdFrom, int CertificateIdTo)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalStarting, new object[]
|
||||
{
|
||||
CertificateCount,
|
||||
CertificateIdFrom,
|
||||
CertificateIdTo
|
||||
});
|
||||
}
|
||||
public static void LogRetrievalFinished()
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalFinished, new object[0]);
|
||||
}
|
||||
public static void LogRetrievalWarning(string Message)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalWarning, new object[]
|
||||
{
|
||||
Message
|
||||
});
|
||||
}
|
||||
public static void LogRetrievalError(string Message)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalError, new object[]
|
||||
{
|
||||
Message
|
||||
});
|
||||
}
|
||||
public static void LogRetrievalCertificateStarting(string CertificateId)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalCertificateStarting, new object[]
|
||||
{
|
||||
CertificateId
|
||||
});
|
||||
}
|
||||
public static void LogRetrievalCertificateFinished(string CertificateId)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalCertificateFinished, new object[]
|
||||
{
|
||||
CertificateId
|
||||
});
|
||||
}
|
||||
public static void LogRetrievalCertificateWarning(string CertificateId, string Message)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalCertificateWarning, new object[]
|
||||
{
|
||||
CertificateId,
|
||||
Message
|
||||
});
|
||||
}
|
||||
public static void LogRetrievalCertificateError(string CertificateId, string Message)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalCertificateError, new object[]
|
||||
{
|
||||
CertificateId,
|
||||
Message
|
||||
});
|
||||
}
|
||||
public static void LogAllocated(string CertificateId, string DeviceSerialNumber)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.Allocated, new object[]
|
||||
{
|
||||
CertificateId,
|
||||
DeviceSerialNumber
|
||||
});
|
||||
}
|
||||
public static void LogAllocationFailed(string DeviceSerialNumber)
|
||||
{
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.AllocationFailed, new object[]
|
||||
{
|
||||
DeviceSerialNumber
|
||||
});
|
||||
}
|
||||
public static void LogCertificateRetrievalProgress(bool? IsProcessing, int? Progress, string Status)
|
||||
{
|
||||
bool flag = IsProcessing.HasValue;
|
||||
if (flag)
|
||||
{
|
||||
CertificateProvidersLog._IsCertificateRetrievalProcessing = IsProcessing.Value;
|
||||
}
|
||||
flag = CertificateProvidersLog._IsCertificateRetrievalProcessing;
|
||||
if (flag)
|
||||
{
|
||||
bool flag2 = Status != null;
|
||||
if (flag2)
|
||||
{
|
||||
CertificateProvidersLog._CertificateRetrievalStatus = Status;
|
||||
}
|
||||
flag2 = Progress.HasValue;
|
||||
if (flag2)
|
||||
{
|
||||
CertificateProvidersLog._CertificateRetrievalProgress = Progress.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CertificateProvidersLog._CertificateRetrievalStatus = null;
|
||||
CertificateProvidersLog._CertificateRetrievalProgress = 0;
|
||||
}
|
||||
CertificateProvidersLog.Log(CertificateProvidersLog.EventTypeIds.RetrievalProgress, new object[]
|
||||
{
|
||||
CertificateProvidersLog._IsCertificateRetrievalProcessing,
|
||||
CertificateProvidersLog._CertificateRetrievalProgress,
|
||||
CertificateProvidersLog._CertificateRetrievalStatus
|
||||
});
|
||||
}
|
||||
protected override System.Collections.Generic.List<LogEventType> LoadEventTypes()
|
||||
{
|
||||
return new System.Collections.Generic.List<LogEventType>
|
||||
{
|
||||
new LogEventType
|
||||
{
|
||||
Id = 10,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Starting",
|
||||
Format = "Starting retrieval of {0} certificate/s ({1} to {2})",
|
||||
Severity = 0,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 11,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Progress",
|
||||
Format = "Processing: {0}; {1}% Complete; Status: {2}",
|
||||
Severity = 0,
|
||||
UseLive = true,
|
||||
UsePersist = false,
|
||||
UseDisplay = false
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 12,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Finished",
|
||||
Format = "Retrieval of Certificates Complete",
|
||||
Severity = 0,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 15,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Warning",
|
||||
Format = "Retrieval Warning: {0}",
|
||||
Severity = 1,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 16,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Error",
|
||||
Format = "Retrieval Error: {0}",
|
||||
Severity = 2,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 20,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Certificate Starting",
|
||||
Format = "Retrieving Certificate: {0}",
|
||||
Severity = 0,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 22,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Certificate Finished",
|
||||
Format = "Certificate Retrieved: {0}",
|
||||
Severity = 0,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 25,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Certificate Warning",
|
||||
Format = "{0} Certificate Warning: {1}",
|
||||
Severity = 1,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 26,
|
||||
ModuleId = 60,
|
||||
Name = "Retrieval Certificate Error",
|
||||
Format = "{0} Certificate Error: {1}",
|
||||
Severity = 2,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 40,
|
||||
ModuleId = 60,
|
||||
Name = "Allocated",
|
||||
Format = "Certificate {0} allocated to {1}",
|
||||
Severity = 0,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = 50,
|
||||
ModuleId = 60,
|
||||
Name = "Allocation Failed",
|
||||
Format = "No certificates available for Device: {0}",
|
||||
Severity = 2,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.InteroperabilityProvider
|
||||
{
|
||||
[PluginFeatureCategory(DisplayName = "Interoperability Providers")]
|
||||
public abstract class InteroperabilityProviderFeature : PluginFeature
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.Other
|
||||
{
|
||||
[PluginFeatureCategory(DisplayName = "Other")]
|
||||
public abstract class OtherFeature : PluginFeature
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Mvc;
|
||||
using Disco.Data.Repository;
|
||||
using Disco.Models.BI.Config;
|
||||
using Disco.Models.Repository;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.WarrantyProvider
|
||||
{
|
||||
[PluginFeatureCategory(DisplayName = "Warranty Providers")]
|
||||
public abstract class WarrantyProviderFeature : PluginFeature
|
||||
{
|
||||
// Warranty Plugin Requirements
|
||||
public abstract string WarrantyProviderId { get; }
|
||||
public abstract Type SubmitJobViewType { get; }
|
||||
public abstract dynamic SubmitJobViewModel(DiscoDataContext dbContext, Controller controller, Job Job, OrganisationAddress Address, User TechUser);
|
||||
public abstract Dictionary<string, string> SubmitJobParseProperties(DiscoDataContext dbContext, FormCollection form, Controller controller, Job Job, OrganisationAddress Address, User TechUser, string FaultDescription);
|
||||
public abstract Dictionary<string, string> SubmitJobDiscloseInfo(DiscoDataContext dbContext, Job Job, OrganisationAddress Address, User TechUser, string FaultDescription, Dictionary<string, string> WarrantyProviderProperties);
|
||||
public abstract string SubmitJob(DiscoDataContext dbContext, Job Job, OrganisationAddress Address, User TechUser, string FaultDescription, Dictionary<string, string> WarrantyProviderProperties);
|
||||
|
||||
public abstract Type JobDetailsViewType { get; }
|
||||
public bool JobDetailsSupported { get { return this.JobDetailsViewType != null; } }
|
||||
public abstract dynamic JobDetailsViewModel(DiscoDataContext dbContext, Controller controller, Job Job);
|
||||
|
||||
public static PluginFeatureManifest FindPluginFeature(string PluginIdOrWarrantyProviderId)
|
||||
{
|
||||
var defs = Plugins.GetPluginFeatures(typeof(WarrantyProviderFeature));
|
||||
var def = defs.FirstOrDefault(d => d.PluginManifest.Id.Equals(PluginIdOrWarrantyProviderId, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (def != null)
|
||||
return def;
|
||||
else
|
||||
foreach (var d in defs)
|
||||
{
|
||||
using (var providerInstance = d.CreateInstance<WarrantyProviderFeature>())
|
||||
{
|
||||
if (providerInstance.WarrantyProviderId != null && providerInstance.WarrantyProviderId.Equals(PluginIdOrWarrantyProviderId, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace Disco.Services.Plugins.Features.WarrantyProvider
|
||||
{
|
||||
public class WarrantyProviderSubmitJobException : Exception
|
||||
{
|
||||
public WarrantyProviderSubmitJobException(string Message)
|
||||
: base(Message)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public class InvalidFeatureCategoryTypeException : Exception
|
||||
{
|
||||
private string _pluginRequested;
|
||||
private Type _categoryType;
|
||||
|
||||
public string PluginRequested
|
||||
{
|
||||
get
|
||||
{
|
||||
return _pluginRequested;
|
||||
}
|
||||
}
|
||||
public Type CategoryType
|
||||
{
|
||||
get
|
||||
{
|
||||
return _categoryType;
|
||||
}
|
||||
}
|
||||
|
||||
public InvalidFeatureCategoryTypeException(Type CategoryType)
|
||||
: this(CategoryType, null)
|
||||
{
|
||||
}
|
||||
public InvalidFeatureCategoryTypeException(Type CategoryType, string PluginRequested)
|
||||
{
|
||||
this._categoryType = CategoryType;
|
||||
this._pluginRequested = PluginRequested;
|
||||
}
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(_pluginRequested))
|
||||
return string.Format("Invalid Category Type [{0}]", _categoryType.Name);
|
||||
else
|
||||
return string.Format("Plugin [{1}] is not of the correct Category Type [{0}]", _categoryType.Name, _pluginRequested);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Disco.Data.Repository;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public abstract class Plugin : IDisposable
|
||||
{
|
||||
public PluginManifest Manifest {get; internal set;}
|
||||
|
||||
#region Lifecycle
|
||||
public abstract bool Install(DiscoDataContext dbContext);
|
||||
public abstract bool Initalize(DiscoDataContext dbContext);
|
||||
public abstract bool Uninstall(DiscoDataContext dbContext);
|
||||
public abstract bool BeforeUpdate(DiscoDataContext dbContext, PluginManifest updateManifest);
|
||||
#endregion
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
// Nothing in Base Class
|
||||
}
|
||||
|
||||
public override sealed string ToString()
|
||||
{
|
||||
return string.Format("{0} ({1}) - v{2}", this.Manifest.Name, this.Manifest.Id, this.Manifest.Version.ToString(4));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class PluginAttribute : Attribute
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Author { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Mvc;
|
||||
using Disco.Data.Repository;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public abstract class PluginConfigurationHandler : IDisposable
|
||||
{
|
||||
public PluginManifest Manifest { get; set; }
|
||||
|
||||
public abstract PluginConfigurationHandlerGetResponse Get(DiscoDataContext dbContext, Controller controller);
|
||||
public abstract bool Post(DiscoDataContext dbContext, FormCollection form, Controller controller);
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
// Nothing in Base Class
|
||||
}
|
||||
|
||||
protected PluginConfigurationHandlerGetResponse GetResponse(Type ViewType, dynamic ViewModel = null)
|
||||
{
|
||||
return new PluginConfigurationHandlerGetResponse(this.Manifest, ViewType, ViewModel);
|
||||
}
|
||||
public class PluginConfigurationHandlerGetResponse
|
||||
{
|
||||
public PluginManifest Manifest { get; set; }
|
||||
public Type ViewType { get; set; }
|
||||
public dynamic ViewModel { get; set; }
|
||||
|
||||
public PluginConfigurationHandlerGetResponse(PluginManifest Manifest, Type ViewType, dynamic ViewModel = null)
|
||||
{
|
||||
if (ViewType == null)
|
||||
throw new ArgumentNullException("ViewType");
|
||||
if (!typeof(WebViewPage).IsAssignableFrom(ViewType))
|
||||
throw new ArgumentException("The PluginConfigurationHandler ViewType must inherit System.Web.Mvc.WebViewPage", "ViewType");
|
||||
|
||||
this.Manifest = Manifest;
|
||||
|
||||
this.ViewType = ViewType;
|
||||
this.ViewModel = ViewModel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Disco.Data.Repository;
|
||||
using System.IO;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using System.Web;
|
||||
using System.Web.Mvc.Html;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public static class PluginExtensions
|
||||
{
|
||||
#region Model Binding from Controller
|
||||
public static bool TryUpdateModel<TModel>(this Controller controller, TModel model) where TModel : class
|
||||
{
|
||||
return controller.TryUpdateModel<TModel>(model, null, controller.ValueProvider);
|
||||
}
|
||||
public static bool TryUpdateModel<TModel>(this Controller controller, TModel model, IValueProvider valueProvider) where TModel : class
|
||||
{
|
||||
return controller.TryUpdateModel<TModel>(model, null, valueProvider);
|
||||
}
|
||||
public static bool TryUpdateModel<TModel>(this Controller controller, TModel model, string prefix) where TModel : class
|
||||
{
|
||||
return controller.TryUpdateModel<TModel>(model, prefix, controller.ValueProvider);
|
||||
}
|
||||
public static bool TryUpdateModel<TModel>(this Controller controller, TModel model, string prefix, IValueProvider valueProvider) where TModel : class
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException("model");
|
||||
if (valueProvider == null)
|
||||
throw new ArgumentNullException("valueProvider");
|
||||
|
||||
Predicate<string> predicate = propertyName => true;
|
||||
IModelBinder binder = ModelBinders.Binders.GetBinder(typeof(TModel));
|
||||
|
||||
ModelBindingContext context2 = new ModelBindingContext
|
||||
{
|
||||
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, typeof(TModel)),
|
||||
ModelName = prefix,
|
||||
ModelState = controller.ModelState,
|
||||
PropertyFilter = predicate,
|
||||
ValueProvider = valueProvider
|
||||
};
|
||||
|
||||
ModelBindingContext bindingContext = context2;
|
||||
|
||||
binder.BindModel(controller.ControllerContext, bindingContext);
|
||||
|
||||
return controller.ModelState.IsValid;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Virtual Directories
|
||||
//public static string WebHandlerResource(this PluginManifest pluginManifest, string resourcePath, RequestContext requestContext)
|
||||
//{
|
||||
// var rootPath = WebHandlerRootUrl(pluginManifest, requestContext);
|
||||
// return string.Concat(rootPath, resourcePath);
|
||||
//}
|
||||
//public static string WebHandlerRootUrl(this PluginManifest pluginManifest, RequestContext requestContext)
|
||||
//{
|
||||
// var tempPath = pluginManifest.WebHandlerActionUrl(requestContext, "_");
|
||||
// return tempPath.Substring(0, tempPath.LastIndexOf(@"/") + 1);
|
||||
//}
|
||||
//public static string WebHandlerActionUrl(this PluginManifest pluginManifest, RequestContext requestContext, string PluginAction)
|
||||
//{
|
||||
// var routeValues = new RouteValueDictionary(new { PluginId = pluginManifest.Id, PluginAction = PluginAction });
|
||||
// return UrlHelper.GenerateUrl("Plugin", "PluginWebHandler", "Index", routeValues, RouteTable.Routes, requestContext, true);
|
||||
//}
|
||||
//public static string WebHandlerResourceUrl(this PluginManifest pluginManifest, RequestContext requestContext, string PluginAction)
|
||||
//{
|
||||
// var routeValues = new RouteValueDictionary(new { PluginId = pluginManifest.Id, PluginAction = PluginAction });
|
||||
|
||||
|
||||
|
||||
// return UrlHelper.GenerateUrl("Plugin", "PluginWebHandler", "Index", routeValues, RouteTable.Routes, requestContext, true);
|
||||
//}
|
||||
|
||||
public static HtmlString DiscoPluginResourceUrl<T>(this WebViewPage<T> ViewPage, string Resource)
|
||||
{
|
||||
return ViewPage.DiscoPluginResourceUrl(Resource, false);
|
||||
}
|
||||
public static HtmlString DiscoPluginResourceUrl<T>(this WebViewPage<T> ViewPage, string Resource, bool Download)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Resource))
|
||||
throw new ArgumentNullException("Resource");
|
||||
|
||||
// Find Plugin
|
||||
var pageType = ViewPage.GetType();
|
||||
var pageAssembly = pageType.Assembly;
|
||||
var manifest = Plugins.GetPlugin(pageAssembly);
|
||||
|
||||
var resourcePath = manifest.WebResourcePath(Resource);
|
||||
|
||||
var routeValues = new RouteValueDictionary(new { PluginId = manifest.Id, res = Resource });
|
||||
string pluginActionUrl = UrlHelper.GenerateUrl("Plugin_Resources", null, null, routeValues, RouteTable.Routes, ViewPage.ViewContext.RequestContext, false);
|
||||
|
||||
pluginActionUrl += string.Format("?v={0}", resourcePath.Item2);
|
||||
|
||||
if (Download)
|
||||
pluginActionUrl += "&Download=true";
|
||||
|
||||
return new HtmlString(pluginActionUrl);
|
||||
}
|
||||
public static HtmlString DiscoPluginActionUrl<T>(this WebViewPage<T> ViewPage, string PluginAction)
|
||||
{
|
||||
if (string.IsNullOrEmpty(PluginAction))
|
||||
throw new ArgumentNullException("PluginAction");
|
||||
|
||||
// Find Plugin
|
||||
var pageType = ViewPage.GetType();
|
||||
var pageAssembly = pageType.Assembly;
|
||||
var manifest = Plugins.GetPlugin(pageAssembly);
|
||||
|
||||
var routeValues = new RouteValueDictionary(new { PluginId = manifest.Id, PluginAction = PluginAction });
|
||||
string pluginActionUrl = UrlHelper.GenerateUrl("Plugin", null, null, routeValues, RouteTable.Routes, ViewPage.ViewContext.RequestContext, false);
|
||||
return new HtmlString(pluginActionUrl);
|
||||
}
|
||||
public static HtmlString DiscoPluginConfigureUrl<T>(this WebViewPage<T> ViewPage)
|
||||
{
|
||||
// Find Plugin
|
||||
var pageType = ViewPage.GetType();
|
||||
var pageAssembly = pageType.Assembly;
|
||||
var manifest = Plugins.GetPlugin(pageAssembly);
|
||||
|
||||
var routeValues = new RouteValueDictionary(new { PluginId = manifest.Id });
|
||||
string pluginActionUrl = UrlHelper.GenerateUrl("Config_Plugins_Configure", null, null, routeValues, RouteTable.Routes, ViewPage.ViewContext.RequestContext, false);
|
||||
return new HtmlString(pluginActionUrl);
|
||||
}
|
||||
public static MvcForm DiscoPluginActionBeginForm<T>(this WebViewPage<T> ViewPage, string PluginAction, FormMethod method, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
if (string.IsNullOrEmpty(PluginAction))
|
||||
throw new ArgumentNullException("PluginAction");
|
||||
|
||||
// Find Plugin
|
||||
var pageType = ViewPage.GetType();
|
||||
var pageAssembly = pageType.Assembly;
|
||||
var manifest = Plugins.GetPlugin(pageAssembly);
|
||||
|
||||
var routeValues = new RouteValueDictionary(new { PluginId = manifest.Id, PluginAction = PluginAction });
|
||||
string pluginActionUrl = UrlHelper.GenerateUrl("Plugin", null, null, routeValues, RouteTable.Routes, ViewPage.ViewContext.RequestContext, false);
|
||||
|
||||
return ViewPage.FormHelper(pluginActionUrl, method, htmlAttributes);
|
||||
}
|
||||
public static MvcForm DiscoPluginActionBeginForm<T>(this WebViewPage<T> ViewPage, string PluginAction, FormMethod method)
|
||||
{
|
||||
return ViewPage.DiscoPluginActionBeginForm(PluginAction, method, null);
|
||||
}
|
||||
public static MvcForm DiscoPluginActionBeginForm<T>(this WebViewPage<T> ViewPage, string PluginAction, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
return ViewPage.DiscoPluginActionBeginForm(PluginAction, FormMethod.Post, htmlAttributes);
|
||||
}
|
||||
public static MvcForm DiscoPluginActionBeginForm<T>(this WebViewPage<T> ViewPage, string PluginAction)
|
||||
{
|
||||
return ViewPage.DiscoPluginActionBeginForm(PluginAction, FormMethod.Post, null);
|
||||
}
|
||||
|
||||
private static MvcForm FormHelper<T>(this WebViewPage<T> ViewPage, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
|
||||
{
|
||||
TagBuilder builder = new TagBuilder("form");
|
||||
builder.MergeAttributes<string, object>(htmlAttributes);
|
||||
builder.MergeAttribute("action", formAction);
|
||||
builder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
|
||||
bool flag = ViewPage.ViewContext.ClientValidationEnabled && !ViewPage.ViewContext.UnobtrusiveJavaScriptEnabled;
|
||||
if (flag)
|
||||
{
|
||||
object obj2 = ViewPage.ViewContext.HttpContext.Items["DiscoPluginLastFormNum"];
|
||||
int num = (obj2 != null) ? (((int)obj2) + 1) : 1000;
|
||||
ViewPage.ViewContext.HttpContext.Items["DiscoPluginLastFormNum"] = num;
|
||||
|
||||
builder.GenerateId(string.Format(CultureInfo.InvariantCulture, "form{0}", new object[] { num }));
|
||||
}
|
||||
ViewPage.ViewContext.Writer.Write(builder.ToString(TagRenderMode.StartTag));
|
||||
MvcForm form = new MvcForm(ViewPage.ViewContext);
|
||||
if (flag)
|
||||
{
|
||||
ViewPage.ViewContext.FormContext.FormId = builder.Attributes["id"];
|
||||
}
|
||||
return form;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Disco.Data.Repository;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public abstract class PluginFeature : IDisposable
|
||||
{
|
||||
public PluginFeatureManifest Manifest {get; internal set;}
|
||||
|
||||
public abstract bool Initalize(DiscoDataContext dbContext);
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
// Nothing in Base Class
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class PluginFeatureAttribute : Attribute
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class PluginFeatureCategoryAttribute : Attribute
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Disco.Data.Repository;
|
||||
using Newtonsoft.Json;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public class PluginFeatureManifest
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string TypeName { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
private string CategoryTypeName { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public PluginManifest PluginManifest { get; private set; }
|
||||
[JsonIgnore]
|
||||
internal Type Type { get; private set; }
|
||||
[JsonIgnore]
|
||||
public Type CategoryType { get; private set; }
|
||||
|
||||
internal bool Initialize(DiscoDataContext dbContext, PluginManifest pluginManifest)
|
||||
{
|
||||
this.PluginManifest = pluginManifest;
|
||||
|
||||
if (this.Type == null)
|
||||
this.Type = this.PluginManifest.PluginAssembly.GetType(this.TypeName, true, true);
|
||||
|
||||
if (this.CategoryType == null)
|
||||
this.CategoryType = Type.GetType(this.CategoryTypeName, true, true);
|
||||
|
||||
using (var instance = this.CreateInstance())
|
||||
{
|
||||
instance.Initalize(dbContext);
|
||||
}
|
||||
|
||||
PluginsLog.LogInitializedPluginFeature(this.PluginManifest, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public PluginFeature CreateInstance()
|
||||
{
|
||||
var i = (PluginFeature)Activator.CreateInstance(Type);
|
||||
i.Manifest = this;
|
||||
return i;
|
||||
}
|
||||
public CategoryType CreateInstance<CategoryType>() where CategoryType : PluginFeature
|
||||
{
|
||||
if (typeof(CategoryType).IsAssignableFrom(this.Type))
|
||||
{
|
||||
var i = (CategoryType)Activator.CreateInstance(Type);
|
||||
i.Manifest = this;
|
||||
return i;
|
||||
}
|
||||
else
|
||||
throw new InvalidOperationException(string.Format("The feature [{0}] cannot be cast into type [{1}]", this.Type.Name, typeof(CategoryType).Name));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses reflection to build a Plugin Manifest
|
||||
/// </summary>
|
||||
/// <param name="pluginAssembly">Assembly containing a plugin</param>
|
||||
/// <returns>A plugin manifest for the first encountered plugin within the assembly</returns>
|
||||
public static PluginFeatureManifest FromPluginFeatureType(Type featureType, PluginManifest pluginManifest)
|
||||
{
|
||||
var featureAttribute = (PluginFeatureAttribute)featureType.GetCustomAttributes(typeof(PluginFeatureAttribute), false).FirstOrDefault();
|
||||
|
||||
if (featureAttribute == null)
|
||||
throw new ArgumentException(string.Format("Plugin Feature found [{0}], but no PluginFeatureAttribute found", featureType.Name), "featureType");
|
||||
|
||||
var featureId = featureAttribute.Id;
|
||||
var featureName = featureAttribute.Name;
|
||||
|
||||
// Determine Feature Category
|
||||
var featureCategoryType = featureType.BaseType;
|
||||
|
||||
if (featureCategoryType == null)
|
||||
throw new ArgumentException(string.Format("Plugin Feature found [{0}], but has no Base Type to determine its Category", featureType.Name), "featureType");
|
||||
|
||||
if (featureCategoryType == typeof(PluginFeature) || !typeof(PluginFeature).IsAssignableFrom(featureCategoryType))
|
||||
throw new ArgumentException(string.Format("Plugin Feature found [{0}], but its Base Type is not a valid Feature Category Type (Base Feature or not assignable)", featureType.Name), "featureType");
|
||||
|
||||
var featureCategoryAttribute = (PluginFeatureCategoryAttribute)featureCategoryType.GetCustomAttributes(typeof(PluginFeatureCategoryAttribute), false).FirstOrDefault();
|
||||
|
||||
if (featureCategoryAttribute == null)
|
||||
throw new ArgumentException(string.Format("Plugin Feature found [{0}], but its Base Type is not a valid Feature Category Type (no attribute)", featureType.Name), "featureType");
|
||||
|
||||
return new PluginFeatureManifest()
|
||||
{
|
||||
PluginManifest = pluginManifest,
|
||||
Id = featureId,
|
||||
Name = featureName,
|
||||
Type = featureType,
|
||||
TypeName = featureType.FullName,
|
||||
CategoryType = featureCategoryType,
|
||||
CategoryTypeName = featureCategoryType.FullName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Disco.Data.Repository;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public class PluginManifest
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Author { get; set; }
|
||||
public Version Version { get; set; }
|
||||
[JsonProperty]
|
||||
internal string AssemblyPath { get; set; }
|
||||
[JsonProperty]
|
||||
private string TypeName { get; set; }
|
||||
[JsonProperty]
|
||||
private string ConfigurationHandlerTypeName { get; set; }
|
||||
[JsonProperty]
|
||||
private string WebHandlerTypeName { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
internal Dictionary<string, string> AssemblyReferences { get; set; }
|
||||
|
||||
[JsonProperty]
|
||||
public List<PluginFeatureManifest> Features { get; private set; }
|
||||
|
||||
[JsonIgnore]
|
||||
internal Assembly PluginAssembly { get; private set; }
|
||||
[JsonIgnore]
|
||||
internal Type Type { get; private set; }
|
||||
[JsonIgnore]
|
||||
private Type ConfigurationHandlerType { get; set; }
|
||||
[JsonIgnore]
|
||||
private Type WebHandlerType { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public string PluginLocation { get; private set; }
|
||||
[JsonIgnore]
|
||||
public string StorageLocation { get; private set; }
|
||||
|
||||
private static Dictionary<string, Tuple<string, DateTime>> WebResourceHashes = new Dictionary<string, Tuple<string, DateTime>>();
|
||||
|
||||
public List<PluginFeatureManifest> GetFeatures(Type FeatureCategoryType)
|
||||
{
|
||||
return this.Features.Where(fm => fm.CategoryType.IsAssignableFrom(FeatureCategoryType)).ToList();
|
||||
}
|
||||
public PluginFeatureManifest GetFeature(string PluginFeatureId)
|
||||
{
|
||||
return this.Features.Where(fm => fm.Id == PluginFeatureId).FirstOrDefault();
|
||||
}
|
||||
|
||||
public Plugin CreateInstance()
|
||||
{
|
||||
var i = (Plugin)Activator.CreateInstance(Type);
|
||||
i.Manifest = this;
|
||||
return i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a Json Manifest
|
||||
/// </summary>
|
||||
/// <param name="FilePath">Path to the Json Manifest file</param>
|
||||
/// <returns></returns>
|
||||
public static PluginManifest FromPluginManifestFile(string FilePath)
|
||||
{
|
||||
using (Stream manifestStream = File.OpenRead(FilePath))
|
||||
{
|
||||
PluginManifest manifest = FromPluginManifestFile(manifestStream, Path.GetDirectoryName(FilePath));
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializes a Json Manifest
|
||||
/// </summary>
|
||||
/// <param name="FileStream">Stream containing the encoded Json Manifest File</param>
|
||||
/// <param name="PluginLocation">PluginLocation to be set in the manifest</param>
|
||||
/// <returns></returns>
|
||||
public static PluginManifest FromPluginManifestFile(Stream FileStream, string PluginLocation = null)
|
||||
{
|
||||
string manifestString;
|
||||
using (StreamReader manifestStreamReader = new StreamReader(FileStream))
|
||||
{
|
||||
manifestString = manifestStreamReader.ReadToEnd();
|
||||
}
|
||||
|
||||
var manifest = JsonConvert.DeserializeObject<PluginManifest>(manifestString);
|
||||
|
||||
manifest.PluginLocation = PluginLocation;
|
||||
|
||||
return manifest;
|
||||
}
|
||||
/// <summary>
|
||||
/// Uses reflection to build a Plugin Manifest
|
||||
/// </summary>
|
||||
/// <param name="pluginAssembly">Assembly containing a plugin</param>
|
||||
/// <returns>A plugin manifest for the first encountered plugin within the assembly</returns>
|
||||
public static PluginManifest FromPluginAssembly(Assembly assembly)
|
||||
{
|
||||
// Determine Plugin Properties
|
||||
var pluginType = (from type in assembly.GetTypes()
|
||||
where typeof(Plugin).IsAssignableFrom(type) && !type.IsAbstract
|
||||
select type).FirstOrDefault();
|
||||
|
||||
if (pluginType == null)
|
||||
throw new ArgumentException("No Plugin was found in this Assembly", "pluginAssembly");
|
||||
|
||||
var assemblyName = assembly.GetName();
|
||||
|
||||
var pluginAttributes = pluginType.GetCustomAttribute<PluginAttribute>(false);
|
||||
|
||||
if (pluginAttributes == null)
|
||||
throw new ArgumentException(string.Format("Plugin found [{0}], but no PluginAttribute found", pluginType.Name), "pluginAssembly");
|
||||
|
||||
var pluginId = pluginAttributes.Id;
|
||||
var pluginName = pluginAttributes.Name;
|
||||
var pluginAuthor = pluginAttributes.Author;
|
||||
|
||||
var pluginVersion = assemblyName.Version;
|
||||
var pluginAssemblyPath = Path.GetFileName(assembly.Location);
|
||||
var pluginTypeName = pluginType.FullName;
|
||||
var pluginLocation = Path.GetDirectoryName(assembly.Location);
|
||||
|
||||
// Find Configuration Handler
|
||||
var pluginConfigurationHandlerType = (from type in assembly.GetTypes()
|
||||
where typeof(PluginConfigurationHandler).IsAssignableFrom(type) && !type.IsAbstract
|
||||
select type).FirstOrDefault();
|
||||
if (pluginConfigurationHandlerType == null)
|
||||
throw new ArgumentException("A Plugin was found, but no Configuration Handler was found in this Assembly - this is required", "pluginAssembly");
|
||||
|
||||
// Find Web Handler
|
||||
var pluginWebHandlerType = (from type in assembly.GetTypes()
|
||||
where typeof(PluginWebHandler).IsAssignableFrom(type) && !type.IsAbstract
|
||||
select type).FirstOrDefault();
|
||||
|
||||
Dictionary<string, string> pluginAssemblyReferences = new Dictionary<string, string>();
|
||||
foreach (string referenceFilename in Directory.EnumerateFiles(pluginLocation, "*.dll", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
if (!referenceFilename.Equals(assembly.Location, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
Assembly pluginRefAssembly = Assembly.ReflectionOnlyLoadFrom(referenceFilename);
|
||||
pluginAssemblyReferences[pluginRefAssembly.FullName] = referenceFilename.Substring(pluginLocation.Length + 1);
|
||||
}
|
||||
catch (Exception) { } // Ignore Load Exceptions
|
||||
}
|
||||
}
|
||||
|
||||
PluginManifest pluginManifest = new PluginManifest()
|
||||
{
|
||||
Id = pluginId,
|
||||
Name = pluginName,
|
||||
Author = pluginAuthor,
|
||||
Version = pluginVersion,
|
||||
AssemblyPath = pluginAssemblyPath,
|
||||
TypeName = pluginTypeName,
|
||||
AssemblyReferences = pluginAssemblyReferences,
|
||||
PluginAssembly = assembly,
|
||||
Type = pluginType,
|
||||
PluginLocation = pluginLocation,
|
||||
ConfigurationHandlerType = pluginConfigurationHandlerType,
|
||||
ConfigurationHandlerTypeName = pluginConfigurationHandlerType.FullName,
|
||||
WebHandlerType = pluginWebHandlerType,
|
||||
WebHandlerTypeName = (pluginWebHandlerType == null ? null : pluginWebHandlerType.FullName)
|
||||
};
|
||||
|
||||
pluginManifest.Features = (from type in assembly.GetTypes()
|
||||
where typeof(PluginFeature).IsAssignableFrom(type) && !type.IsAbstract
|
||||
select PluginFeatureManifest.FromPluginFeatureType(type, pluginManifest)).ToList();
|
||||
|
||||
return pluginManifest;
|
||||
}
|
||||
|
||||
public string ToManifestFile()
|
||||
{
|
||||
return JsonConvert.SerializeObject(this, Formatting.Indented);
|
||||
}
|
||||
public bool InitializePlugin(DiscoDataContext dbContext)
|
||||
{
|
||||
var assemblyFullPath = Path.Combine(this.PluginLocation, this.AssemblyPath);
|
||||
|
||||
if (!File.Exists(assemblyFullPath))
|
||||
throw new FileNotFoundException(string.Format("Plugin Assembly [{0}] not found at: {1}", this.Id, assemblyFullPath), assemblyFullPath);
|
||||
|
||||
if (this.PluginAssembly == null)
|
||||
this.PluginAssembly = Assembly.LoadFile(assemblyFullPath);
|
||||
|
||||
if (this.PluginAssembly == null)
|
||||
throw new InvalidOperationException(string.Format("Unable to load Plugin Assembly [{0}] at: {1}", this.Id, assemblyFullPath));
|
||||
|
||||
PluginsLog.LogInitializingPluginAssembly(this.PluginAssembly);
|
||||
|
||||
// Check Manifest/Assembly Versions Match
|
||||
if (this.Version != this.PluginAssembly.GetName().Version)
|
||||
throw new InvalidOperationException(string.Format("The plugin [{0}] manifest version [{1}] doesn't match the plugin assembly [{2} : {3}]", this.Id, this.Version, assemblyFullPath, this.PluginAssembly.GetName().Version));
|
||||
|
||||
if (this.Type == null)
|
||||
this.Type = this.PluginAssembly.GetType(this.TypeName, true, true);
|
||||
|
||||
if (this.ConfigurationHandlerType == null)
|
||||
this.ConfigurationHandlerType = this.PluginAssembly.GetType(this.ConfigurationHandlerTypeName, true, true);
|
||||
|
||||
if (!string.IsNullOrEmpty(this.WebHandlerTypeName) && this.WebHandlerType == null)
|
||||
this.WebHandlerType = this.PluginAssembly.GetType(this.WebHandlerTypeName, true, true);
|
||||
|
||||
// Update non-static values
|
||||
this.StorageLocation = Path.Combine(dbContext.DiscoConfiguration.PluginStorageLocation, this.Id);
|
||||
|
||||
// Initialize Plugin
|
||||
using (var pluginInstance = this.CreateInstance())
|
||||
{
|
||||
pluginInstance.Initalize(dbContext);
|
||||
}
|
||||
PluginsLog.LogInitializedPlugin(this);
|
||||
|
||||
// Initialize Plugin Features
|
||||
if (Features != null)
|
||||
{
|
||||
foreach (var feature in Features)
|
||||
{
|
||||
feature.Initialize(dbContext, this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Features = new List<PluginFeatureManifest>();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public PluginConfigurationHandler CreateConfigurationHandler()
|
||||
{
|
||||
// Configuration Handler is Required
|
||||
if (this.ConfigurationHandlerType == null)
|
||||
throw new ArgumentNullException("ConfigurationType");
|
||||
if (!typeof(PluginConfigurationHandler).IsAssignableFrom(this.ConfigurationHandlerType))
|
||||
throw new ArgumentException("The Plugin ConfigurationHandlerType must inherit Disco.Services.Plugins.PluginConfigurationHandler", "ConfigurationHandlerType");
|
||||
|
||||
var handler = (PluginConfigurationHandler)Activator.CreateInstance(this.ConfigurationHandlerType);
|
||||
|
||||
handler.Manifest = this;
|
||||
|
||||
return handler;
|
||||
}
|
||||
[JsonIgnore]
|
||||
public bool HasWebHandler
|
||||
{
|
||||
get
|
||||
{
|
||||
return this.WebHandlerType != null;
|
||||
}
|
||||
}
|
||||
public PluginWebHandler CreateWebHandler(Controller HostController)
|
||||
{
|
||||
// Web Handler is Not Required
|
||||
if (this.WebHandlerType == null)
|
||||
return null;
|
||||
|
||||
if (!typeof(PluginWebHandler).IsAssignableFrom(this.WebHandlerType))
|
||||
throw new ArgumentException("The Plugin WebHandlerType must inherit Disco.Services.Plugins.PluginWebHandler", "WebHandlerType");
|
||||
|
||||
var handler = (PluginWebHandler)Activator.CreateInstance(this.WebHandlerType);
|
||||
|
||||
handler.Manifest = this;
|
||||
handler.HostController = HostController;
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
public Tuple<string, string> WebResourcePath(string Resource)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Resource))
|
||||
throw new ArgumentNullException("Resource");
|
||||
|
||||
if (Resource.Contains(".."))
|
||||
throw new ArgumentException("Resource Paths cannot navigate to the parent", "Resource");
|
||||
|
||||
var resourcePath = Path.Combine(this.PluginLocation, "WebResources", Resource.Replace(@"/", @"\"));
|
||||
|
||||
Tuple<string, DateTime> resourceHash;
|
||||
string resourceKey = string.Format("{0}://{1}", this.Name, Resource);
|
||||
if (WebResourceHashes.TryGetValue(resourceKey, out resourceHash))
|
||||
{
|
||||
#if DEBUG
|
||||
var fileDateCheck = System.IO.File.GetLastWriteTime(resourcePath);
|
||||
if (fileDateCheck == resourceHash.Item2)
|
||||
#endif
|
||||
return new Tuple<string, string>(resourcePath, resourceHash.Item1);
|
||||
}
|
||||
|
||||
if (!File.Exists(resourcePath))
|
||||
throw new FileNotFoundException(string.Format("Resource [{0}] not found", Resource), resourcePath);
|
||||
|
||||
var fileDate = System.IO.File.GetLastWriteTime(resourcePath);
|
||||
var fileBytes = System.IO.File.ReadAllBytes(resourcePath);
|
||||
if (fileBytes.Length > 0)
|
||||
{
|
||||
using (SHA256 sha = SHA256.Create())
|
||||
{
|
||||
byte[] hash = sha.ComputeHash(fileBytes);
|
||||
resourceHash = new Tuple<string, DateTime>(HttpServerUtility.UrlTokenEncode(hash), fileDate);
|
||||
}
|
||||
}
|
||||
WebResourceHashes[resourceKey] = resourceHash;
|
||||
|
||||
return new Tuple<string, string>(resourcePath, resourceHash.Item1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Routing;
|
||||
using RazorGenerator.Mvc;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public abstract class PluginWebHandler : IDisposable
|
||||
{
|
||||
public PluginManifest Manifest { get; set; }
|
||||
public Controller HostController { get; set; }
|
||||
|
||||
public abstract ActionResult ExecuteAction(string ActionName);
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
// Nothing in Base Class
|
||||
}
|
||||
|
||||
|
||||
#region Action Results
|
||||
|
||||
#region Compiled View
|
||||
private static string[] _viewFileNames = new string[] { "cshtml" };
|
||||
public ActionResult CompiledView(Type CompiledViewType, object Model, bool UseDiscoLayout)
|
||||
{
|
||||
string layoutPath = UseDiscoLayout ? "~/Views/Shared/_Layout.cshtml" : null;
|
||||
|
||||
IView v = new PrecompiledMvcView(this.HostController.Request.Path, layoutPath, CompiledViewType, false, _viewFileNames);
|
||||
|
||||
if (Model != null)
|
||||
this.HostController.ViewData.Model = Model;
|
||||
|
||||
return new ViewResult { View = v, ViewData = this.HostController.ViewData, TempData = this.HostController.TempData };
|
||||
}
|
||||
public ActionResult CompiledView(Type CompiledViewType, bool UseDiscoLayout)
|
||||
{
|
||||
return this.CompiledView(CompiledViewType, null, UseDiscoLayout);
|
||||
}
|
||||
public ActionResult CompiledView(Type CompiledViewType, object Model)
|
||||
{
|
||||
return this.CompiledView(CompiledViewType, Model, true);
|
||||
}
|
||||
public ActionResult CompiledView(Type CompiledViewType)
|
||||
{
|
||||
return this.CompiledView(CompiledViewType, false, true);
|
||||
}
|
||||
public ActionResult CompiledPartialView(Type PartialCompiledViewType, object Model)
|
||||
{
|
||||
IView v = new PrecompiledMvcView(this.HostController.Request.Path, PartialCompiledViewType, false, _viewFileNames);
|
||||
|
||||
if (Model != null)
|
||||
this.HostController.ViewData.Model = Model;
|
||||
|
||||
return new PartialViewResult { View = v, ViewData = this.HostController.ViewData, TempData = this.HostController.TempData };
|
||||
}
|
||||
public ActionResult CompiledPartialView(Type PartialCompiledViewType)
|
||||
{
|
||||
return this.CompiledView(PartialCompiledViewType, null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Content
|
||||
public ActionResult Content(string content, string contentType, Encoding contentEncoding)
|
||||
{
|
||||
return new ContentResult { Content = content, ContentType = contentType, ContentEncoding = contentEncoding };
|
||||
}
|
||||
public ActionResult Content(string content, string contentType)
|
||||
{
|
||||
return this.Content(content, null, null);
|
||||
}
|
||||
public ActionResult Content(string content)
|
||||
{
|
||||
return this.Content(content, null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Json
|
||||
public ActionResult Json(object data, JsonRequestBehavior behavior)
|
||||
{
|
||||
return new JsonResult { Data = data, ContentType = null, ContentEncoding = null, JsonRequestBehavior = behavior };
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region File
|
||||
public ActionResult File(Stream fileStream, string contentType)
|
||||
{
|
||||
return this.File(fileStream, contentType, null);
|
||||
}
|
||||
public ActionResult File(Stream fileStream, string contentType, string fileDownloadName)
|
||||
{
|
||||
return new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName };
|
||||
}
|
||||
public ActionResult File(byte[] fileContents, string contentType)
|
||||
{
|
||||
return this.File(fileContents, contentType, null);
|
||||
}
|
||||
public ActionResult File(byte[] fileContents, string contentType, string fileDownloadName)
|
||||
{
|
||||
return new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName };
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region HttpNotFound
|
||||
public ActionResult HttpNotFound(string statusDescription)
|
||||
{
|
||||
return new HttpNotFoundResult(statusDescription);
|
||||
}
|
||||
public ActionResult HttpNotFound()
|
||||
{
|
||||
return this.HttpNotFound(null);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Redirect
|
||||
public ActionResult RedirectToScheduledTaskStatus(string SessionId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(SessionId))
|
||||
throw new ArgumentNullException(SessionId);
|
||||
|
||||
return this.RedirectToAction("TaskStatus", "Logging", "Config", new { id = SessionId });
|
||||
}
|
||||
public ActionResult Redirect(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
throw new ArgumentNullException("url");
|
||||
|
||||
return new RedirectResult(url);
|
||||
}
|
||||
public ActionResult RedirectPermanent(string url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url))
|
||||
throw new ArgumentNullException("url");
|
||||
|
||||
return new RedirectResult(url, true);
|
||||
}
|
||||
public ActionResult RedirectToPluginAction(string PluginAction)
|
||||
{
|
||||
if (string.IsNullOrEmpty(PluginAction))
|
||||
throw new ArgumentNullException("PluginAction");
|
||||
|
||||
var routeValues = new RouteValueDictionary(new { PluginId = this.Manifest.Id, PluginAction = PluginAction });
|
||||
string pluginActionUrl = UrlHelper.GenerateUrl("Plugin", null, null, routeValues, RouteTable.Routes, this.HostController.Request.RequestContext, false);
|
||||
|
||||
return new RedirectResult(pluginActionUrl, false);
|
||||
}
|
||||
public ActionResult RedirectToPluginResource(string Resource, bool? Download)
|
||||
{
|
||||
var resourcePath = this.Manifest.WebResourcePath(Resource);
|
||||
|
||||
var routeValues = new RouteValueDictionary(new { PluginId = this.Manifest.Id, res = Resource });
|
||||
string pluginActionUrl = UrlHelper.GenerateUrl("Plugin_Resources", null, null, routeValues, RouteTable.Routes, this.HostController.Request.RequestContext, false);
|
||||
|
||||
pluginActionUrl += string.Format("?v={0}", resourcePath.Item2);
|
||||
|
||||
if (Download.HasValue && Download.Value)
|
||||
{
|
||||
pluginActionUrl += "&Download=true";
|
||||
}
|
||||
|
||||
return new RedirectResult(pluginActionUrl, false);
|
||||
}
|
||||
public ActionResult RedirectToPluginResource(string Resource)
|
||||
{
|
||||
return this.RedirectToPluginResource(Resource, null);
|
||||
}
|
||||
public ActionResult RedirectToRoute(string routeName, object routeValues)
|
||||
{
|
||||
RouteValueDictionary routeValueDictionary;
|
||||
if (routeValues != null)
|
||||
routeValueDictionary = new RouteValueDictionary(routeValues);
|
||||
else
|
||||
routeValueDictionary = new RouteValueDictionary();
|
||||
|
||||
return new RedirectToRouteResult(routeName, routeValueDictionary);
|
||||
}
|
||||
public ActionResult RedirectToRoute(string routeName)
|
||||
{
|
||||
return this.RedirectToRoute(routeName, null);
|
||||
}
|
||||
public ActionResult RedirectToAction(string actionName, string controller, string areaName, object routeValues)
|
||||
{
|
||||
RouteValueDictionary routeValueDictionary;
|
||||
if (routeValues != null)
|
||||
routeValueDictionary = new RouteValueDictionary(routeValues);
|
||||
else
|
||||
routeValueDictionary = new RouteValueDictionary();
|
||||
|
||||
routeValueDictionary["action"] = actionName;
|
||||
routeValueDictionary["controller"] = controller;
|
||||
if (areaName != null)
|
||||
routeValueDictionary["area"] = areaName;
|
||||
|
||||
return new RedirectToRouteResult(routeValueDictionary);
|
||||
}
|
||||
public ActionResult RedirectToAction(string actionName, string controller, string areaName)
|
||||
{
|
||||
return this.RedirectToAction(actionName, controller, areaName, null);
|
||||
}
|
||||
public ActionResult RedirectToAction(string actionName, string controller, object routeValues)
|
||||
{
|
||||
return this.RedirectToAction(actionName, controller, null, routeValues);
|
||||
}
|
||||
public ActionResult RedirectToAction(string actionName, string controller)
|
||||
{
|
||||
return this.RedirectToAction(actionName, controller, null, null);
|
||||
}
|
||||
public ActionResult RedirectToDiscoJob(int jobId)
|
||||
{
|
||||
return this.RedirectToAction("Show", "Job", null, new { id = jobId.ToString() });
|
||||
}
|
||||
public ActionResult RedirectToDiscoDevice(string DeviceSerialNumber)
|
||||
{
|
||||
return this.RedirectToAction("Show", "Device", null, new { id = DeviceSerialNumber });
|
||||
}
|
||||
public ActionResult RedirectToDiscoUser(string UserId)
|
||||
{
|
||||
return this.RedirectToAction("Show", "User", null, new { id = UserId });
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public abstract class PluginWebHandlerController : PluginWebHandler
|
||||
{
|
||||
|
||||
public override ActionResult ExecuteAction(string ActionName)
|
||||
{
|
||||
var handlerType = this.GetType();
|
||||
var methodDescriptor = FindControllerMethod(handlerType, ActionName);
|
||||
|
||||
if (methodDescriptor == null)
|
||||
return this.HttpNotFound("Unknown Plugin Method");
|
||||
|
||||
var methodParams = BuildMethodParameters(handlerType, methodDescriptor.MethodInfo, ActionName, this.HostController);
|
||||
|
||||
return (ActionResult)methodDescriptor.MethodInfo.Invoke(this, methodParams);
|
||||
}
|
||||
|
||||
private static WebHandlerCachedItem FindControllerMethod(Type Handler, string ActionName)
|
||||
{
|
||||
var descriptors = CacheWebHandler(Handler);
|
||||
WebHandlerCachedItem method;
|
||||
if (descriptors.TryGetValue(ActionName.ToLower(), out method))
|
||||
return method; // Not Found
|
||||
else
|
||||
return null; // Not Found
|
||||
}
|
||||
private static object[] BuildMethodParameters(Type Handler, MethodInfo methodInfo, string ActionName, Controller HostController)
|
||||
{
|
||||
var methodParams = methodInfo.GetParameters();
|
||||
var result = new object[methodParams.Length];
|
||||
|
||||
for (int i = 0; i < methodParams.Length; i++)
|
||||
{
|
||||
var methodParam = methodParams[i];
|
||||
|
||||
Type parameterType = methodParam.ParameterType;
|
||||
IModelBinder modelBinder = ModelBinders.Binders.GetBinder(parameterType);
|
||||
IValueProvider valueProvider = HostController.ValueProvider;
|
||||
string parameterName = methodParam.Name;
|
||||
|
||||
ModelBindingContext bindingContext = new ModelBindingContext()
|
||||
{
|
||||
FallbackToEmptyPrefix = true,
|
||||
ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType),
|
||||
ModelName = parameterName,
|
||||
ModelState = HostController.ViewData.ModelState,
|
||||
PropertyFilter = (p) => true,
|
||||
ValueProvider = valueProvider
|
||||
};
|
||||
|
||||
var parameterValue = modelBinder.BindModel(HostController.ControllerContext, bindingContext);
|
||||
|
||||
if (parameterValue == null && methodParam.HasDefaultValue)
|
||||
parameterValue = methodParam.DefaultValue;
|
||||
|
||||
result[i] = parameterValue;
|
||||
|
||||
//var paramInstance = Activator.CreateInstance(methodParam.ParameterType);
|
||||
|
||||
//IModelBinder binder = ModelBinders.Binders.GetBinder(methodParam.ParameterType);
|
||||
//ModelBindingContext bindingContext = new ModelBindingContext
|
||||
//{
|
||||
// ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => paramInstance, methodParam.ParameterType),
|
||||
// ModelName = methodParam.Name,
|
||||
// ModelState = HostController.ModelState,
|
||||
// PropertyFilter = (p) => true,
|
||||
// ValueProvider = HostController.ValueProvider
|
||||
//};
|
||||
//binder.BindModel(HostController.ControllerContext, bindingContext);
|
||||
|
||||
//if (methodParam.HasDefaultValue && paramInstance == null)
|
||||
// paramInstance = methodParam.DefaultValue;
|
||||
|
||||
//result[i] = paramInstance;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#region Method Cache
|
||||
private static Dictionary<Type, Dictionary<string, WebHandlerCachedItem>> WebHandlerCachedItems = new Dictionary<Type, Dictionary<string, WebHandlerCachedItem>>();
|
||||
private static Dictionary<string, WebHandlerCachedItem> CacheWebHandler(Type Handler)
|
||||
{
|
||||
Dictionary<string, WebHandlerCachedItem> result;
|
||||
|
||||
if (!WebHandlerCachedItems.TryGetValue(Handler, out result))
|
||||
{
|
||||
// Cache Miss
|
||||
result = new Dictionary<string, WebHandlerCachedItem>();
|
||||
var methods = Array.FindAll<MethodInfo>(Handler.GetMethods(BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), mi => { return !mi.IsSpecialName && typeof(ActionResult).IsAssignableFrom(mi.ReturnType); });
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var item = new WebHandlerCachedItem()
|
||||
{
|
||||
Method = method.Name,
|
||||
MethodInfo = method
|
||||
};
|
||||
result.Add(item.Method.ToLower(), item);
|
||||
}
|
||||
WebHandlerCachedItems[Handler] = result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
private class WebHandlerCachedItem
|
||||
{
|
||||
public string Method { get; set; }
|
||||
public MethodInfo MethodInfo { get; set; }
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,363 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Disco.Data.Repository;
|
||||
using System.IO.Compression;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public static class Plugins
|
||||
{
|
||||
private static Dictionary<Assembly, PluginManifest> _PluginAssemblyManifests;
|
||||
private static Dictionary<string, PluginManifest> _PluginManifests;
|
||||
internal static Dictionary<Type, string> FeatureCategoryDisplayNames;
|
||||
|
||||
private static object _PluginLock = new object();
|
||||
public static string PluginPath { get; private set; }
|
||||
|
||||
public static PluginManifest GetPlugin(string PluginId, Type ContainsCategoryType)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
PluginManifest manifest;
|
||||
if (_PluginManifests.TryGetValue(PluginId, out manifest))
|
||||
{
|
||||
if (ContainsCategoryType == null)
|
||||
return manifest;
|
||||
else
|
||||
{
|
||||
foreach (var featureManifest in manifest.Features)
|
||||
{
|
||||
if (ContainsCategoryType.IsAssignableFrom(featureManifest.CategoryType))
|
||||
return manifest;
|
||||
}
|
||||
|
||||
throw new InvalidFeatureCategoryTypeException(ContainsCategoryType, PluginId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnknownPluginException(PluginId);
|
||||
}
|
||||
}
|
||||
public static PluginManifest GetPlugin(string PluginId)
|
||||
{
|
||||
return GetPlugin(PluginId, null);
|
||||
}
|
||||
public static PluginManifest GetPlugin(Assembly PluginAssembly)
|
||||
{
|
||||
if (_PluginAssemblyManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
PluginManifest manifest;
|
||||
if (_PluginAssemblyManifests.TryGetValue(PluginAssembly, out manifest))
|
||||
{
|
||||
return manifest;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new UnknownPluginException(PluginAssembly.FullName);
|
||||
}
|
||||
}
|
||||
public static List<PluginManifest> GetPlugins()
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
return _PluginManifests.Values.ToList();
|
||||
}
|
||||
|
||||
public static PluginFeatureManifest GetPluginFeature(string PluginFeatureId, Type CategoryType)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
var featureManifest = _PluginManifests.Values.SelectMany(pm => pm.Features).Where(fm => fm.Id == PluginFeatureId).FirstOrDefault();
|
||||
|
||||
if (featureManifest == null)
|
||||
throw new UnknownPluginException(PluginFeatureId, "Unknown Feature");
|
||||
|
||||
if (CategoryType == null)
|
||||
return featureManifest;
|
||||
else
|
||||
if (CategoryType.IsAssignableFrom(featureManifest.CategoryType))
|
||||
return featureManifest;
|
||||
else
|
||||
throw new InvalidFeatureCategoryTypeException(CategoryType, PluginFeatureId);
|
||||
}
|
||||
public static PluginFeatureManifest GetPluginFeature(string PluginFeatureId)
|
||||
{
|
||||
return GetPluginFeature(PluginFeatureId, null);
|
||||
}
|
||||
public static List<PluginFeatureManifest> GetPluginFeatures(Type FeatureCategoryType)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
return _PluginManifests.Values.SelectMany(pm => pm.Features).Where(fm => fm.CategoryType.IsAssignableFrom(FeatureCategoryType)).OrderBy(fm => fm.PluginManifest.Name).ToList();
|
||||
}
|
||||
public static List<PluginFeatureManifest> GetPluginFeatures()
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
return _PluginManifests.Values.SelectMany(pm => pm.Features).ToList();
|
||||
}
|
||||
|
||||
|
||||
public static string PluginFeatureCategoryDisplayName(Type FeatureCategoryType)
|
||||
{
|
||||
if (FeatureCategoryType == null)
|
||||
throw new ArgumentNullException("FeatureType");
|
||||
|
||||
string displayName;
|
||||
if (FeatureCategoryDisplayNames.TryGetValue(FeatureCategoryType, out displayName))
|
||||
return displayName;
|
||||
else
|
||||
throw new InvalidOperationException(string.Format("Unknown Plugin Feature Category Type: [{0}]", FeatureCategoryType.Name));
|
||||
}
|
||||
|
||||
public static void InitalizePlugins(DiscoDataContext dbContext)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
{
|
||||
lock (_PluginLock)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
{
|
||||
Dictionary<string, PluginManifest> loadedPlugins = new Dictionary<string, PluginManifest>();
|
||||
|
||||
PluginPath = dbContext.DiscoConfiguration.PluginsLocation;
|
||||
|
||||
AppDomain appDomain = AppDomain.CurrentDomain;
|
||||
|
||||
// Subscribe to Assembly Resolving
|
||||
appDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
|
||||
DirectoryInfo pluginDirectoryRoot = new DirectoryInfo(PluginPath);
|
||||
if (pluginDirectoryRoot.Exists)
|
||||
{
|
||||
foreach (DirectoryInfo pluginDirectory in pluginDirectoryRoot.EnumerateDirectories())
|
||||
{
|
||||
string pluginManifestFilename = Path.Combine(pluginDirectory.FullName, "manifest.json");
|
||||
if (File.Exists(pluginManifestFilename))
|
||||
{
|
||||
PluginManifest pluginManifest = null;
|
||||
try
|
||||
{
|
||||
pluginManifest = PluginManifest.FromPluginManifestFile(pluginManifestFilename);
|
||||
|
||||
if (pluginManifest != null)
|
||||
{
|
||||
if (loadedPlugins.ContainsKey(pluginManifest.Id))
|
||||
throw new InvalidOperationException(string.Format("The plugin [{0}] is already initialized", pluginManifest.Id));
|
||||
|
||||
pluginManifest.InitializePlugin(dbContext);
|
||||
|
||||
loadedPlugins[pluginManifest.Id] = pluginManifest;
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { PluginsLog.LogInitializeException(pluginManifestFilename, ex); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_PluginManifests = loadedPlugins;
|
||||
|
||||
ReinitializePluginEnvironment();
|
||||
|
||||
// Install Plugins - TEMPORARY? Workaround until UI in place? Or Useful for 'built-in' plugins?
|
||||
if (pluginDirectoryRoot.Exists)
|
||||
{
|
||||
foreach (FileInfo pluginPackageFile in pluginDirectoryRoot.EnumerateFiles("*.discoPlugin", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
// Install Plugin
|
||||
InstallPlugin(dbContext, pluginPackageFile.FullName);
|
||||
// Delete Package File
|
||||
pluginPackageFile.Delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReinitializePluginEnvironment()
|
||||
{
|
||||
FeatureCategoryDisplayNames = InitializeFeatureCategoryDetails(_PluginManifests.Values);
|
||||
_PluginAssemblyManifests = _PluginManifests.Values.ToDictionary(p => p.PluginAssembly, p => p);
|
||||
}
|
||||
|
||||
public static void InstallPlugin(DiscoDataContext dbContext, String PackageFilePath)
|
||||
{
|
||||
using (var packageStream = File.OpenRead(PackageFilePath))
|
||||
{
|
||||
InstallPlugin(dbContext, packageStream);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InstallPlugin(DiscoDataContext dbContext, Stream PluginPackage)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
using (MemoryStream packageStream = new MemoryStream())
|
||||
{
|
||||
PluginPackage.CopyTo(packageStream);
|
||||
packageStream.Position = 0;
|
||||
|
||||
using (ZipArchive packageArchive = new ZipArchive(packageStream, ZipArchiveMode.Read, false))
|
||||
{
|
||||
|
||||
ZipArchiveEntry packageManifestEntry = packageArchive.GetEntry("manifest.json");
|
||||
if (packageManifestEntry == null)
|
||||
throw new InvalidDataException("The plugin package does not contain the 'manifest.json' entry");
|
||||
|
||||
PluginManifest packageManifest;
|
||||
|
||||
using (Stream packageManifestStream = packageManifestEntry.Open())
|
||||
{
|
||||
packageManifest = PluginManifest.FromPluginManifestFile(packageManifestStream);
|
||||
}
|
||||
|
||||
lock (_PluginLock)
|
||||
{
|
||||
if (_PluginManifests == null)
|
||||
throw new InvalidOperationException("Plugins have not been initialized");
|
||||
|
||||
// Ensure not already installed
|
||||
if (_PluginManifests.ContainsKey(packageManifest.Id))
|
||||
throw new InvalidOperationException(string.Format("The '{0} [{1}]' Plugin is already installed, please uninstall any existing versions before trying again", packageManifest.Name, packageManifest.Id));
|
||||
|
||||
string packagePath = Path.Combine(dbContext.DiscoConfiguration.PluginsLocation, packageManifest.Id);
|
||||
|
||||
// Force Delete of Existing Folder
|
||||
if (Directory.Exists(packagePath))
|
||||
Directory.Delete(packagePath, true);
|
||||
|
||||
Directory.CreateDirectory(packagePath);
|
||||
|
||||
// Extract Package Contents
|
||||
foreach (var packageEntry in packageArchive.Entries)
|
||||
{
|
||||
// Determine Extraction Path
|
||||
var packageEntryTarget = Path.Combine(packagePath, packageEntry.FullName);
|
||||
|
||||
// Create Sub Directories
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(packageEntryTarget));
|
||||
|
||||
using (var packageEntryStream = packageEntry.Open())
|
||||
{
|
||||
using (var packageTargetStream = File.Open(packageEntryTarget, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
{
|
||||
packageEntryStream.CopyTo(packageTargetStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reload Manifest
|
||||
packageManifest = PluginManifest.FromPluginManifestFile(Path.Combine(packagePath, "manifest.json"));
|
||||
|
||||
// Initialize Plugin
|
||||
packageManifest.InitializePlugin(dbContext);
|
||||
|
||||
// Add Plugin Manifest to Environment
|
||||
_PluginManifests[packageManifest.Id] = packageManifest;
|
||||
|
||||
// Reinitialize Plugin Environment
|
||||
ReinitializePluginEnvironment();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<Type, string> InitializeFeatureCategoryDetails(IEnumerable<PluginManifest> pluginManifests)
|
||||
{
|
||||
Dictionary<Type, string> categoryDisplayNames = new Dictionary<Type, string>();
|
||||
|
||||
// Always add 'Other'
|
||||
var otherFeatureType = typeof(Features.Other.OtherFeature);
|
||||
categoryDisplayNames.Add(otherFeatureType, ((PluginFeatureCategoryAttribute)otherFeatureType.GetCustomAttributes(typeof(PluginFeatureCategoryAttribute), false).FirstOrDefault()).DisplayName);
|
||||
|
||||
foreach (var pluginManifest in pluginManifests)
|
||||
{
|
||||
foreach (var featureManifest in pluginManifest.Features)
|
||||
{
|
||||
if (!categoryDisplayNames.ContainsKey(featureManifest.CategoryType))
|
||||
{
|
||||
string displayName = null;
|
||||
|
||||
var displayAttributes = featureManifest.CategoryType.GetCustomAttributes(typeof(PluginFeatureCategoryAttribute), true);
|
||||
if (displayAttributes != null && displayAttributes.Length > 0)
|
||||
displayName = ((PluginFeatureCategoryAttribute)(displayAttributes[0])).DisplayName;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(displayName))
|
||||
displayName = featureManifest.CategoryType.Name;
|
||||
|
||||
categoryDisplayNames[featureManifest.CategoryType] = displayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return categoryDisplayNames;
|
||||
}
|
||||
|
||||
#region Plugin Referenced Assemblies Resolving
|
||||
|
||||
public static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
if (args.RequestingAssembly.Location.StartsWith(PluginPath, StringComparison.InvariantCultureIgnoreCase) && _PluginManifests != null)
|
||||
{
|
||||
// Try best guess first
|
||||
PluginManifest requestingPlugin = _PluginManifests.Values.Where(p => p.Type.Assembly == args.RequestingAssembly).FirstOrDefault();
|
||||
if (requestingPlugin != null)
|
||||
{
|
||||
Assembly loadedAssembly = CurrentDomain_AssemblyResolve_ByPlugin(requestingPlugin, args);
|
||||
if (loadedAssembly != null)
|
||||
return loadedAssembly;
|
||||
}
|
||||
|
||||
// Try all Plugin References
|
||||
foreach (var pluginDef in _PluginManifests.Values)
|
||||
{
|
||||
Assembly loadedAssembly = CurrentDomain_AssemblyResolve_ByPlugin(pluginDef, args);
|
||||
if (loadedAssembly != null)
|
||||
return loadedAssembly;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static Assembly CurrentDomain_AssemblyResolve_ByPlugin(PluginManifest pluginManifest, ResolveEventArgs args)
|
||||
{
|
||||
if (pluginManifest.AssemblyReferences != null)
|
||||
{
|
||||
string assemblyPath;
|
||||
if (pluginManifest.AssemblyReferences.TryGetValue(args.Name, out assemblyPath))
|
||||
{
|
||||
var resolvedAssemblyPath = Path.Combine(pluginManifest.PluginLocation, assemblyPath);
|
||||
|
||||
try
|
||||
{
|
||||
Assembly loadedAssembly = Assembly.LoadFile(resolvedAssemblyPath);
|
||||
|
||||
PluginsLog.LogPluginReferenceAssemblyLoaded(args.Name, resolvedAssemblyPath, args.RequestingAssembly.FullName);
|
||||
|
||||
return loadedAssembly;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PluginsLog.LogPluginException(string.Format("Resolving Plugin Reference Assembly: '{0}' [{1}]; Requested by: '{2}' [{3}]; Disco.Plugins.DiscoPlugins.CurrentDomain_AssemblyResolve()", args.Name, resolvedAssemblyPath, args.RequestingAssembly.FullName, args.RequestingAssembly.Location), ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Disco.Services.Logging;
|
||||
using Disco.Services.Logging.Models;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public class PluginsLog : LogBase
|
||||
{
|
||||
private const int _ModuleId = 10;
|
||||
|
||||
public override string ModuleDescription { get { return "Plugins"; } }
|
||||
public override int ModuleId { get { return _ModuleId; } }
|
||||
public override string ModuleName { get { return "Plugins"; } }
|
||||
|
||||
public enum EventTypeIds
|
||||
{
|
||||
InitializingPlugins = 10,
|
||||
InitializingPluginAssembly,
|
||||
InitializedPlugin,
|
||||
InitializedPluginFeature,
|
||||
InitializeWarning = 15,
|
||||
InitializeError,
|
||||
InitializeException,
|
||||
InitializeExceptionWithInner,
|
||||
PluginException = 20,
|
||||
PluginExceptionWithInner,
|
||||
PluginReferenceAssemblyLoaded = 50,
|
||||
PluginConfigurationLoaded = 100,
|
||||
PluginConfigurationSaved = 104,
|
||||
PluginWebControllerAccessed = 200
|
||||
}
|
||||
|
||||
public static PluginsLog Current
|
||||
{
|
||||
get
|
||||
{
|
||||
return (PluginsLog)LogContext.LogModules[_ModuleId];
|
||||
}
|
||||
}
|
||||
private static void Log(EventTypeIds EventTypeId, params object[] Args)
|
||||
{
|
||||
Current.Log((int)EventTypeId, Args);
|
||||
}
|
||||
|
||||
public static void LogInitializingPlugins(string PluginDirectory)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.InitializingPlugins, PluginDirectory);
|
||||
}
|
||||
public static void LogInitializingPluginAssembly(Assembly PluginAssembly)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.InitializingPluginAssembly, PluginAssembly.FullName, PluginAssembly.Location);
|
||||
}
|
||||
public static void LogInitializedPlugin(PluginManifest Menifest)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.InitializedPlugin, Menifest.Id, Menifest.Version.ToString(3), Menifest.Type.Name, Menifest.Type.Assembly.Location);
|
||||
}
|
||||
public static void LogInitializedPluginFeature(PluginManifest PluginMenifest, PluginFeatureManifest FeatureManifest)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.InitializedPluginFeature, PluginMenifest.Id, FeatureManifest.Type.Name);
|
||||
}
|
||||
public static void LogInitializeWarning(string Warning)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.InitializeWarning, Warning);
|
||||
}
|
||||
public static void LogInitializeError(string Error)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.InitializeError, Error);
|
||||
}
|
||||
public static void LogPluginReferenceAssemblyLoaded(string AssemblyFullName, string AssemblyPath, string RequestedBy)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.PluginReferenceAssemblyLoaded, AssemblyFullName, AssemblyPath, RequestedBy);
|
||||
}
|
||||
public static void LogPluginConfigurationLoaded(string PluginId, string UserId)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.PluginConfigurationLoaded, PluginId, UserId);
|
||||
}
|
||||
public static void LogPluginConfigurationSaved(string PluginId, string UserId)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.PluginConfigurationSaved, PluginId, UserId);
|
||||
}
|
||||
public static void LogPluginWebControllerAccessed(string PluginId, string PluginAction, string UserId)
|
||||
{
|
||||
Current.Log((int)EventTypeIds.PluginWebControllerAccessed, PluginId, PluginAction, UserId);
|
||||
}
|
||||
|
||||
public static void LogInitializeException(string PluginFilename, Exception ex)
|
||||
{
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Log(EventTypeIds.InitializeExceptionWithInner, PluginFilename, ex.GetType().Name, ex.Message, ex.StackTrace, ex.InnerException.GetType().Name, ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(EventTypeIds.InitializeException, PluginFilename, ex.GetType().Name, ex.Message, ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
public static void LogPluginException(string Component, Exception ex)
|
||||
{
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
Log(EventTypeIds.PluginExceptionWithInner, Component, ex.GetType().Name, ex.Message, ex.StackTrace, ex.InnerException.GetType().Name, ex.InnerException.Message, ex.InnerException.StackTrace);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(EventTypeIds.PluginException, Component, ex.GetType().Name, ex.Message, ex.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
protected override List<Logging.Models.LogEventType> LoadEventTypes()
|
||||
{
|
||||
return new System.Collections.Generic.List<LogEventType>
|
||||
{
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.InitializingPlugins,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Initializing Plugins",
|
||||
Format = "Starting plugin discovery and initialization from: {0}",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = false,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.InitializingPluginAssembly,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Initializing Plugin Assembly",
|
||||
Format = "Initializing Plugin Assembly: [{0}] From '{1}'",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = false,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.InitializedPlugin,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Initialized Plugin",
|
||||
Format = "Initialized Plugin: '{0} (v{1})' [{2}] From '{3}'",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = false,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.InitializedPluginFeature,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Initialized Plugin Feature",
|
||||
Format = "Initialized Plugin Feature: '{1}' From '{0}'",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = false,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.InitializeWarning,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Initialize Warning",
|
||||
Format = "Initialize Warning: {0}",
|
||||
Severity = (int)LogEventType.Severities.Warning,
|
||||
UseLive = false,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.InitializeError,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Initialize Error",
|
||||
Format = "Initialize Error: {0}",
|
||||
Severity = (int)LogEventType.Severities.Error,
|
||||
UseLive = false,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.InitializeException,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Initialize Exception",
|
||||
Format = "Exception: {0}; {1}: {2}; {3}",
|
||||
Severity = (int)LogEventType.Severities.Error,
|
||||
UseLive = false,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.InitializeExceptionWithInner,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Initialize Exception with Inner Exception",
|
||||
Format = "Exception: {0}; {1}: {2}; {3}; Inner: {4}: {5}; {6}",
|
||||
Severity = (int)LogEventType.Severities.Error,
|
||||
UseLive = false,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.PluginException,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Plugin Exception",
|
||||
Format = "Exception: {0}; {1}: {2}; {3}",
|
||||
Severity = (int)LogEventType.Severities.Error,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.PluginExceptionWithInner,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Plugin Exception with Inner Exception",
|
||||
Format = "Exception: {0}; {1}: {2}; {3}; Inner: {4}: {5}; {6}",
|
||||
Severity = (int)LogEventType.Severities.Error,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.PluginReferenceAssemblyLoaded,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Plugin Reference Assembly Loaded",
|
||||
Format = "Loaded Plugin Reference Assembly: [{0}] From: '{1}'; Requested by: [{2}]",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.PluginConfigurationLoaded,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Plugin Configuration Loaded",
|
||||
Format = "Plugin Configuration Loaded: [{0}] by [{1}]",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.PluginConfigurationSaved,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Plugin Configuration Saved",
|
||||
Format = "Plugin Configuration Saved: [{0}] by [{1}]",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
},
|
||||
new LogEventType
|
||||
{
|
||||
Id = (int)EventTypeIds.PluginWebControllerAccessed,
|
||||
ModuleId = _ModuleId,
|
||||
Name = "Plugin Web Controller Accessed",
|
||||
Format = "Plugin Web Controller Accessed: Plugin [{0}], Action [{1}], By [{2}]",
|
||||
Severity = (int)LogEventType.Severities.Information,
|
||||
UseLive = true,
|
||||
UsePersist = true,
|
||||
UseDisplay = true
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Disco.Services.Plugins
|
||||
{
|
||||
public class UnknownPluginException : Exception
|
||||
{
|
||||
private string _pluginRequested;
|
||||
|
||||
public string PluginRequested
|
||||
{
|
||||
get
|
||||
{
|
||||
return _pluginRequested;
|
||||
}
|
||||
}
|
||||
|
||||
public UnknownPluginException(string PluginRequested)
|
||||
{
|
||||
this._pluginRequested = PluginRequested;
|
||||
}
|
||||
public UnknownPluginException(string PluginRequested, string Message) : base(Message)
|
||||
{
|
||||
this._pluginRequested = PluginRequested;
|
||||
}
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.Format("Unknown Plugin Id: [{0}]", _pluginRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user