using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; 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 packageUrlPath = (string)this.ExecutionContext.JobDetail.JobDataMap["PackageUrl"]; 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"); if (!string.IsNullOrEmpty(packageUrlPath)) { this.Status.UpdateStatus(0, "Downloading Plugin Package", "Connecting..."); if (File.Exists(packageFilePath)) File.Delete(packageFilePath); if (!Directory.Exists(Path.GetDirectoryName(packageFilePath))) Directory.CreateDirectory(Path.GetDirectoryName(packageFilePath)); // Need to Download the Package WebClient downloader = new WebClient(); DateTime progressExpires = DateTime.Now; downloader.DownloadProgressChanged += (sender, e) => { Console.WriteLine(e.ProgressPercentage); if (progressExpires <= DateTime.Now) { this.Status.UpdateStatus(e.ProgressPercentage, string.Format("{0} of {1} KB downloaded", e.BytesReceived / 1024, e.TotalBytesToReceive / 1024)); progressExpires = DateTime.Now.AddMilliseconds(250); } }; downloader.DownloadFileTaskAsync(new Uri(packageUrlPath), packageFilePath).Wait(); } 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); // Check for Compatibility var compatibilityData = Plugins.LoadCompatibilityData(dbContext); var pluginCompatibility = compatibilityData.Plugins.FirstOrDefault(i => i.Id.Equals(packageManifest.Id, StringComparison.InvariantCultureIgnoreCase) && packageManifest.Version == Version.Parse(i.Version)); if (pluginCompatibility != null && !pluginCompatibility.Compatible) throw new InvalidOperationException(string.Format("The plugin [{0} v{1}] is not compatible: {2}", packageManifest.Id, packageManifest.VersionFormatted, pluginCompatibility.Reason)); // 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.SetFinishedUrl(string.Format("/Config/Plugins/{0}", System.Web.HttpUtility.UrlEncode(packageManifest.Id))); this.Status.UpdateStatus(100, "Plugin Installation Completed"); } } } } if (DeletePackageAfterInstall) File.Delete(packageFilePath); } public static ScheduledTaskStatus InstallLocalPlugin(string PackageFilePath, bool DeletePackageAfterInstall) { return InstallPlugin(null, PackageFilePath, DeletePackageAfterInstall); } public static ScheduledTaskStatus InstallPlugin(string PackageUrl, string PackageFilePath, bool DeletePackageAfterInstall) { if (ScheduledTasks.GetTaskStatuses(typeof(InstallPluginTask)).Where(s => s.IsRunning).Count() > 0) throw new InvalidOperationException("A plugin is already being Installed"); if (ScheduledTasks.GetTaskStatuses(typeof(UpdatePluginTask)).Where(s => s.IsRunning).Count() > 0) throw new InvalidOperationException("A plugin is being Updated"); if (ScheduledTasks.GetTaskStatuses(typeof(UninstallPluginTask)).Where(s => s.IsRunning).Count() > 0) throw new InvalidOperationException("A plugin is being Uninstalled"); JobDataMap taskData = new JobDataMap() { { "PackageUrl", PackageUrl }, { "PackageFilePath", PackageFilePath }, { "DeletePackageAfterInstall", DeletePackageAfterInstall } }; var instance = new InstallPluginTask(); return instance.ScheduleTask(taskData); } } }