diff options
author | Lluis Sanchez <llsan@microsoft.com> | 2022-06-14 13:32:00 +0300 |
---|---|---|
committer | Lluis Sanchez <llsan@microsoft.com> | 2022-09-13 20:38:42 +0300 |
commit | 831a6714d10976c4f5845efd06c9333dbf4c18d1 (patch) | |
tree | c71a2a9df62cd51bdb53d5bc0ea20d7cf24d3220 | |
parent | 86b644dd323c725888d09cb53423fe430fc684a9 (diff) |
Thread safe add-in database
-rw-r--r-- | Mono.Addins/Mono.Addins.Database/AddinDatabase.cs | 336 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs | 68 | ||||
-rw-r--r-- | Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs | 126 |
3 files changed, 327 insertions, 203 deletions
diff --git a/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs b/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs index e7baccb..4029185 100644 --- a/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs +++ b/Mono.Addins/Mono.Addins.Database/AddinDatabase.cs @@ -28,40 +28,42 @@ using System; -using System.Threading; using System.Collections; -using System.Collections.Specialized; -using System.IO; -using System.Xml; -using System.Reflection; -using Mono.Addins.Description; using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; using System.Linq; -using Mono.Addins.Serialization; +using Mono.Addins.Description; namespace Mono.Addins.Database { - class AddinDatabase + class AddinDatabase { public const string GlobalDomain = "global"; public const string UnknownDomain = "unknown"; public const string VersionTag = "004"; - List<Addin> allSetupInfos; - List<Addin> addinSetupInfos; - List<Addin> rootSetupInfos; + readonly AddinEngine addinEngine; + readonly AddinRegistry registry; + readonly string addinDbDir; + readonly FileDatabase fileDatabase; + readonly object localLock = new object (); + + bool allSetupInfosLoaded; + ImmutableArray<Addin> allSetupInfos; + ImmutableArray<Addin> addinSetupInfos; + ImmutableArray<Addin> rootSetupInfos; + + Dictionary<string, Addin> cachedAddinSetupInfos = new Dictionary<string, Addin> (); + ImmutableAddinHostIndex hostIndex; + internal static bool RunningSetupProcess; + bool fatalDatabseError; - Hashtable cachedAddinSetupInfos = new Hashtable (); - AddinHostIndex hostIndex; - FileDatabase fileDatabase; - string addinDbDir; DatabaseConfiguration config = null; - AddinRegistry registry; int lastDomainId; - AddinEngine addinEngine; - AddinFileSystemExtension fs = new AddinFileSystemExtension (); + AddinFileSystemExtension fileSystemExtension = new AddinFileSystemExtension (); List<object> extensions = new List<object> (); public AddinDatabase (AddinEngine addinEngine, AddinRegistry registry) @@ -71,13 +73,18 @@ namespace Mono.Addins.Database addinDbDir = Path.Combine (registry.AddinCachePath, "addin-db-" + VersionTag); fileDatabase = new FileDatabase (AddinDbDir); } + + public AddinDatabaseTransaction BeginTransaction () + { + return new AddinDatabaseTransaction (); + } string AddinDbDir { get { return addinDbDir; } } public AddinFileSystemExtension FileSystem { - get { return fs; } + get { return fileSystemExtension; } } public string AddinCachePath { @@ -116,36 +123,34 @@ namespace Mono.Addins.Database } } - public void Clear () - { - if (Directory.Exists (AddinCachePath)) - Directory.Delete (AddinCachePath, true); - if (Directory.Exists (AddinFolderCachePath)) - Directory.Delete (AddinFolderCachePath, true); - } - public void CopyExtensions (AddinDatabase other) { - foreach (object o in other.extensions) - RegisterExtension (o); + lock (extensions) { + foreach (object o in other.extensions) + RegisterExtension (o); + } } public void RegisterExtension (object extension) { - extensions.Add (extension); - if (extension is AddinFileSystemExtension) - fs = (AddinFileSystemExtension) extension; - else - throw new NotSupportedException (); + lock (extensions) { + extensions.Add (extension); + if (extension is AddinFileSystemExtension) + fileSystemExtension = (AddinFileSystemExtension)extension; + else + throw new NotSupportedException (); + } } public void UnregisterExtension (object extension) { - extensions.Remove (extension); - if ((extension as AddinFileSystemExtension) == fs) - fs = new AddinFileSystemExtension (); - else - throw new InvalidOperationException (); + lock (extensions) { + extensions.Remove (extension); + if ((extension as AddinFileSystemExtension) == fileSystemExtension) + fileSystemExtension = new AddinFileSystemExtension (); + else + throw new InvalidOperationException (); + } } public ExtensionNodeSet FindNodeSet (string domain, string addinId, string id) @@ -231,44 +236,39 @@ namespace Mono.Addins.Database if ((type & AddinSearchFlagsInternal.LatestVersionsOnly) != 0) throw new InvalidOperationException ("LatestVersionsOnly flag not supported here"); - if (allSetupInfos == null) { - Dictionary<string,Addin> adict = new Dictionary<string, Addin> (); - - // Global add-ins are valid for any private domain - if (domain != AddinDatabase.GlobalDomain) - FindInstalledAddins (adict, AddinDatabase.GlobalDomain, idFilter); - - FindInstalledAddins (adict, domain, idFilter); - List<Addin> alist = new List<Addin> (adict.Values); - UpdateLastVersionFlags (alist); - if (idFilter != null) - return alist; - allSetupInfos = alist; + if (!allSetupInfosLoaded) { + lock (localLock) { + if (!allSetupInfosLoaded) { + Dictionary<string, Addin> adict = new Dictionary<string, Addin> (); + + // Global add-ins are valid for any private domain + if (domain != AddinDatabase.GlobalDomain) + FindInstalledAddins (adict, AddinDatabase.GlobalDomain, idFilter); + + FindInstalledAddins (adict, domain, idFilter); + List<Addin> alist = new List<Addin> (adict.Values); + UpdateLastVersionFlags (alist); + if (idFilter != null) + return alist; + allSetupInfos = alist.ToImmutableArray (); + addinSetupInfos = alist.Where (addin => !addin.Description.IsRoot).ToImmutableArray (); + rootSetupInfos = alist.Where (addin => addin.Description.IsRoot).ToImmutableArray (); + allSetupInfosLoaded = true; + } + } } if ((type & AddinSearchFlagsInternal.IncludeAll) == AddinSearchFlagsInternal.IncludeAll) return FilterById (allSetupInfos, idFilter); if ((type & AddinSearchFlagsInternal.IncludeAddins) == AddinSearchFlagsInternal.IncludeAddins) { - if (addinSetupInfos == null) { - addinSetupInfos = new List<Addin> (); - foreach (Addin adn in allSetupInfos) - if (!adn.Description.IsRoot) - addinSetupInfos.Add (adn); - } return FilterById (addinSetupInfos, idFilter); } else { - if (rootSetupInfos == null) { - rootSetupInfos = new List<Addin> (); - foreach (Addin adn in allSetupInfos) - if (adn.Description.IsRoot) - rootSetupInfos.Add (adn); - } return FilterById (rootSetupInfos, idFilter); } } - IEnumerable<Addin> FilterById (List<Addin> addins, string id) + IEnumerable<Addin> FilterById (IEnumerable<Addin> addins, string id) { if (id == null) return addins; @@ -330,23 +330,26 @@ namespace Mono.Addins.Database else return null; } - + Addin GetInstalledDomainAddin (string domain, string id, bool exactVersionMatch, bool enabledOnly, bool dbLockCheck) { - Addin sinfo = null; string idd = id + " " + domain; - object ob = cachedAddinSetupInfos [idd]; - if (ob != null) { - sinfo = ob as Addin; + Addin sinfo; + bool found; + lock (cachedAddinSetupInfos) { + found = cachedAddinSetupInfos.TryGetValue (idd, out sinfo); + } + + if (found) { if (sinfo != null) { if (!enabledOnly || sinfo.Enabled) return sinfo; if (exactVersionMatch) return null; - } - else if (enabledOnly) + } else if (enabledOnly) { // Ignore the 'not installed' flag when disabled add-ins are allowed return null; + } } if (dbLockCheck) @@ -376,13 +379,15 @@ namespace Mono.Addins.Database } if (foundDomain != null) { sinfo = new Addin (this, foundDomain, id); - cachedAddinSetupInfos [idd] = sinfo; - if (!enabledOnly || sinfo.Enabled) - return sinfo; - if (exactVersionMatch) { - // Cache lookups with negative result - cachedAddinSetupInfos [idd] = this; - return null; + lock (cachedAddinSetupInfos) { + cachedAddinSetupInfos [idd] = sinfo; + if (!enabledOnly || sinfo.Enabled) + return sinfo; + if (exactVersionMatch) { + // Cache lookups with negative result + cachedAddinSetupInfos [idd] = null; + return null; + } } } } @@ -404,15 +409,20 @@ namespace Mono.Addins.Database } } if (sinfo != null) { - cachedAddinSetupInfos [idd] = sinfo; + lock (cachedAddinSetupInfos) { + cachedAddinSetupInfos [idd] = sinfo; + } return sinfo; } } - + // Cache lookups with negative result // Ignore the 'not installed' flag when disabled add-ins are allowed - if (enabledOnly) - cachedAddinSetupInfos [idd] = this; + if (enabledOnly) { + lock (cachedAddinSetupInfos) { + cachedAddinSetupInfos [idd] = null; + } + } return null; } } @@ -426,20 +436,22 @@ namespace Mono.Addins.Database { InternalCheck (domain); Addin ainfo = null; - - object ob = cachedAddinSetupInfos [assemblyLocation]; - if (ob != null) - return ob as Addin; // Don't use a cast here is ob may not be an Addin. - AddinHostIndex index = GetAddinHostIndex (); + lock (cachedAddinSetupInfos) { + if (cachedAddinSetupInfos.TryGetValue (assemblyLocation, out var ob)) + return ob; // this can be null, if the add-in is disabled + } + + var index = GetAddinHostIndex (); string addin, addinFile, rdomain; if (index.GetAddinForAssembly (assemblyLocation, out addin, out addinFile, out rdomain)) { string sid = addin + " " + rdomain; - ainfo = cachedAddinSetupInfos [sid] as Addin; - if (ainfo == null) - ainfo = new Addin (this, rdomain, addin); - cachedAddinSetupInfos [assemblyLocation] = ainfo; - cachedAddinSetupInfos [addin + " " + rdomain] = ainfo; + lock (cachedAddinSetupInfos) { + if (!cachedAddinSetupInfos.TryGetValue(sid, out ainfo)) + ainfo = new Addin (this, rdomain, addin); + cachedAddinSetupInfos [assemblyLocation] = ainfo; + cachedAddinSetupInfos [sid] = ainfo; + } } return ainfo; @@ -469,8 +481,14 @@ namespace Mono.Addins.Database { EnableAddin (domain, id, true); } - - internal void EnableAddin (string domain, string id, bool exactVersionMatch) + + public void EnableAddin (string domain, string id, bool exactVersionMatch) + { + using var transaction = BeginTransaction (); + EnableAddin (transaction, domain, id, exactVersionMatch); + } + + void EnableAddin (AddinDatabaseTransaction dbTransaction, string domain, string id, bool exactVersionMatch) { Addin ainfo = GetInstalledAddin (domain, id, exactVersionMatch, false); if (ainfo == null) @@ -486,21 +504,27 @@ namespace Mono.Addins.Database if (dep is AddinDependency) { AddinDependency adep = dep as AddinDependency; string adepid = Addin.GetFullId (ainfo.AddinInfo.Namespace, adep.AddinId, adep.Version); - EnableAddin (domain, adepid, false); + EnableAddin (dbTransaction, domain, adepid, false); } } - Configuration.SetEnabled (id, true, ainfo.AddinInfo.EnabledByDefault, true); - SaveConfiguration (); + Configuration.SetEnabled (dbTransaction, id, true, ainfo.AddinInfo.EnabledByDefault, true); + SaveConfiguration (dbTransaction); if (addinEngine != null && addinEngine.IsInitialized) { using var transaction = addinEngine.BeginTransaction (); addinEngine.ActivateAddin (transaction, id); } } - + public void DisableAddin (string domain, string id, bool exactVersionMatch = false, bool onlyForCurrentSession = false) { + using var transaction = BeginTransaction (); + DisableAddin (transaction, domain, id, exactVersionMatch, onlyForCurrentSession); + } + + void DisableAddin (AddinDatabaseTransaction dbTransaction, string domain, string id, bool exactVersionMatch = false, bool onlyForCurrentSession = false) + { Addin ai = GetInstalledAddin (domain, id, true); if (ai == null) throw new InvalidOperationException ("Add-in '" + id + "' not installed."); @@ -508,8 +532,8 @@ namespace Mono.Addins.Database if (!IsAddinEnabled (domain, id, exactVersionMatch)) return; - Configuration.SetEnabled (id, false, ai.AddinInfo.EnabledByDefault, exactVersionMatch, onlyForCurrentSession); - SaveConfiguration (); + Configuration.SetEnabled (dbTransaction, id, false, ai.AddinInfo.EnabledByDefault, exactVersionMatch, onlyForCurrentSession); + SaveConfiguration (dbTransaction); // Disable all add-ins which depend on it @@ -533,7 +557,7 @@ namespace Mono.Addins.Database Addin adepinfo = GetInstalledAddin (domain, adepid, false, true); if (adepinfo == null) { - DisableAddin (domain, ainfo.Id, onlyForCurrentSession: onlyForCurrentSession); + DisableAddin (dbTransaction, domain, ainfo.Id, onlyForCurrentSession: onlyForCurrentSession); break; } } @@ -541,8 +565,8 @@ namespace Mono.Addins.Database } catch { // If something goes wrong, enable the add-in again - Configuration.SetEnabled (id, true, ai.AddinInfo.EnabledByDefault, false, onlyForCurrentSession); - SaveConfiguration (); + Configuration.SetEnabled (dbTransaction, id, true, ai.AddinInfo.EnabledByDefault, false, onlyForCurrentSession); + SaveConfiguration (dbTransaction); throw; } @@ -552,16 +576,16 @@ namespace Mono.Addins.Database } } - public void UpdateEnabledStatus () + void UpdateEnabledStatus (AddinDatabaseTransaction transaction) { // Ensure that all enabled addins that have dependencies also have their dependencies enabled. HashSet<Addin> updatedAddins = new HashSet<Addin> (); var allAddins = GetInstalledAddins (registry.CurrentDomain, AddinSearchFlagsInternal.IncludeAddins | AddinSearchFlagsInternal.LatestVersionsOnly).ToList (); foreach (Addin addin in allAddins) - UpdateEnabledStatus (registry.CurrentDomain, addin, allAddins, updatedAddins); + UpdateEnabledStatus (transaction, registry.CurrentDomain, addin, allAddins, updatedAddins); } - void UpdateEnabledStatus (string domain, Addin addin, List<Addin> allAddins, HashSet<Addin> updatedAddins) + void UpdateEnabledStatus (AddinDatabaseTransaction transaction, string domain, Addin addin, List<Addin> allAddins, HashSet<Addin> updatedAddins) { if (!updatedAddins.Add (addin)) return; @@ -579,12 +603,12 @@ namespace Mono.Addins.Database string adepid = Addin.GetFullId (addin.AddinInfo.Namespace, adep.AddinId, null); var dependency = allAddins.FirstOrDefault (a => Addin.GetFullId (a.Namespace, a.LocalId, null) == adepid); if (dependency != null) { - UpdateEnabledStatus (domain, dependency, allAddins, updatedAddins); + UpdateEnabledStatus (transaction, domain, dependency, allAddins, updatedAddins); if (!dependency.Enabled) { // One of the dependencies is disabled, so this add-in also needs to be disabled. // However, we disabled only for the current configuration, we don't want to change // what the user configured. - DisableAddin (domain, addin.Id, onlyForCurrentSession: true); + DisableAddin (transaction, domain, addin.Id, onlyForCurrentSession: true); return; } } @@ -593,9 +617,10 @@ namespace Mono.Addins.Database public void RegisterForUninstall (string domain, string id, IEnumerable<string> files) { - DisableAddin (domain, id, true); - Configuration.RegisterForUninstall (id, files); - SaveConfiguration (); + using var transaction = BeginTransaction (); + DisableAddin (transaction, domain, id, true); + Configuration.RegisterForUninstall (transaction, id, files); + SaveConfiguration (transaction); } public bool IsRegisteredForUninstall (string domain, string addinId) @@ -634,7 +659,8 @@ namespace Mono.Addins.Database AddinUpdateData updateData = new AddinUpdateData (this, monitor); // Clear cached data - cachedAddinSetupInfos.Clear (); + lock(cachedAddinSetupInfos) + cachedAddinSetupInfos.Clear (); // Collect all information @@ -707,7 +733,7 @@ namespace Mono.Addins.Database } // If the original file does not exist, the description can be deleted - if (!fs.FileExists (conf.AddinFile)) { + if (!fileSystemExtension.FileExists (conf.AddinFile)) { SafeDelete (monitor, file); continue; } @@ -1016,16 +1042,16 @@ namespace Mono.Addins.Database internal void ResetBasicCachedData () { - allSetupInfos = null; - addinSetupInfos = null; - rootSetupInfos = null; + lock(localLock) + allSetupInfosLoaded = false; } internal void ResetCachedData () { ResetBasicCachedData (); hostIndex = null; - cachedAddinSetupInfos.Clear (); + lock(cachedAddinSetupInfos) + cachedAddinSetupInfos.Clear (); dependsOnCache.Clear (); if (addinEngine != null) addinEngine.ResetCachedData (); @@ -1104,8 +1130,10 @@ namespace Mono.Addins.Database fatalDatabseError = false; DateTime tim = DateTime.Now; - - RunPendingUninstalls (monitor); + + using (var dbTransaction = BeginTransaction ()) { + RunPendingUninstalls (dbTransaction, monitor); + } Hashtable installed = new Hashtable (); bool changesFound = CheckFolders (monitor, domain); @@ -1163,10 +1191,11 @@ namespace Mono.Addins.Database } } } - UpdateEnabledStatus (); + using var dbTransation = BeginTransaction (); + UpdateEnabledStatus (dbTransation); } - void RunPendingUninstalls (IProgressStatus monitor) + void RunPendingUninstalls (AddinDatabaseTransaction dbTransaction, IProgressStatus monitor) { bool changesDone = false; @@ -1204,12 +1233,12 @@ namespace Mono.Addins.Database } if (canUninstall) { - Configuration.UnregisterForUninstall (adn.AddinId); + Configuration.UnregisterForUninstall (dbTransaction, adn.AddinId); changesDone = true; } } if (changesDone) - SaveConfiguration (); + SaveConfiguration (dbTransaction); } void RunScannerProcess (IProgressStatus monitor, ScanOptions context) @@ -1220,8 +1249,8 @@ namespace Mono.Addins.Database IProgressStatus scanMonitor = monitor; context = context ?? new ScanOptions (); - if (fs.GetType () != typeof (AddinFileSystemExtension)) - context.FileSystemExtension = fs; + if (fileSystemExtension.GetType () != typeof (AddinFileSystemExtension)) + context.FileSystemExtension = fileSystemExtension; bool retry = false; do { @@ -1356,10 +1385,10 @@ namespace Mono.Addins.Database void InternalScanFolders (IProgressStatus monitor, AddinScanResult scanResult) { try { - fs.ScanStarted (); + fileSystemExtension.ScanStarted (); InternalScanFolders2 (monitor, scanResult); } finally { - fs.ScanFinished (); + fileSystemExtension.ScanFinished (); } } @@ -1372,7 +1401,7 @@ namespace Mono.Addins.Database return; try { - scanResult.HostIndex = GetAddinHostIndex (); + scanResult.HostIndex = new AddinHostIndex(GetAddinHostIndex ()); } catch (Exception ex) { if (scanResult.CheckOnly) { @@ -1391,7 +1420,7 @@ namespace Mono.Addins.Database AddinScanFolderInfo folderInfo; bool res = ReadFolderInfo (monitor, file, out folderInfo); bool validForDomain = scanResult.Domain == null || folderInfo.Domain == GlobalDomain || folderInfo.Domain == scanResult.Domain; - if (!res || (validForDomain && !fs.DirectoryExists (folderInfo.Folder))) { + if (!res || (validForDomain && !fileSystemExtension.DirectoryExists (folderInfo.Folder))) { if (res) { // Folder has been deleted. Remove the add-ins it had. updater.UpdateDeletedAddins (monitor, folderInfo); @@ -1450,7 +1479,7 @@ namespace Mono.Addins.Database if (monitor.LogLevel > 1) monitor.Log ("Folders scan completed (" + (int) (DateTime.Now - tim).TotalMilliseconds + " ms)"); - SaveAddinHostIndex (); + SaveAddinHostIndex (scanResult); ResetCachedData (); if (!scanResult.ChangesFound) { @@ -1478,7 +1507,9 @@ namespace Mono.Addins.Database if (monitor.LogLevel > 1) monitor.Log ("Add-in relations analyzed (" + (int) (DateTime.Now - tim).TotalMilliseconds + " ms)"); - SaveAddinHostIndex (); + SaveAddinHostIndex (scanResult); + + hostIndex = scanResult.HostIndex.ToImmutableAddinHostIndex (); } public void ParseAddin (IProgressStatus progressStatus, string domain, string file, string outFile, bool inProcess) @@ -1774,25 +1805,25 @@ namespace Mono.Addins.Database return false; } } - - AddinHostIndex GetAddinHostIndex () + + ImmutableAddinHostIndex GetAddinHostIndex () { if (hostIndex != null) return hostIndex; using (fileDatabase.LockRead ()) { if (fileDatabase.Exists (HostIndexFile)) - hostIndex = AddinHostIndex.Read (fileDatabase, HostIndexFile); + hostIndex = AddinHostIndex.ReadAsImmutable (fileDatabase, HostIndexFile); else - hostIndex = new AddinHostIndex (); + hostIndex = new ImmutableAddinHostIndex (); } return hostIndex; } - void SaveAddinHostIndex () + void SaveAddinHostIndex (AddinScanResult scanResult) { - if (hostIndex != null) - hostIndex.Write (fileDatabase, HostIndexFile); + if (scanResult.HostIndex != null) + scanResult.HostIndex.Write (fileDatabase, HostIndexFile); } internal string GetUniqueAddinId (string file, string oldId, string ns, string version) @@ -1850,18 +1881,20 @@ namespace Mono.Addins.Database DatabaseConfiguration Configuration { get { if (config == null) { - using (fileDatabase.LockRead ()) { - if (fileDatabase.Exists (ConfigFile)) - config = DatabaseConfiguration.Read (ConfigFile); - else - config = DatabaseConfiguration.ReadAppConfig (); + lock (localLock) { + using (fileDatabase.LockRead ()) { + if (fileDatabase.Exists (ConfigFile)) + config = DatabaseConfiguration.Read (ConfigFile); + else + config = DatabaseConfiguration.ReadAppConfig (); + } } } return config; } } - void SaveConfiguration () + void SaveConfiguration (AddinDatabaseTransaction dbTransaction) { if (config != null) { using (fileDatabase.LockWrite ()) { @@ -2002,9 +2035,16 @@ namespace Mono.Addins.Database list.Add (desc); } } - - // Keep in sync with AddinSearchFlags - [Flags] + + class AddinDatabaseTransaction : IDisposable + { + public void Dispose () + { + } + } + + // Keep in sync with AddinSearchFlags + [Flags] enum AddinSearchFlagsInternal { IncludeAddins = 1, diff --git a/Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs b/Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs index 7919497..713384a 100644 --- a/Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs +++ b/Mono.Addins/Mono.Addins.Database/AddinHostIndex.cs @@ -31,26 +31,46 @@ using System; using System.Collections; using Mono.Addins.Serialization; using System.IO; +using System.Collections.Generic; namespace Mono.Addins.Database { class AddinHostIndex: IBinaryXmlElement { static BinaryXmlTypeMap typeMap = new BinaryXmlTypeMap (typeof(AddinHostIndex)); - - Hashtable index = new Hashtable (); - + + Dictionary<string, string> index; + + public AddinHostIndex () + { + index = new Dictionary<string, string> (); + } + + public AddinHostIndex (ImmutableAddinHostIndex immutableIndex) + { + this.index = immutableIndex.ToDictionary(); + } + + public ImmutableAddinHostIndex ToImmutableAddinHostIndex () + { + return new ImmutableAddinHostIndex (new Dictionary<string, string> (index)); + } + public void RegisterAssembly (string assemblyLocation, string addinId, string addinLocation, string domain) { assemblyLocation = NormalizeFileName (assemblyLocation); index [Path.GetFullPath (assemblyLocation)] = addinId + " " + addinLocation + " " + domain; } - + public bool GetAddinForAssembly (string assemblyLocation, out string addinId, out string addinLocation, out string domain) { + return LookupAddinForAssembly (index, assemblyLocation, out addinId, out addinLocation, out domain); + } + + internal static bool LookupAddinForAssembly (Dictionary<string, string> index, string assemblyLocation, out string addinId, out string addinLocation, out string domain) + { assemblyLocation = NormalizeFileName (assemblyLocation); - string s = index [Path.GetFullPath (assemblyLocation)] as string; - if (s == null) { + if (!index.TryGetValue(Path.GetFullPath (assemblyLocation), out var s)) { addinId = null; addinLocation = null; domain = null; @@ -70,7 +90,7 @@ namespace Mono.Addins.Database { string loc = addinId + " " + Path.GetFullPath (addinLocation) + " "; ArrayList todelete = new ArrayList (); - foreach (DictionaryEntry e in index) { + foreach (var e in index) { if (((string)e.Value).StartsWith (loc)) todelete.Add (e.Key); } @@ -82,7 +102,13 @@ namespace Mono.Addins.Database { return (AddinHostIndex) fileDatabase.ReadObject (file, typeMap); } - + + public static ImmutableAddinHostIndex ReadAsImmutable (FileDatabase fileDatabase, string file) + { + var hostIndex = (AddinHostIndex)fileDatabase.ReadObject (file, typeMap); + return new ImmutableAddinHostIndex (hostIndex.index); + } + public void Write (FileDatabase fileDatabase, string file) { fileDatabase.WriteObject (file, this, typeMap); @@ -98,7 +124,7 @@ namespace Mono.Addins.Database reader.ReadValue ("index", index); } - string NormalizeFileName (string name) + internal static string NormalizeFileName (string name) { if (Util.IsWindows) return name.ToLower (); @@ -106,4 +132,28 @@ namespace Mono.Addins.Database return name; } } + + class ImmutableAddinHostIndex + { + Dictionary<string, string> index; + + public ImmutableAddinHostIndex (): this(new Dictionary<string, string>()) + { + } + + public ImmutableAddinHostIndex (Dictionary<string, string> index) + { + this.index = index; + } + + public bool GetAddinForAssembly (string assemblyLocation, out string addinId, out string addinLocation, out string domain) + { + return AddinHostIndex.LookupAddinForAssembly (index, assemblyLocation, out addinId, out addinLocation, out domain); + } + + public Dictionary<string, string> ToDictionary () + { + return new Dictionary<string, string> (index); + } + } } diff --git a/Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs b/Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs index 14d8840..9c73d25 100644 --- a/Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs +++ b/Mono.Addins/Mono.Addins.Database/DatabaseConfiguration.cs @@ -33,25 +33,61 @@ using System.IO; using System.Collections; using System.Collections.Generic; using System.Xml; +using System.Collections.Immutable; namespace Mono.Addins.Database { internal class DatabaseConfiguration { - Dictionary<string,AddinStatus> addinStatus = new Dictionary<string,AddinStatus> (); + ImmutableDictionary<string, AddinStatus> addinStatus = ImmutableDictionary<string, AddinStatus>.Empty; internal class AddinStatus { - public AddinStatus (string addinId) + public AddinStatus (string addinId, bool configEnabled = false, bool? sessionEnabled = null, bool uninstalled = false, ImmutableArray<string> files = default) { this.AddinId = addinId; + ConfigEnabled = configEnabled; + SessionEnabled = sessionEnabled; + Uninstalled = uninstalled; + Files = files; } - public string AddinId; - public bool ConfigEnabled; - public bool? SessionEnabled; - public bool Uninstalled; - public List<string> Files; + AddinStatus Copy () + { + return new AddinStatus (AddinId, + configEnabled: ConfigEnabled, + sessionEnabled: SessionEnabled, + uninstalled: Uninstalled, + files: Files + ); + } + + public AddinStatus AsEnabled (bool enabled, bool sessionOnly) + { + var copy = Copy (); + if (sessionOnly) + copy.SessionEnabled = true; + else { + copy.ConfigEnabled = true; + copy.SessionEnabled = null; + } + return copy; + } + + public AddinStatus AsUninstalled () + { + var copy = Copy (); + copy.ConfigEnabled = false; + copy.Uninstalled = true; + return copy; + } + + public string AddinId { get; private set;} + public bool ConfigEnabled { get; private set; } + public bool? SessionEnabled { get; private set; } + public bool Uninstalled { get; private set; } + public ImmutableArray<string> Files { get; private set; } + public bool Enabled { get { return SessionEnabled ?? ConfigEnabled; } } @@ -63,56 +99,51 @@ namespace Mono.Addins.Database AddinStatus s; + var status = addinStatus; + // If the add-in is globaly disabled, it is disabled no matter what the version specific status is - if (addinStatus.TryGetValue (addinName, out s)) { + if (status.TryGetValue (addinName, out s)) { if (!s.Enabled) return false; } - if (addinStatus.TryGetValue (addinId, out s)) - return s.Enabled && !IsRegisteredForUninstall (addinId); + if (status.TryGetValue (addinId, out s)) + return s.Enabled && !s.Uninstalled; else return defaultValue; } - public void SetEnabled (string addinId, bool enabled, bool defaultValue, bool exactVersionMatch, bool onlyForTheSession = false) + public void SetEnabled (AddinDatabaseTransaction transaction, string addinId, bool enabled, bool defaultValue, bool exactVersionMatch, bool onlyForTheSession = false) { if (IsRegisteredForUninstall (addinId)) return; var addinName = exactVersionMatch ? addinId : Addin.GetIdName (addinId); - AddinStatus s; - addinStatus.TryGetValue (addinName, out s); - - if (s == null) - s = addinStatus [addinName] = new AddinStatus (addinName); - if (onlyForTheSession) - s.SessionEnabled = enabled; - else { - s.ConfigEnabled = enabled; - s.SessionEnabled = null; - } + if (!addinStatus.TryGetValue (addinName, out var s)) + s = new AddinStatus (addinName); + + s = s.AsEnabled (enabled, onlyForTheSession); + addinStatus = addinStatus.SetItem (addinName, s); // If enabling a specific version of an add-in, make sure the add-in is enabled as a whole if (enabled && exactVersionMatch) - SetEnabled (addinId, true, defaultValue, false, onlyForTheSession); + SetEnabled (transaction, addinId, true, defaultValue, false, onlyForTheSession); } - public void RegisterForUninstall (string addinId, IEnumerable<string> files) + public void RegisterForUninstall (AddinDatabaseTransaction transaction, string addinId, IEnumerable<string> files) { AddinStatus s; if (!addinStatus.TryGetValue (addinId, out s)) - s = addinStatus [addinId] = new AddinStatus (addinId); + s = new AddinStatus (addinId); - s.ConfigEnabled = false; - s.Uninstalled = true; - s.Files = new List<string> (files); + s = s.AsUninstalled (); + addinStatus = addinStatus.SetItem (addinId, s); } - - public void UnregisterForUninstall (string addinId) + + public void UnregisterForUninstall (AddinDatabaseTransaction transaction, string addinId) { - addinStatus.Remove (addinId); + addinStatus = addinStatus.Remove (addinId); } public bool IsRegisteredForUninstall (string addinId) @@ -143,8 +174,7 @@ namespace Mono.Addins.Database return config; // Overwrite app config values with user config values - foreach (var entry in config.addinStatus) - appConfig.addinStatus [entry.Key] = entry.Value; + appConfig.addinStatus = appConfig.addinStatus.SetItems (config.addinStatus); return appConfig; } @@ -170,25 +200,29 @@ namespace Mono.Addins.Database XmlElement disabledElem = (XmlElement) doc.DocumentElement.SelectSingleNode ("DisabledAddins"); if (disabledElem != null) { // For back compatibility - foreach (XmlElement elem in disabledElem.SelectNodes ("Addin")) - config.SetEnabled (elem.InnerText, false, true, false); + var dictionary = ImmutableDictionary.CreateBuilder<string, AddinStatus> (); + foreach (XmlElement elem in disabledElem.SelectNodes ("Addin")) { + AddinStatus status = new AddinStatus (Addin.GetIdName (elem.GetAttribute ("id")), configEnabled: false); + dictionary [status.AddinId] = status; + } + config.addinStatus = dictionary.ToImmutable (); return config; } - + XmlElement statusElem = (XmlElement) doc.DocumentElement.SelectSingleNode ("AddinStatus"); if (statusElem != null) { + var dictionary = ImmutableDictionary.CreateBuilder<string, AddinStatus> (); foreach (XmlElement elem in statusElem.SelectNodes ("Addin")) { - AddinStatus status = new AddinStatus (elem.GetAttribute ("id")); string senabled = elem.GetAttribute ("enabled"); - status.ConfigEnabled = senabled.Length == 0 || senabled == "True"; - status.Uninstalled = elem.GetAttribute ("uninstalled") == "True"; - config.addinStatus [status.AddinId] = status; - foreach (XmlElement fileElem in elem.SelectNodes ("File")) { - if (status.Files == null) - status.Files = new List<string> (); - status.Files.Add (fileElem.InnerText); - } + + AddinStatus status = new AddinStatus (elem.GetAttribute ("id"), + configEnabled: senabled.Length == 0 || senabled == "True", + uninstalled: elem.GetAttribute ("uninstalled") == "True", + files: elem.SelectNodes ("File").OfType<XmlElement> ().Select (fileElem => fileElem.InnerText).ToImmutableArray () + ); + dictionary [status.AddinId] = status; } + config.addinStatus = dictionary.ToImmutable (); } return config; } @@ -208,7 +242,7 @@ namespace Mono.Addins.Database tw.WriteAttributeString ("enabled", e.ConfigEnabled.ToString ()); if (e.Uninstalled) tw.WriteAttributeString ("uninstalled", "True"); - if (e.Files != null && e.Files.Count > 0) { + if (e.Files.Length > 0) { foreach (var f in e.Files) tw.WriteElementString ("File", f); } |