diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/DebugOptions.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/DebugOptions.cs index d7f5e54..25bfafb 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/DebugOptions.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/DebugOptions.cs @@ -18,7 +18,9 @@ using Acacia.Utils; using Microsoft.Win32; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Text; +using System.Linq; namespace Acacia { @@ -33,7 +35,18 @@ namespace Acacia this.Token = token; } - abstract public string GetToken(ValueType value); + public string Key + { + get + { + string key = Token; + if (key.StartsWith("-") || key.StartsWith("+")) + key = key.Substring(1); + return key.ToLower(); + } + } + + abstract public string GetToken(ValueType value, bool needExplicit); abstract public ValueType GetValue(string value); } @@ -43,17 +56,31 @@ namespace Acacia public BoolOption(string token, bool inverse) : - base(inverse ? "-" + token : (token.Length == 0 ? "+" : token)) + base(inverse ? ("-" + token) : (token.Length == 0 ? "+" : token)) { this.Inverse = inverse; } - public override string GetToken(bool value) + public override string GetToken(bool value, bool needExplicit) { if (Inverse) value = !value; if (value) return Token; + else if (needExplicit) + { + string inverse; + if (Token.StartsWith("-")) + { + if (Token.Length == 1) + inverse = "+"; + else + inverse = Token.Substring(1); + } + else + inverse = "-" + Token; + return inverse; + } else return null; } @@ -89,9 +116,9 @@ namespace Acacia this._defaultValue = defaultValue; } - public override string GetToken(EnumType value) + public override string GetToken(EnumType value, bool needExplicit) { - if (value.Equals(DefaultValue)) + if (!needExplicit && value.Equals(DefaultValue)) return null; return Token + "=" + value.ToString(); } @@ -121,9 +148,9 @@ namespace Acacia this._defaultValue = defaultValue; } - public override string GetToken(TimeSpan value) + public override string GetToken(TimeSpan value, bool needExplicit) { - if (value.Equals(_defaultValue)) + if (!needExplicit && value.Equals(_defaultValue)) return null; return Token + "=" + value.ToString(); } @@ -153,9 +180,9 @@ namespace Acacia this._defaultValue = defaultValue; } - public override string GetToken(string value) + public override string GetToken(string value, bool needExplicit) { - if (value.Equals(_defaultValue)) + if (!needExplicit && value.Equals(_defaultValue)) return null; return Token + "=" + value.ToString(); } @@ -185,9 +212,9 @@ namespace Acacia this._defaultValue = defaultValue; } - public override string GetToken(int value) + public override string GetToken(int value, bool needExplicit) { - if (value.Equals(_defaultValue)) + if (!needExplicit && value.Equals(_defaultValue)) return null; return Token + "=" + value.ToString(); } @@ -239,57 +266,127 @@ namespace Acacia #region Access methods - public static string GetOptions(string prefix) + private class Token { - if (ReturnDefaults) - return null; + public string Key; + public string Value; + public bool HasCurrentUser; + public bool HasLocalMachine; + public string LocalMachineToken; + public int Order; - return RegistryUtil.GetConfigValue(prefix, null, null); - } - - public static ValueType GetOption(string prefix, Option option) - { - // Parse the options - Dictionary tokens = ParseTokens(prefix); - string value; - tokens.TryGetValue(option.Token.ToLower(), out value); - return option.GetValue(value); - } - - private static Dictionary ParseTokens(string prefix) - { - Dictionary tokens = new Dictionary(); - string value = GetOptions(prefix); - if (!string.IsNullOrEmpty(value)) + public override string ToString() { - foreach (string token in value.Split(',')) + return string.Format("key={0}, value={1}, HKCU={2}, HLKM={3}, HLKMToken={4}", Key, Value, HasCurrentUser, HasLocalMachine, LocalMachineToken); + } + } + + private static Dictionary GetEffectiveTokens(string prefix) + { + Dictionary tokens = new Dictionary(); + + foreach (bool localMachine in new bool[] {true, false}) + { + string value = RegistryUtil.GetConfigValue(localMachine, prefix, null); + + if (!string.IsNullOrEmpty(value)) { - if (!string.IsNullOrEmpty(token)) + foreach (string token in value.Split(',')) { - string[] keyVal = token.Split(new[] { '=' }, 2); - if (!string.IsNullOrEmpty(keyVal[0])) + if (!string.IsNullOrEmpty(token)) { - tokens[keyVal[0].ToLower()] = token; + string[] keyVal = token.Split(new[] { '=' }, 2); + if (!string.IsNullOrEmpty(keyVal[0])) + { + string key = keyVal[0].ToLower(); + if (key.StartsWith("-") || key.StartsWith("+")) + key = key.Substring(1); + + Token existing; + if (tokens.TryGetValue(key, out existing)) + { + existing.Value = token; + } + else + { + existing = new Token() { Key = key, Value = token }; + existing.Order = tokens.Count; + tokens.Add(key, existing); + } + if (localMachine) + { + existing.HasLocalMachine = true; + existing.LocalMachineToken = token; + } + else + { + existing.HasCurrentUser = true; + } + } } } } } + return tokens; } + public static string GetTokens(string prefix) + { + // Use GetEffectiveTokens to get the effective string in case there are duplicates in local and user + Dictionary tokens = GetEffectiveTokens(prefix); + return String.Join(",", tokens.Values.OrderBy(t => t.Order).Select(t => t.Value)); + } + + public static ValueType GetOption(string prefix, Option option) + { + // Get all options + Dictionary tokens = GetEffectiveTokens(prefix); + + // And get the effective value + Token value; + tokens.TryGetValue(option.Key, out value); + ValueType result = option.GetValue(value?.Value); + return result; + } + public static void SetOption(string prefix, Option option, ValueType value) { - Dictionary tokens = ParseTokens(prefix); + Dictionary tokens = GetEffectiveTokens(prefix); + + // If the token is currently defined in HKLM, we need an explicit override. Otherwise, leave it empty for default value + string key = option.Key; + Token existing; + tokens.TryGetValue(key, out existing); + bool needExplicit = existing?.HasLocalMachine == true; // Update the token - string token = option.GetToken(value); - if (token != null) - tokens[option.Token.ToLower()] = token; - else - tokens.Remove(option.Token.ToLower()); + string token = option.GetToken(value, needExplicit); - // Write to registry - string newValue = string.Join(",", tokens.Values); + // If the new value matches the value set in HKLM, remove it + if (token != null && existing?.LocalMachineToken?.Equals(token) == true) + { + tokens.Remove(key); + } + else if (token != null) + { + // Set or add the token + if (tokens.ContainsKey(key)) + { + tokens[key].Value = token; + tokens[key].HasCurrentUser = true; + } + else + tokens.Add(key, new Token() { Key = key, Value = token, HasCurrentUser = true }); + } + else + { + // Remove the token + tokens.Remove(key); + } + + // Write to registry, skipping ones only defined in HKLM + string newValue = string.Join(",", tokens.Values.Where(t => t.HasCurrentUser).Select(t => t.Value)); RegistryUtil.SetConfigValue(prefix, null, newValue, RegistryValueKind.String); } diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Feature.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Feature.cs index 2de436f..c42bafb 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Feature.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Features/Feature.cs @@ -71,7 +71,7 @@ namespace Acacia.Features public static string GetDebugTokens(Type featureType) { - return DebugOptions.GetOptions(GetFeatureName(featureType)); + return DebugOptions.GetTokens(GetFeatureName(featureType)); } public static bool IsEnabled(Type featureType) diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs index 3278353..9484799 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/ThisAddIn.cs @@ -95,7 +95,7 @@ namespace Acacia Logger.Instance.Info(this, "Starting version {0}: {1} @ {2}. Outlook version: {3}. Options: '{4}'", LibUtils.Version, BuildVersions.REVISION, LibUtils.BuildTime, Application.Version, - DebugOptions.GetOptions(null)); + DebugOptions.GetTokens(null)); Logger.Instance.Initialize(); // Check if we're enabled @@ -200,7 +200,7 @@ namespace Acacia } else { - Logger.Instance.Trace(this, "OutlookUI is disabled: '{0}'", DebugOptions.GetOptions(null)); + Logger.Instance.Trace(this, "OutlookUI is disabled: '{0}'", DebugOptions.GetTokens(null)); } } return _outlookUI; diff --git a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/RegistryUtil.cs b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/RegistryUtil.cs index 49aec5a..7f47f29 100644 --- a/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/RegistryUtil.cs +++ b/src/AcaciaZPushPlugin/AcaciaZPushPlugin/Utils/RegistryUtil.cs @@ -94,19 +94,33 @@ namespace Acacia.Utils public static ValueType GetConfigValue(string path, string valueName, ValueType defaultValue) { // Try current user first - foreach (bool localMachine in new bool[]{ false, true}) + foreach (bool localMachine in new bool[] { false, true }) { - using (RegistryKey key = OpenKeyImpl(Constants.PLUGIN_REGISTRY_BASE, path, localMachine, RegistryKeyPermissionCheck.ReadSubTree)) + ValueType value = GetConfigValue(localMachine, path, valueName); + if (value != null) + return value; + } + + return defaultValue; + } + + public static ValueType GetConfigValue(bool localMachine, string path, string valueName) + { + using (RegistryKey key = OpenKeyImpl(Constants.PLUGIN_REGISTRY_BASE, path, localMachine, RegistryKeyPermissionCheck.ReadSubTree)) + { + if (key != null) { - if (key != null) + object value = key.GetValue(valueName); + if (value != null) { - object value = key.GetValue(valueName); - if (value != null) - return (ValueType)value; + // Treat an empty string like a missing value. Otherwise the default value used for options is always present in HKCU + if (typeof(ValueType) == typeof(string) && string.IsNullOrWhiteSpace((string)value)) + return default(ValueType); + return (ValueType)value; } } } - return defaultValue; + return default(ValueType); } public static void SetConfigValue(string path, string valueName, object value, RegistryValueKind kind) diff --git a/src/AcaciaZPushPlugin/PluginDebugger/OptionsInfra.cs b/src/AcaciaZPushPlugin/PluginDebugger/OptionsInfra.cs index 566b2ff..7f2aa25 100644 --- a/src/AcaciaZPushPlugin/PluginDebugger/OptionsInfra.cs +++ b/src/AcaciaZPushPlugin/PluginDebugger/OptionsInfra.cs @@ -300,7 +300,7 @@ namespace PluginDebugger { // Show full registry string Feature feature = value as Feature; - return DebugOptions.GetOptions(feature?.Name); + return DebugOptions.GetTokens(feature?.Name); } return base.ConvertTo(context, culture, value, destinationType); }