#region Apache License 2.0
/*
Nuclex .NET Framework
Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#endregion // Apache License 2.0
#if WINDOWS
using System;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Win32;
namespace Nuclex.Support.Settings {
/// Stores settings in the registry on Windows operating systems
///
///
/// For the cases when you must use the Windows registry, the windows registry store
/// lets you map a registry key as a settings store. Its direct subkeys will become
/// categories and all registry values are made available as options.
///
///
/// Use of the registry is strongly discouraged. It binds you to Microsoft's silly
/// technology stack and fragments your application by storing some of its data in
/// the file system while storing other data in an opaque, globally-shared settings
/// manager that is filled with megabytes on unrelated things. Xcopy deployment
/// and portability will be out of the question when relying on the registry.
///
///
/// Instead of using this, consider querying for the platform's appropriate location
/// to store settings in.
///
///
public class WindowsRegistryStore : ISettingsStore, IDisposable {
/// Initializes a new settings store on the specified registry path
/// Hive in which to look
/// Base path of the settings in the specified hive
/// Whether to open the registry in writable mode
public WindowsRegistryStore(RegistryHive hive, string directory, bool writable = true) {
using(RegistryKey hiveKey = RegistryKey.OpenBaseKey(hive, RegistryView.Default)) {
this.rootKey = hiveKey.OpenSubKey(directory, writable);
}
this.writable = writable;
}
/// Initializes a new settings store on the specified registry key
/// Registry key the settings are stored under
/// Whether the registry was opened in writable mode
///
/// This constructor takes ownership of the registry key. It will be disposed when
/// the settings store is disposed.
///
public WindowsRegistryStore(RegistryKey rootKey, bool writable = true) {
this.rootKey = rootKey;
this.writable = writable;
}
/// Immediately releases all resources owned by the instance
public void Dispose() {
if(this.rootKey != null) {
this.rootKey.Dispose();
this.rootKey = null;
}
}
/// Enumerates the categories defined in the configuration
/// An enumerable list of all used categories
public IEnumerable EnumerateCategories() {
return this.rootKey.GetSubKeyNames();
}
/// Enumerates the options stored under the specified category
/// Category whose options will be enumerated
/// An enumerable list of all options in the category
public IEnumerable EnumerateOptions(string category = null) {
if(string.IsNullOrEmpty(category)) {
string[] valueNames = this.rootKey.GetValueNames();
for(int index = 0; index < valueNames.Length; ++index) {
yield return new OptionInfo() {
Name = valueNames[index],
OptionType = getBestMatchingType(this.rootKey, valueNames[index])
};
}
} else {
using(RegistryKey categoryKey = this.rootKey.OpenSubKey(category, this.writable)) {
if(categoryKey == null) {
yield break;
}
string[] valueNames = categoryKey.GetValueNames();
for(int index = 0; index < valueNames.Length; ++index) {
yield return new OptionInfo() {
Name = valueNames[index],
OptionType = getBestMatchingType(categoryKey, valueNames[index])
};
}
}
}
}
/// Retrieves the value of the specified option
/// Type the option will be converted to
/// Category the option can be found in. Can be null.
/// Name of the option that will be looked up
/// The value of the option with the specified name
public TValue Get(string category, string optionName) {
TValue value;
if(TryGet(category, optionName, out value)) {
return value;
} else {
if(string.IsNullOrEmpty(category)) {
throw new KeyNotFoundException(
"There is no option named '" + optionName + "' in the registry"
);
} else {
throw new KeyNotFoundException(
"There is no option named '" + optionName + "' under the category '" +
category + "' in the registry"
);
}
}
}
/// Tries to retrieve the value of the specified option
/// Type the option will be converted to
/// Category the option can be found in. Can be null.
/// Name of the option that will be looked up
/// Will receive the value of the option, if found
///
/// True if the option existed and its value was written into the
/// parameter, false otherwise
///
public bool TryGet(string category, string optionName, out TValue value) {
if(string.IsNullOrEmpty(category)) {
return tryGetValueFromKey(this.rootKey, optionName, out value);
} else {
RegistryKey categoryKey = this.rootKey.OpenSubKey(category, this.writable);
if(categoryKey == null) {
value = default(TValue);
return false;
}
using(categoryKey) {
return tryGetValueFromKey(categoryKey, optionName, out value);
}
}
}
/// Saves an option in the settings store
/// Type of value that will be saved
/// Category the option will be placed in. Can be null.
/// Name of the option that will be saved
/// The value under which the option will be saved
public void Set(string category, string optionName, TValue value) {
if(string.IsNullOrEmpty(category)) {
setValue(this.rootKey, optionName, value);
} else {
RegistryKey categoryKey = this.rootKey.OpenSubKey(category, this.writable);
if(categoryKey == null) {
categoryKey = this.rootKey.CreateSubKey(category);
}
using(categoryKey) {
setValue(categoryKey, optionName, value);
}
}
}
/// Removes the option with the specified name
/// Category the option is found in. Can be null.
/// Name of the option that will be removed
/// True if the option was found and removed
public bool Remove(string category, string optionName) {
if(string.IsNullOrEmpty(category)) {
object value = this.rootKey.GetValue(optionName);
this.rootKey.DeleteValue(optionName, throwOnMissingValue: false);
return (value != null);
} else {
RegistryKey categoryKey = this.rootKey.OpenSubKey(category, this.writable);
if(categoryKey == null) {
return false;
}
using(categoryKey) {
object value = categoryKey.GetValue(optionName);
categoryKey.DeleteValue(optionName, throwOnMissingValue: false);
return (value != null);
}
}
}
/// Writes a setting to the registry
///
///
///
///
private void setValue(RegistryKey registryKey, string optionName, TValue value) {
if(typeof(TValue) == typeof(int)) {
registryKey.SetValue(optionName, value, RegistryValueKind.DWord);
} else if(typeof(TValue) == typeof(long)) {
registryKey.SetValue(optionName, value, RegistryValueKind.QWord);
} else if(typeof(TValue) == typeof(bool)) {
registryKey.SetValue(optionName, value, RegistryValueKind.DWord);
} else if(typeof(TValue) == typeof(string)) {
registryKey.SetValue(optionName, value, RegistryValueKind.String);
} else if(typeof(TValue) == typeof(string[])) {
registryKey.SetValue(optionName, value, RegistryValueKind.MultiString);
} else if(typeof(TValue) == typeof(byte[])) {
registryKey.SetValue(optionName, value, RegistryValueKind.Binary);
} else {
string valueAsString = (string)Convert.ChangeType(
value, typeof(string), CultureInfo.InvariantCulture
);
registryKey.SetValue(optionName, valueAsString, RegistryValueKind.String);
}
}
/// Tries to retrieve the value of a registry key if it exists
/// Type of value the registry key is expected to have
/// Registry key the value is stored under
/// Name of the option in the registry
/// Will receive the value read from the registry
/// True if the value was found, false otherwise
private bool tryGetValueFromKey(
RegistryKey categoryKey, string optionName, out TValue value
) {
object valueAsObject = categoryKey.GetValue(optionName);
if(valueAsObject == null) {
value = default(TValue);
return false;
} else {
value = (TValue)Convert.ChangeType(
valueAsObject, typeof(TValue), CultureInfo.InvariantCulture
);
return true;
}
}
/// Figures out which .NET type best matches the registry value
/// Registry key the key is stored in
/// Name of the option that will be retrieved
/// The best matching .NET type for the registry key's value
private static Type getBestMatchingType(RegistryKey categoryKey, string optionName) {
RegistryValueKind valueKind = categoryKey.GetValueKind(optionName);
switch(valueKind) {
case RegistryValueKind.Binary: { return typeof(byte[]); }
case RegistryValueKind.DWord: { return typeof(int); }
case RegistryValueKind.QWord: { return typeof(long); }
case RegistryValueKind.MultiString: { return typeof(string[]); }
case RegistryValueKind.ExpandString:
case RegistryValueKind.String:
case RegistryValueKind.Unknown:
case RegistryValueKind.None:
default: { return typeof(string); }
}
}
/// Key on which the registry store is operating
private RegistryKey rootKey;
/// Whether the user can write to the registry key
private bool writable;
}
} // namespace Nuclex.Support.Settings
#endif // WINDOWS