Update: New Plugin Framework

Plugin Framework re-written. Initial commit.
This commit is contained in:
Gary Sharp
2013-02-07 18:34:41 +11:00
parent 5b93d7b1d1
commit da6d7a338f
30 changed files with 15018 additions and 14038 deletions
+188 -187
View File
@@ -1,188 +1,189 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{B80A737F-BD6A-4986-9182-DD7B932BD950}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Disco.Services</RootNamespace>
<AssemblyName>Disco.Services</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework">
<HintPath>..\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Quartz">
<HintPath>..\Resources\Libraries\Quartz\Quartz.dll</HintPath>
</Reference>
<Reference Include="RazorGenerator.Mvc, Version=1.5.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\RazorGenerator.Mvc.1.5.0.0\lib\net40\RazorGenerator.Mvc.dll</HintPath>
</Reference>
<Reference Include="SignalR, Version=0.5.1.10822, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SignalR.Server.0.5.3\lib\net40\SignalR.dll</HintPath>
</Reference>
<Reference Include="SignalR.Hosting.AspNet, Version=0.5.1.10822, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SignalR.Hosting.AspNet.0.5.3\lib\net45\SignalR.Hosting.AspNet.dll</HintPath>
</Reference>
<Reference Include="SignalR.Hosting.Common">
<HintPath>..\packages\SignalR.Hosting.Common.0.5.3\lib\net40\SignalR.Hosting.Common.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="WebActivator, Version=1.5.3.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\WebActivator.1.5.3\lib\net40\WebActivator.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="App_Start\RazorGeneratorMvcStart.cs" />
<Compile Include="Logging\LogBase.cs" />
<Compile Include="Logging\LogContext.cs" />
<Compile Include="Logging\LogReInitalizeJob.cs" />
<Compile Include="Logging\Models\LogEvent.cs" />
<Compile Include="Logging\Models\LogEventType.cs" />
<Compile Include="Logging\Models\LogLiveEvent.cs" />
<Compile Include="Logging\Models\LogModule.cs" />
<Compile Include="Logging\ReadLogContext.cs" />
<Compile Include="Logging\SystemLog.cs" />
<Compile Include="Logging\Targets\LogLiveContext.cs" />
<Compile Include="Logging\Targets\LogPersistContext.cs" />
<Compile Include="Logging\Utilities.cs" />
<None Include="_Plugins\Categories\CertificateProvider\CertificateProviderPlugin.cs" />
<None Include="_Plugins\Categories\CertificateProvider\CertificateProviderLog.cs" />
<None Include="_Plugins\Categories\InteroperabilityProvider\InteroperabilityProviderPlugin.cs" />
<None Include="_Plugins\Categories\WarrantyProvider\WarrantyProviderPlugin.cs" />
<None Include="_Plugins\Categories\WarrantyProvider\WarrantyProviderSubmitJobException.cs" />
<None Include="_Plugins\PluginCategoryAttribute.cs" />
<None Include="_Plugins\PluginDefinition.cs" />
<None Include="_Plugins\Plugins.cs" />
<None Include="_Plugins\Plugin.cs" />
<None Include="_Plugins\IPluginConfiguration.cs" />
<None Include="_Plugins\IPluginWebController.cs" />
<None Include="_Plugins\InvalidCategoryTypeException.cs" />
<None Include="_Plugins\PluginExtensions.cs" />
<None Include="_Plugins\PluginsLog.cs" />
<None Include="_Plugins\PluginWebControllerException.cs" />
<None Include="_Plugins\PluginWebControllerExtensions.cs" />
<None Include="_Plugins\UnknownPluginException.cs" />
<Compile Include="Plugins\PluginAttribute.cs" />
<Compile Include="Plugins\PluginExtensions.cs" />
<Compile Include="Plugins\PluginFeatureAttribute.cs" />
<Compile Include="Plugins\Features\CertificateProvider\CertificateProviderFeature.cs" />
<Compile Include="Plugins\Features\CertificateProvider\CertificateProviderLog.cs" />
<Compile Include="Plugins\Features\InteroperabilityProvider\InteroperabilityProviderFeature.cs" />
<Compile Include="Plugins\Features\Other\OtherFeature.cs" />
<Compile Include="Plugins\Features\WarrantyProvider\WarrantyProviderFeature.cs" />
<Compile Include="Plugins\Features\WarrantyProvider\WarrantyProviderSubmitJobException.cs" />
<Compile Include="Plugins\InvalidFeatureCategoryTypeException.cs" />
<Compile Include="Plugins\Plugin.cs" />
<Compile Include="Plugins\PluginConfigurationHandler.cs" />
<Compile Include="Plugins\PluginFeature.cs" />
<Compile Include="Plugins\PluginFeatureCategoryAttribute.cs" />
<Compile Include="Plugins\PluginFeatureManifest.cs" />
<Compile Include="Plugins\PluginManifest.cs" />
<Compile Include="Plugins\Plugins.cs" />
<Compile Include="Plugins\PluginsLog.cs" />
<Compile Include="Plugins\PluginWebHandler.cs" />
<Compile Include="Plugins\PluginWebHandlerController.cs" />
<Compile Include="Plugins\UnknownPluginException.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tasks\ScheduledTask.cs" />
<Compile Include="Tasks\ScheduledTaskCleanup.cs" />
<Compile Include="Tasks\ScheduledTasks.cs" />
<Compile Include="Tasks\ScheduledTasksLog.cs" />
<Compile Include="Tasks\ScheduledTaskStatus.cs" />
<Compile Include="Tasks\ScheduledTasksLiveStatusService.cs" />
<Compile Include="Tasks\ScheduledTaskStatusLive.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Disco.Data\Disco.Data.csproj">
<Project>{85A6BD19-2C64-4746-8F2C-A68A86E8C2D7}</Project>
<Name>Disco.Data</Name>
</ProjectReference>
<ProjectReference Include="..\Disco.Models\Disco.Models.csproj">
<Project>{FBC05512-FCCA-4B16-9E76-8C413C5DE6C9}</Project>
<Name>Disco.Models</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_UseGlobalSettings="True" BuildVersion_DetectChanges="False" BuildVersion_BuildAction="ReBuild" BuildVersion_StartDate="2001/1/1" />
</VisualStudio>
</ProjectExtensions>
<PropertyGroup>
<PostBuildEvent>
if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{B80A737F-BD6A-4986-9182-DD7B932BD950}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Disco.Services</RootNamespace>
<AssemblyName>Disco.Services</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="EntityFramework">
<HintPath>..\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Web.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Quartz">
<HintPath>..\Resources\Libraries\Quartz\Quartz.dll</HintPath>
</Reference>
<Reference Include="RazorGenerator.Mvc, Version=1.5.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\RazorGenerator.Mvc.1.5.0.0\lib\net40\RazorGenerator.Mvc.dll</HintPath>
</Reference>
<Reference Include="SignalR, Version=0.5.1.10822, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SignalR.Server.0.5.3\lib\net40\SignalR.dll</HintPath>
</Reference>
<Reference Include="SignalR.Hosting.AspNet, Version=0.5.1.10822, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\SignalR.Hosting.AspNet.0.5.3\lib\net45\SignalR.Hosting.AspNet.dll</HintPath>
</Reference>
<Reference Include="SignalR.Hosting.Common">
<HintPath>..\packages\SignalR.Hosting.Common.0.5.3\lib\net40\SignalR.Hosting.Common.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.DataAnnotations" />
<Reference Include="System.Core" />
<Reference Include="System.Data.Entity" />
<Reference Include="System.Data.SqlServerCe, Version=4.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\Microsoft.SqlServer.Compact.4.0.8876.1\lib\net40\System.Data.SqlServerCe.dll</HintPath>
</Reference>
<Reference Include="System.IO.Compression" />
<Reference Include="System.Web" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="WebActivator, Version=1.5.3.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\WebActivator.1.5.3\lib\net40\WebActivator.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Logging\LogBase.cs" />
<Compile Include="Logging\LogContext.cs" />
<Compile Include="Logging\LogReInitalizeJob.cs" />
<Compile Include="Logging\Models\LogEvent.cs" />
<Compile Include="Logging\Models\LogEventType.cs" />
<Compile Include="Logging\Models\LogLiveEvent.cs" />
<Compile Include="Logging\Models\LogModule.cs" />
<Compile Include="Logging\ReadLogContext.cs" />
<Compile Include="Logging\SystemLog.cs" />
<Compile Include="Logging\Targets\LogLiveContext.cs" />
<Compile Include="Logging\Targets\LogPersistContext.cs" />
<Compile Include="Logging\Utilities.cs" />
<None Include="_Plugins\Categories\CertificateProvider\CertificateProviderPlugin.cs" />
<None Include="_Plugins\Categories\CertificateProvider\CertificateProviderLog.cs" />
<None Include="_Plugins\Categories\InteroperabilityProvider\InteroperabilityProviderPlugin.cs" />
<None Include="_Plugins\Categories\WarrantyProvider\WarrantyProviderPlugin.cs" />
<None Include="_Plugins\Categories\WarrantyProvider\WarrantyProviderSubmitJobException.cs" />
<None Include="_Plugins\PluginCategoryAttribute.cs" />
<None Include="_Plugins\PluginDefinition.cs" />
<None Include="_Plugins\Plugins.cs" />
<None Include="_Plugins\Plugin.cs" />
<None Include="_Plugins\IPluginConfiguration.cs" />
<None Include="_Plugins\IPluginWebController.cs" />
<None Include="_Plugins\InvalidCategoryTypeException.cs" />
<None Include="_Plugins\PluginExtensions.cs" />
<None Include="_Plugins\PluginsLog.cs" />
<None Include="_Plugins\PluginWebControllerException.cs" />
<None Include="_Plugins\PluginWebControllerExtensions.cs" />
<None Include="_Plugins\UnknownPluginException.cs" />
<Compile Include="Plugins\InstallPluginTask.cs" />
<Compile Include="Plugins\PluginAttribute.cs" />
<Compile Include="Plugins\PluginExtensions.cs" />
<Compile Include="Plugins\PluginFeatureAttribute.cs" />
<Compile Include="Plugins\Features\CertificateProvider\CertificateProviderFeature.cs" />
<Compile Include="Plugins\Features\CertificateProvider\CertificateProviderLog.cs" />
<Compile Include="Plugins\Features\InteroperabilityProvider\InteroperabilityProviderFeature.cs" />
<Compile Include="Plugins\Features\Other\OtherFeature.cs" />
<Compile Include="Plugins\Features\WarrantyProvider\WarrantyProviderFeature.cs" />
<Compile Include="Plugins\Features\WarrantyProvider\WarrantyProviderSubmitJobException.cs" />
<Compile Include="Plugins\InvalidFeatureCategoryTypeException.cs" />
<Compile Include="Plugins\Plugin.cs" />
<Compile Include="Plugins\PluginConfigurationHandler.cs" />
<Compile Include="Plugins\PluginFeature.cs" />
<Compile Include="Plugins\PluginFeatureCategoryAttribute.cs" />
<Compile Include="Plugins\PluginFeatureManifest.cs" />
<Compile Include="Plugins\PluginManifest.cs" />
<Compile Include="Plugins\Plugins.cs" />
<Compile Include="Plugins\PluginsLog.cs" />
<Compile Include="Plugins\PluginWebHandler.cs" />
<Compile Include="Plugins\PluginWebHandlerController.cs" />
<Compile Include="Plugins\UninstallPluginTask.cs" />
<Compile Include="Plugins\UnknownPluginException.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tasks\ScheduledTask.cs" />
<Compile Include="Tasks\ScheduledTaskCleanup.cs" />
<Compile Include="Tasks\ScheduledTasks.cs" />
<Compile Include="Tasks\ScheduledTasksLog.cs" />
<Compile Include="Tasks\ScheduledTaskStatus.cs" />
<Compile Include="Tasks\ScheduledTasksLiveStatusService.cs" />
<Compile Include="Tasks\ScheduledTaskStatusLive.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Disco.Data\Disco.Data.csproj">
<Project>{85A6BD19-2C64-4746-8F2C-A68A86E8C2D7}</Project>
<Name>Disco.Data</Name>
</ProjectReference>
<ProjectReference Include="..\Disco.Models\Disco.Models.csproj">
<Project>{FBC05512-FCCA-4B16-9E76-8C413C5DE6C9}</Project>
<Name>Disco.Models</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_UseGlobalSettings="True" BuildVersion_DetectChanges="False" BuildVersion_BuildAction="ReBuild" BuildVersion_StartDate="2001/1/1" />
</VisualStudio>
</ProjectExtensions>
<PropertyGroup>
<PostBuildEvent>
if not exist "$(TargetDir)x86" md "$(TargetDir)x86"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\x86\*.*" "$(TargetDir)x86"
if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64"
xcopy /s /y "$(SolutionDir)packages\Microsoft.SqlServer.Compact.4.0.8876.1\NativeBinaries\amd64\*.*" "$(TargetDir)amd64"</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
+132
View File
@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Disco.Data.Repository;
using Disco.Services.Tasks;
using Quartz;
namespace Disco.Services.Plugins
{
public class InstallPluginTask : ScheduledTask
{
public override string TaskName { get { return "Installing Plugin"; } }
protected override void ExecuteTask()
{
string packageFilePath = (string)this.ExecutionContext.JobDetail.JobDataMap["PackageFilePath"];
bool DeletePackageAfterInstall = (bool)this.ExecutionContext.JobDetail.JobDataMap["DeletePackageAfterInstall"];
if (!Plugins.PluginsLoaded)
throw new InvalidOperationException("Plugins have not been initialized");
this.Status.UpdateStatus(10, "Opening Plugin Package", Path.GetFileName(packageFilePath));
using (var packageStream = File.OpenRead(packageFilePath))
{
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);
}
this.Status.UpdateStatus(20, string.Format("{0} [{1} v{2}] by {3}", packageManifest.Name, packageManifest.Id, packageManifest.Version.ToString(4), packageManifest.Author), "Initializing Install Environment");
PluginsLog.LogInstalling(packageManifest);
lock (Plugins._PluginLock)
{
if (!Plugins.PluginsLoaded)
throw new InvalidOperationException("Plugins have not been initialized");
// Ensure not already installed
if (Plugins.GetPlugins().FirstOrDefault(p => p.Id == packageManifest.Id) != null)
throw new InvalidOperationException(string.Format("The '{0} [{1}]' Plugin is already installed, please uninstall any existing versions before trying again", packageManifest.Name, packageManifest.Id));
using (DiscoDataContext dbContext = new DiscoDataContext())
{
string packagePath = Path.Combine(dbContext.DiscoConfiguration.PluginsLocation, packageManifest.Id);
// Force Delete of Existing Folder
if (Directory.Exists(packagePath))
{
this.Status.UpdateStatus(25, "Removing Existing Files");
Directory.Delete(packagePath, true);
}
Directory.CreateDirectory(packagePath);
this.Status.UpdateStatus(30, "Extracting Files");
double extractFileInterval = (double)50 / packageArchive.Entries.Count;
int countExtractedFiles = 0;
// Extract Package Contents
foreach (var packageEntry in packageArchive.Entries)
{
this.Status.UpdateStatus(30 + (countExtractedFiles++ * extractFileInterval), string.Format("Extracting File: {0}", packageEntry.FullName));
// 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"));
// Install Plugin
this.Status.UpdateStatus(80, "Initial Package Configuration");
packageManifest.InstallPlugin(dbContext, this.Status);
// Initialize Plugin
this.Status.UpdateStatus(98, "Initializing Plugin for Use");
packageManifest.InitializePlugin(dbContext);
// Add Plugin Manifest to Host Environment
Plugins.AddPlugin(packageManifest);
PluginsLog.LogInstalled(packageManifest);
this.Status.Finished("Plugin Installation Completed", string.Format("/Config/Plugins/{0}", System.Web.HttpUtility.UrlEncode(packageManifest.Id)));
}
}
}
}
if (DeletePackageAfterInstall)
File.Delete(packageFilePath);
}
public static ScheduledTaskStatus InstallPlugin(string PackageFilePath, bool DeletePackageAfterInstall)
{
if (ScheduledTasks.GetTaskStatuses(typeof(InstallPluginTask)).Where(s => s.IsRunning).Count() > 0)
throw new InvalidOperationException("A plugin is already being Installed");
JobDataMap taskData = new JobDataMap() { { "PackageFilePath", PackageFilePath }, { "DeletePackageAfterInstall", DeletePackageAfterInstall } };
var instance = new InstallPluginTask();
return instance.ScheduleTask(taskData);
}
}
}
+34 -31
View File
@@ -1,31 +1,34 @@
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));
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Disco.Data.Repository;
using Disco.Services.Tasks;
namespace Disco.Services.Plugins
{
public abstract class Plugin : IDisposable
{
public PluginManifest Manifest {get; internal set;}
#region Lifecycle
// Events/Triggers for Custom Plugin Initialization (Optional)
public virtual void Install(DiscoDataContext dbContext, ScheduledTaskStatus Status) { return; }
public virtual void Initialize(DiscoDataContext dbContext) { return; }
public virtual void Uninstall(DiscoDataContext dbContext, bool UninstallData, ScheduledTaskStatus Status) { return; }
public virtual void BeforeUpdate(DiscoDataContext dbContext, PluginManifest UpdateManifest, ScheduledTaskStatus Status) { return; }
public virtual void AfterUpdate(DiscoDataContext dbContext, PluginManifest PreviousManifest) { return; }
#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));
}
}
}
+22 -21
View File
@@ -1,21 +1,22 @@
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
}
}
}
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;}
// Allow Custom Initialization (Optional)
public virtual void Initialize(DiscoDataContext dbContext) { return; }
public virtual void Dispose()
{
// Nothing in Base Class
}
}
}
+107 -107
View File
@@ -1,107 +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
};
}
}
}
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.Initialize(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
};
}
}
}
+386 -321
View File
@@ -1,321 +1,386 @@
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);
}
}
}
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 Disco.Services.Tasks;
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; }
[JsonIgnore]
private bool environmentInitalized { get; 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);
}
private bool InitializePluginEnvironment(DiscoDataContext dbContext)
{
if (!environmentInitalized)
{
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);
environmentInitalized = true;
}
return true;
}
internal bool BeforePluginUpdate(DiscoDataContext dbContext, PluginManifest UpdateManifest, ScheduledTaskStatus Status)
{
// Initialize Plugin
InitializePluginEnvironment(dbContext);
using (var pluginInstance = this.CreateInstance())
{
pluginInstance.BeforeUpdate(dbContext, UpdateManifest, Status);
}
return true;
}
internal bool AfterPluginUpdate(DiscoDataContext dbContext, PluginManifest PreviousManifest)
{
// Initialize Plugin
InitializePluginEnvironment(dbContext);
using (var pluginInstance = this.CreateInstance())
{
pluginInstance.AfterUpdate(dbContext, PreviousManifest);
}
return true;
}
internal bool UninstallPlugin(DiscoDataContext dbContext, bool UninstallData, ScheduledTaskStatus Status)
{
// Initialize Plugin
InitializePluginEnvironment(dbContext);
using (var pluginInstance = this.CreateInstance())
{
pluginInstance.Uninstall(dbContext, UninstallData, Status);
}
return true;
}
internal bool InstallPlugin(DiscoDataContext dbContext, ScheduledTaskStatus Status)
{
// Initialize Plugin
InitializePluginEnvironment(dbContext);
using (var pluginInstance = this.CreateInstance())
{
pluginInstance.Install(dbContext, Status);
}
return true;
}
internal bool InitializePlugin(DiscoDataContext dbContext)
{
// Initialize Plugin
InitializePluginEnvironment(dbContext);
// Initialize Plugin
using (var pluginInstance = this.CreateInstance())
{
pluginInstance.Initialize(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);
}
}
}
+427 -363
View File
@@ -1,363 +1,427 @@
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
}
}
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;
internal static object _PluginLock = new object();
public static string PluginPath { get; private set; }
public static bool PluginsLoaded
{
get
{
return (_PluginManifests != null);
}
}
internal static void AddPlugin(PluginManifest Manifest)
{
lock (_PluginLock)
{
if (_PluginManifests.ContainsKey(Manifest.Id))
throw new InvalidOperationException(string.Format("The '{0} [{1}]' Plugin is already installed, please uninstall any existing versions before trying again", Manifest.Name, Manifest.Id));
// Add Plugin Manifest to Environment
_PluginManifests[Manifest.Id] = Manifest;
// Reinitialize Plugin Host Environment
Plugins.ReinitializePluginHostEnvironment();
}
}
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));
// Check for Update
string updatePackagePath = Path.Combine(pluginDirectoryRoot.FullName, string.Format("{0}.discoPlugin", pluginManifest.Id));
if (File.Exists(updatePackagePath))
{
// Update Plugin
pluginManifest = UpdatePlugin(dbContext, pluginManifest, updatePackagePath);
}
if (pluginManifest != null)
{
pluginManifest.InitializePlugin(dbContext);
loadedPlugins[pluginManifest.Id] = pluginManifest;
}
}
}
catch (Exception ex) { PluginsLog.LogInitializeException(pluginManifestFilename, ex); }
}
else
{
string pluginManifestUninstallFilename = Path.Combine(pluginDirectory.FullName, "manifest.uninstall.json");
if (File.Exists(pluginManifestUninstallFilename))
{
PluginManifest uninstallManifest = PluginManifest.FromPluginManifestFile(pluginManifestUninstallFilename);
// Remove All Files
DateTime removeRetryTime = DateTime.Now.AddSeconds(60);
while (true)
{
UnauthorizedAccessException lastAccessException;
try
{
pluginDirectory.Delete(true);
break;
}
catch (UnauthorizedAccessException ex) { lastAccessException = ex; }
if (removeRetryTime < DateTime.Now)
throw lastAccessException;
System.Threading.Thread.Sleep(2000);
}
// Check for Data Removal
bool DataUninstalled = false;
string pluginStorageLocation = Path.Combine(dbContext.DiscoConfiguration.PluginStorageLocation, uninstallManifest.Id);
string pluginManifestUninstallDataFilename = Path.Combine(pluginStorageLocation, "manifest.uninstall.json");
if (File.Exists(pluginManifestUninstallDataFilename))
{
DataUninstalled = true;
Directory.Delete(pluginStorageLocation, true);
}
PluginsLog.LogUninstalled(uninstallManifest, DataUninstalled);
}
}
}
}
_PluginManifests = loadedPlugins;
ReinitializePluginHostEnvironment();
}
}
}
}
private static void ReinitializePluginHostEnvironment()
{
FeatureCategoryDisplayNames = InitializeFeatureCategoryDetails(_PluginManifests.Values);
_PluginAssemblyManifests = _PluginManifests.Values.ToDictionary(p => p.PluginAssembly, p => p);
}
public static PluginManifest UpdatePlugin(DiscoDataContext dbContext, PluginManifest ExistingManifest, String UpdatePluginPackageFilePath)
{
PluginManifest updatedManifest;
using (var packageStream = File.OpenRead(UpdatePluginPackageFilePath))
{
updatedManifest = UpdatePlugin(dbContext, ExistingManifest, packageStream);
}
// Remove Update after processing
File.Delete(UpdatePluginPackageFilePath);
return updatedManifest;
}
public static PluginManifest UpdatePlugin(DiscoDataContext dbContext, PluginManifest ExistingManifest, Stream UpdatePluginPackage)
{
using (MemoryStream packageStream = new MemoryStream())
{
if (UpdatePluginPackage.Position != 0)
UpdatePluginPackage.Position = 0;
UpdatePluginPackage.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);
}
if (ExistingManifest.Version == packageManifest.Version)
{
// Skip Update if already installed
PluginsLog.LogInitializeWarning(string.Format("This plugin [{0}] version [{1}] is already installed, skipping Update", ExistingManifest.Id, ExistingManifest.Version));
return ExistingManifest;
}
if (ExistingManifest.Version > packageManifest.Version)
{
throw new InvalidDataException("A newer version of this plugin is already installed");
}
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"));
// Trigger AfterPluginUpdate
packageManifest.AfterPluginUpdate(dbContext, ExistingManifest);
PluginsLog.LogAfterUpdate(ExistingManifest, packageManifest);
// Return Updated Manifest
return packageManifest;
}
}
}
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
}
}
+373 -275
View File
@@ -1,275 +1,373 @@
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
}
};
}
}
}
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,
Installing = 500,
Installed = 550,
BeforeUpdate = 600,
AfterUpdate = 700,
Uninstalling = 800,
Uninstalled = 850
}
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 LogInstalling(PluginManifest Manifest)
{
Current.Log((int)EventTypeIds.Installing, Manifest.Id, Manifest.Version.ToString(4), Manifest.Name);
}
public static void LogInstalled(PluginManifest Manifest)
{
Current.Log((int)EventTypeIds.Installing, Manifest.Id, Manifest.Version.ToString(4), Manifest.Name, Manifest.PluginLocation);
}
public static void LogBeforeUpdate(PluginManifest ExistingManifest, PluginManifest UpdateManifest)
{
Current.Log((int)EventTypeIds.BeforeUpdate, ExistingManifest.Id, ExistingManifest.Name, ExistingManifest.PluginLocation, ExistingManifest.Version.ToString(4), UpdateManifest.Version.ToString(4));
}
public static void LogAfterUpdate(PluginManifest ExistingManifest, PluginManifest UpdateManifest)
{
Current.Log((int)EventTypeIds.AfterUpdate, UpdateManifest.Id, UpdateManifest.Name, UpdateManifest.PluginLocation, ExistingManifest.Version.ToString(4), UpdateManifest.Version.ToString(4));
}
public static void LogUninstalling(PluginManifest Manifest, bool UninstallData)
{
Current.Log((int)EventTypeIds.Uninstalling, Manifest.Id, Manifest.Name, Manifest.PluginLocation, Manifest.Version.ToString(4), UninstallData);
}
public static void LogUninstalled(PluginManifest Manifest, bool UninstalledData)
{
Current.Log((int)EventTypeIds.Uninstalled, Manifest.Id, Manifest.Name, Manifest.PluginLocation, Manifest.Version.ToString(4), UninstalledData);
}
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
},
new LogEventType
{
Id = (int)EventTypeIds.Installing,
ModuleId = _ModuleId,
Name = "Installing Plugin",
Format = "Installing Plugin: {2} [{0} v{1}]",
Severity = (int)LogEventType.Severities.Information,
UseLive = true,
UsePersist = true,
UseDisplay = true
},
new LogEventType
{
Id = (int)EventTypeIds.Installed,
ModuleId = _ModuleId,
Name = "Plugin Installed",
Format = "Plugin Installed: {2} [{0} v{1}], Location: {3}",
Severity = (int)LogEventType.Severities.Information,
UseLive = true,
UsePersist = true,
UseDisplay = true
},
new LogEventType
{
Id = (int)EventTypeIds.BeforeUpdate,
ModuleId = _ModuleId,
Name = "Updating Plugin",
Format = "Updating Plugin: {1} [{0}], v{3} -> v{4}, Location: {2}",
Severity = (int)LogEventType.Severities.Information,
UseLive = true,
UsePersist = true,
UseDisplay = true
},
new LogEventType
{
Id = (int)EventTypeIds.AfterUpdate,
ModuleId = _ModuleId,
Name = "Plugin Updated",
Format = "Plugin Updated: {1} [{0}], v{3} -> v{4}, Location: {2}",
Severity = (int)LogEventType.Severities.Information,
UseLive = false,
UsePersist = true,
UseDisplay = true
},
new LogEventType
{
Id = (int)EventTypeIds.Uninstalling,
ModuleId = _ModuleId,
Name = "Uninstalling Plugin",
Format = "Uninstalling Plugin: {1} [{0} v{3}], Location: {2}, UninstallData: {4}",
Severity = (int)LogEventType.Severities.Information,
UseLive = true,
UsePersist = true,
UseDisplay = true
},
new LogEventType
{
Id = (int)EventTypeIds.Uninstalled,
ModuleId = _ModuleId,
Name = "Plugin Uninstalled",
Format = "Plugin Uninstalled: {1} [{0} v{3}], Location: {2}, UninstallData: {4}",
Severity = (int)LogEventType.Severities.Information,
UseLive = false,
UsePersist = true,
UseDisplay = true
}
};
}
}
}
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using Disco.Services.Tasks;
using Quartz;
namespace Disco.Services.Plugins
{
public class UninstallPluginTask : ScheduledTask
{
public override string TaskName { get { return "Uninstalling Plugin"; } }
protected override void ExecuteTask()
{
PluginManifest manifest = (PluginManifest)this.ExecutionContext.JobDetail.JobDataMap["PluginManifest"];
bool UninstallData = (bool)this.ExecutionContext.JobDetail.JobDataMap["UninstallData"];
this.Status.UpdateStatus(25, string.Format("Uninstalling Plugin: {0} [{1}]", manifest.Name, manifest.Id), "Queuing plugin for uninstall");
PluginsLog.LogUninstalling(manifest, UninstallData);
string manifestFileLocation = Path.Combine(manifest.PluginLocation, "manifest.json");
if (!File.Exists(manifestFileLocation))
throw new FileNotFoundException("Plugin Manifest File Not Found", manifestFileLocation);
string manifestUninstallFileLocation = Path.Combine(manifest.PluginLocation, "manifest.uninstall.json");
if (File.Exists(manifestUninstallFileLocation))
File.Delete(manifestUninstallFileLocation);
File.Move(manifestFileLocation, manifestUninstallFileLocation);
if (UninstallData && Directory.Exists(manifest.StorageLocation))
{
var manifestDataUninstallFileLocation = Path.Combine(manifest.StorageLocation, "manifest.uninstall.json");
if (File.Exists(manifestDataUninstallFileLocation))
File.Delete(manifestDataUninstallFileLocation);
File.Copy(manifestUninstallFileLocation, manifestDataUninstallFileLocation);
}
this.Status.Finished("Restarting Disco, please wait...", "/Config/Plugins");
RestartApp(1500);
}
public static ScheduledTaskStatus UninstallPlugin(PluginManifest Manifest, bool UninstallData)
{
if (ScheduledTasks.GetTaskStatuses(typeof(InstallPluginTask)).Where(s => s.IsRunning).Count() > 0)
throw new InvalidOperationException("A plugin is already being Uninstalled");
JobDataMap taskData = new JobDataMap() { { "PluginManifest", Manifest }, { "UninstallData", UninstallData } };
var instance = new UninstallPluginTask();
return instance.ScheduleTask(taskData);
}
#region Restart App
private static object _restartTimerLock = new object();
private static Timer _restartTimer;
private void RestartApp(int DelayMilliseconds)
{
lock (_restartTimerLock)
{
if (_restartTimer != null)
{
_restartTimer.Dispose();
}
_restartTimer = new Timer((state) =>
{
HttpRuntime.UnloadAppDomain();
//AppDomain.Unload(AppDomain.CurrentDomain);
}, null, DelayMilliseconds, Timeout.Infinite);
}
}
#endregion
}
}
+36 -36
View File
@@ -1,36 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Disco.Services")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Disco.Services")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3f4b432d-2abd-4bc2-86cd-f90650b73930")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.0131.2002")]
[assembly: AssemblyFileVersion("1.2.0131.2002")]
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Disco.Services")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Disco.Services")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("3f4b432d-2abd-4bc2-86cd-f90650b73930")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.2.0207.1727")]
[assembly: AssemblyFileVersion("1.2.0207.1727")]
+108 -108
View File
@@ -1,108 +1,108 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Quartz;
using Disco.Data.Repository;
namespace Disco.Services.Tasks
{
public abstract class ScheduledTask : IJob
{
public abstract void InitalizeScheduledTask(DiscoDataContext dbContext);
internal protected ScheduledTaskStatus Status { get; private set; }
internal protected IJobExecutionContext ExecutionContext { get; private set; }
public abstract bool CancelInitiallySupported { get; }
public abstract bool SingleInstanceTask { get; }
public virtual bool IsSilent { get { return false; } }
public virtual bool LogExceptionsOnly { get { return false; } }
public abstract string TaskName { get; }
protected abstract void ExecuteTask();
#region Protected Triggers
/// <summary>
/// Schedules the Task to Begin Immediately
/// </summary>
protected ScheduledTaskStatus ScheduleTask()
{
return ScheduleTask(null, null);
}
/// <summary>
/// Schedules the Task to Begin Immediately
/// </summary>
/// <param name="DataMap">DataMap passed into the executing Task</param>
/// <returns></returns>
protected ScheduledTaskStatus ScheduleTask(JobDataMap DataMap)
{
return ScheduleTask(null, DataMap);
}
/// <summary>
/// Schedules the Task to Begin based on the Trigger
/// </summary>
/// <param name="Trigger">Trigger for the Task</param>
protected ScheduledTaskStatus ScheduleTask(TriggerBuilder Trigger)
{
return ScheduleTask(Trigger, null);
}
/// <summary>
/// Schedules the Task to Begin based on the Trigger including the DataMap
/// </summary>
/// <param name="Trigger">Trigger for the Task</param>
/// <param name="DataMap">DataMap passed into the executing Task</param>
/// <returns></returns>
protected ScheduledTaskStatus ScheduleTask(TriggerBuilder Trigger, JobDataMap DataMap)
{
if (Trigger == null)
Trigger = TriggerBuilder.Create(); // Defaults to Start Immediately
if (DataMap != null)
Trigger = Trigger.UsingJobData(DataMap);
return ScheduledTasks.RegisterTask(this, Trigger);
}
#endregion
public void Execute(IJobExecutionContext context)
{
// Task Status
this.ExecutionContext = context;
this.Status = context.GetDiscoScheduledTaskStatus();
if (this.Status == null)
this.Status = ScheduledTasks.RegisterTask(this);
try
{
if (!this.LogExceptionsOnly)
ScheduledTasksLog.LogScheduledTaskExecuted(this.Status.TaskName, this.Status.SessionId);
this.Status.Started();
this.ExecuteTask();
}
catch (Exception ex)
{
ScheduledTasksLog.LogScheduledTaskException(this.Status.TaskName, this.Status.SessionId, this.GetType(), ex);
this.Status.SetTaskException(ex);
}
finally
{
if (!this.Status.FinishedTimestamp.HasValue) // Scheduled Task Didn't Trigger 'Finished'
this.Status.Finished();
var nextTriggerTime = context.NextFireTimeUtc;
if (nextTriggerTime.HasValue)
{ // Continuous Task
this.Status.Reset(nextTriggerTime.Value.LocalDateTime);
}
else
{
this.UnregisterTask();
}
if (!this.LogExceptionsOnly)
ScheduledTasksLog.LogScheduledTaskFinished(this.Status.TaskName, this.Status.SessionId);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Quartz;
using Disco.Data.Repository;
namespace Disco.Services.Tasks
{
public abstract class ScheduledTask : IJob
{
public virtual void InitalizeScheduledTask(DiscoDataContext dbContext) { return; }
internal protected ScheduledTaskStatus Status { get; private set; }
internal protected IJobExecutionContext ExecutionContext { get; private set; }
public virtual bool CancelInitiallySupported { get { return true; } }
public virtual bool SingleInstanceTask { get { return true; } }
public virtual bool IsSilent { get { return false; } }
public virtual bool LogExceptionsOnly { get { return false; } }
public abstract string TaskName { get; }
protected abstract void ExecuteTask();
#region Protected Triggers
/// <summary>
/// Schedules the Task to Begin Immediately
/// </summary>
protected ScheduledTaskStatus ScheduleTask()
{
return ScheduleTask(null, null);
}
/// <summary>
/// Schedules the Task to Begin Immediately
/// </summary>
/// <param name="DataMap">DataMap passed into the executing Task</param>
/// <returns></returns>
protected ScheduledTaskStatus ScheduleTask(JobDataMap DataMap)
{
return ScheduleTask(null, DataMap);
}
/// <summary>
/// Schedules the Task to Begin based on the Trigger
/// </summary>
/// <param name="Trigger">Trigger for the Task</param>
protected ScheduledTaskStatus ScheduleTask(TriggerBuilder Trigger)
{
return ScheduleTask(Trigger, null);
}
/// <summary>
/// Schedules the Task to Begin based on the Trigger including the DataMap
/// </summary>
/// <param name="Trigger">Trigger for the Task</param>
/// <param name="DataMap">DataMap passed into the executing Task</param>
/// <returns></returns>
protected ScheduledTaskStatus ScheduleTask(TriggerBuilder Trigger, JobDataMap DataMap)
{
if (Trigger == null)
Trigger = TriggerBuilder.Create(); // Defaults to Start Immediately
if (DataMap != null)
Trigger = Trigger.UsingJobData(DataMap);
return ScheduledTasks.RegisterTask(this, Trigger);
}
#endregion
public void Execute(IJobExecutionContext context)
{
// Task Status
this.ExecutionContext = context;
this.Status = context.GetDiscoScheduledTaskStatus();
if (this.Status == null)
this.Status = ScheduledTasks.RegisterTask(this);
try
{
if (!this.LogExceptionsOnly)
ScheduledTasksLog.LogScheduledTaskExecuted(this.Status.TaskName, this.Status.SessionId);
this.Status.Started();
this.ExecuteTask();
}
catch (Exception ex)
{
ScheduledTasksLog.LogScheduledTaskException(this.Status.TaskName, this.Status.SessionId, this.GetType(), ex);
this.Status.SetTaskException(ex);
}
finally
{
if (!this.Status.FinishedTimestamp.HasValue) // Scheduled Task Didn't Trigger 'Finished'
this.Status.Finished();
var nextTriggerTime = context.NextFireTimeUtc;
if (nextTriggerTime.HasValue)
{ // Continuous Task
this.Status.Reset(nextTriggerTime.Value.LocalDateTime);
}
else
{
this.UnregisterTask();
}
if (!this.LogExceptionsOnly)
ScheduledTasksLog.LogScheduledTaskFinished(this.Status.TaskName, this.Status.SessionId);
}
}
}
}