// // // // // $Revision: 2318 $ // using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Xml; namespace ICSharpCode.Core { /// /// Specifies the action to be taken for a specific . /// public enum AddInAction { /// /// Enable the . /// Enable, /// /// Disable the . /// Disable, /// /// Install the . /// Install, /// /// Uninstall the . /// Uninstall, /// /// Update the . /// Update, /// /// The is disabled because it has been installed /// twice (duplicate identity). /// InstalledTwice, /// /// Tells that the cannot be loaded because not all dependencies are satisfied. /// DependencyError, /// /// A custom error has occurred (e.g. the AddIn disabled itself using a condition). /// CustomError } /// /// Manages all actions performed on s. /// An AddInManager GUI can use the methods here to install/update/uninstall /// s. /// /// There are three types of AddIns: /// - Preinstalled AddIns (added by host application) -> can only be disabled /// - External AddIns -> can be added, disabled and removed /// Removing external AddIns only removes the reference to the .addin file /// but does not delete the AddIn. /// - User AddIns -> are installed to UserAddInPath, can be installed, disabled /// and uninstalled /// public static class AddInManager { static string configurationFileName; static string addInInstallTemp; static string userAddInPath; /// /// Gets or sets the user addin path. /// This is the path where user AddIns are installed to. /// This property is normally initialized by . /// public static string UserAddInPath { get { return userAddInPath; } set { userAddInPath = value; } } /// /// Gets or sets the addin install temporary directory. /// This is a directory used to store AddIns that should be installed on /// the next start of the application. /// This property is normally initialized by . /// public static string AddInInstallTemp { get { return addInInstallTemp; } set { addInInstallTemp = value; } } /// /// Gets or sets the full name of the configuration file. /// In this file, the AddInManager stores the list of disabled AddIns /// and the list of installed external AddIns. /// This property is normally initialized by . /// public static string ConfigurationFileName { get { return configurationFileName; } set { configurationFileName = value; } } /// /// Installs the AddIns from AddInInstallTemp to the UserAddInPath. /// In case of installation errors, a error message is displayed to the user /// and the affected AddIn is added to the disabled list. /// This method is normally called by /// public static void InstallAddIns(List disabled) { if (!Directory.Exists(addInInstallTemp)) return; LoggingService.Info("AddInManager.InstallAddIns started"); if (!Directory.Exists(userAddInPath)) Directory.CreateDirectory(userAddInPath); string removeFile = Path.Combine(addInInstallTemp, "remove.txt"); bool allOK = true; List notRemoved = new List(); if (File.Exists(removeFile)) { using (StreamReader r = new StreamReader(removeFile)) { string addInName; while ((addInName = r.ReadLine()) != null) { addInName = addInName.Trim(); if (addInName.Length == 0) continue; string targetDir = Path.Combine(userAddInPath, addInName); if (!UninstallAddIn(disabled, addInName, targetDir)) { notRemoved.Add(addInName); allOK = false; } } } if (notRemoved.Count == 0) { LoggingService.Info("Deleting remove.txt"); File.Delete(removeFile); } else { LoggingService.Info("Rewriting remove.txt"); using (StreamWriter w = new StreamWriter(removeFile)) { notRemoved.ForEach(w.WriteLine); } } } foreach (string sourceDir in Directory.GetDirectories(addInInstallTemp)) { string addInName = Path.GetFileName(sourceDir); string targetDir = Path.Combine(userAddInPath, addInName); if (notRemoved.Contains(addInName)) { LoggingService.Info("Skipping installation of " + addInName + " because deinstallation failed."); continue; } if (UninstallAddIn(disabled, addInName, targetDir)) { LoggingService.Info("Installing " + addInName + "..."); Directory.Move(sourceDir, targetDir); } else { allOK = false; } } if (allOK) { try { Directory.Delete(addInInstallTemp, false); } catch (Exception ex) { LoggingService.Warn("Error removing install temp", ex); } } LoggingService.Info("AddInManager.InstallAddIns finished"); } static bool UninstallAddIn(List disabled, string addInName, string targetDir) { if (Directory.Exists(targetDir)) { LoggingService.Info("Removing " + addInName + "..."); try { Directory.Delete(targetDir, true); } catch (Exception ex) { disabled.Add(addInName); MessageService.ShowError("Error removing " + addInName + ":\n" + ex.Message + "\nThe AddIn will be " + "removed on the next start of " + MessageService.ProductName + " and is disabled for now."); return false; } } return true; } /// /// Uninstalls the user addin on next start. /// schedules the AddIn for /// deinstallation, you can unschedule it using /// /// /// The identity of the addin to remove. public static void RemoveUserAddInOnNextStart(string identity) { List removeEntries = new List(); string removeFile = Path.Combine(addInInstallTemp, "remove.txt"); if (File.Exists(removeFile)) { using (StreamReader r = new StreamReader(removeFile)) { string addInName; while ((addInName = r.ReadLine()) != null) { addInName = addInName.Trim(); if (addInName.Length > 0) removeEntries.Add(addInName); } } if (removeEntries.Contains(identity)) return; } removeEntries.Add(identity); if (!Directory.Exists(addInInstallTemp)) Directory.CreateDirectory(addInInstallTemp); using (StreamWriter w = new StreamWriter(removeFile)) { removeEntries.ForEach(w.WriteLine); } } /// /// Prevents a user AddIn from being uninstalled. /// schedules the AddIn for /// deinstallation, you can unschedule it using /// /// /// The identity of which to abort the removal. public static void AbortRemoveUserAddInOnNextStart(string identity) { string removeFile = Path.Combine(addInInstallTemp, "remove.txt"); if (!File.Exists(removeFile)) { return; } List removeEntries = new List(); using (StreamReader r = new StreamReader(removeFile)) { string addInName; while ((addInName = r.ReadLine()) != null) { addInName = addInName.Trim(); if (addInName.Length > 0) removeEntries.Add(addInName); } } if (removeEntries.Remove(identity)) { using (StreamWriter w = new StreamWriter(removeFile)) { removeEntries.ForEach(w.WriteLine); } } } /// /// Adds the specified external AddIns to the list of registered external /// AddIns. /// /// /// The list of AddIns to add. (use instances /// created by ). /// public static void AddExternalAddIns(IList addIns) { List addInFiles = new List(); List disabled = new List(); LoadAddInConfiguration(addInFiles, disabled); foreach (AddIn addIn in addIns) { if (!addInFiles.Contains(addIn.FileName)) addInFiles.Add(addIn.FileName); addIn.Enabled = false; addIn.Action = AddInAction.Install; AddInTree.InsertAddIn(addIn); } SaveAddInConfiguration(addInFiles, disabled); } /// /// Removes the specified external AddIns from the list of registered external /// AddIns. /// /// The list of AddIns to remove. /// (use external AddIns from the collection). public static void RemoveExternalAddIns(IList addIns) { List addInFiles = new List(); List disabled = new List(); LoadAddInConfiguration(addInFiles, disabled); foreach (AddIn addIn in addIns) { foreach (string identity in addIn.Manifest.Identities.Keys) { disabled.Remove(identity); } addInFiles.Remove(addIn.FileName); addIn.Action = AddInAction.Uninstall; if (!addIn.Enabled) { AddInTree.RemoveAddIn(addIn); } } SaveAddInConfiguration(addInFiles, disabled); } /// /// Marks the specified AddIns as enabled (will take effect after /// next application restart). /// public static void Enable(IList addIns) { List addInFiles = new List(); List disabled = new List(); LoadAddInConfiguration(addInFiles, disabled); foreach (AddIn addIn in addIns) { foreach (string identity in addIn.Manifest.Identities.Keys) { disabled.Remove(identity); } if (addIn.Action == AddInAction.Uninstall) { if (FileUtility.IsBaseDirectory(userAddInPath, addIn.FileName)) { foreach (string identity in addIn.Manifest.Identities.Keys) { AbortRemoveUserAddInOnNextStart(identity); } } else { if (!addInFiles.Contains(addIn.FileName)) addInFiles.Add(addIn.FileName); } } addIn.Action = AddInAction.Enable; } SaveAddInConfiguration(addInFiles, disabled); } /// /// Marks the specified AddIns as disabled (will take effect after /// next application restart). /// public static void Disable(IList addIns) { List addInFiles = new List(); List disabled = new List(); LoadAddInConfiguration(addInFiles, disabled); foreach (AddIn addIn in addIns) { string identity = addIn.Manifest.PrimaryIdentity; if (identity == null) throw new ArgumentException("The AddIn cannot be disabled because it has no identity."); if (!disabled.Contains(identity)) disabled.Add(identity); addIn.Action = AddInAction.Disable; } SaveAddInConfiguration(addInFiles, disabled); } /// /// Loads a configuration file. /// The 'file' from XML elements in the form "<AddIn file='full path to .addin file'>" will /// be added to , the 'addin' element from /// "<Disable addin='addin identity'>" will be added to , /// all other XML elements are ignored. /// /// File names of external AddIns are added to this collection. /// Identities of disabled addins are added to this collection. public static void LoadAddInConfiguration(List addInFiles, List disabledAddIns) { if (!File.Exists(configurationFileName)) return; using (XmlTextReader reader = new XmlTextReader(configurationFileName)) { while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element) { if (reader.Name == "AddIn") { string fileName = reader.GetAttribute("file"); if (fileName != null && fileName.Length > 0) { addInFiles.Add(fileName); } } else if (reader.Name == "Disable") { string addIn = reader.GetAttribute("addin"); if (addIn != null && addIn.Length > 0) { disabledAddIns.Add(addIn); } } } } } } /// /// Saves the AddIn configuration in the format expected by /// . /// /// List of file names of external AddIns. /// List of Identities of disabled addins. public static void SaveAddInConfiguration(List addInFiles, List disabledAddIns) { using (XmlTextWriter writer = new XmlTextWriter(configurationFileName, Encoding.UTF8)) { writer.Formatting = Formatting.Indented; writer.WriteStartDocument(); writer.WriteStartElement("AddInConfiguration"); foreach (string file in addInFiles) { writer.WriteStartElement("AddIn"); writer.WriteAttributeString("file", file); writer.WriteEndElement(); } foreach (string name in disabledAddIns) { writer.WriteStartElement("Disable"); writer.WriteAttributeString("addin", name); writer.WriteEndElement(); } writer.WriteEndDocument(); } } } }