diff --git a/bin/netcore/pyRevitLabs.Emojis.dll b/bin/netcore/pyRevitLabs.Emojis.dll index ca3af7d6d..d1b6fd4a0 100644 Binary files a/bin/netcore/pyRevitLabs.Emojis.dll and b/bin/netcore/pyRevitLabs.Emojis.dll differ diff --git a/bin/netfx/pyRevitLabs.Emojis.dll b/bin/netfx/pyRevitLabs.Emojis.dll index 3cf64cf04..095e2b6f6 100644 Binary files a/bin/netfx/pyRevitLabs.Emojis.dll and b/bin/netfx/pyRevitLabs.Emojis.dll differ diff --git a/dev/Directory.Build.targets b/dev/Directory.Build.targets index e7226e7b4..9bbdad2c0 100644 --- a/dev/Directory.Build.targets +++ b/dev/Directory.Build.targets @@ -2,7 +2,7 @@ netfx - netcore + netcore diff --git a/dev/pyRevitLabs/pyRevitCLI/PyRevitCLI.cs b/dev/pyRevitLabs/pyRevitCLI/PyRevitCLI.cs index cb690cfcd..a35e916da 100644 --- a/dev/pyRevitLabs/pyRevitCLI/PyRevitCLI.cs +++ b/dev/pyRevitLabs/pyRevitCLI/PyRevitCLI.cs @@ -6,11 +6,13 @@ using DocoptNet; using pyRevitCLI.Properties; using pyRevitLabs.Common; +using pyRevitLabs.Configurations; using pyRevitLabs.NLog; using pyRevitLabs.NLog.Config; using pyRevitLabs.NLog.Targets; using pyRevitLabs.PyRevit; using Console = Colorful.Console; +using Environment = System.Environment; // NOTE: @@ -455,12 +457,15 @@ private static void ProcessArguments() { } else if (any("enable", "disable")) + { + string revitVersion = TryGetValue("") ?? ConfigurationService.DefaultConfigurationName; PyRevitCLIExtensionCmds.ToggleExtension( + revitVersion: revitVersion, enable: arguments["enable"].IsTrue, cloneName: TryGetValue(""), extName: TryGetValue("") - ); - + ); + } else if (all("sources")) { if (IsHelpMode) PyRevitCLIAppHelps.PrintHelp(PyRevitCLICommandType.ExtensionsSources); @@ -614,12 +619,14 @@ private static void ProcessArguments() { } else if (all("configs")) { + string revitVersion = TryGetValue("") ?? ConfigurationService.DefaultConfigurationName; + if (IsHelpMode) PyRevitCLIAppHelps.PrintHelp(PyRevitCLICommandType.Configs); else if (all("bincache")) { if (any("enable", "disable")) - PyRevitConfigs.SetBinaryCaches(arguments["enable"].IsTrue); + PyRevitConfigs.SetBinaryCaches(arguments["enable"].IsTrue, revitVersion); else Console.WriteLine(string.Format("Binary cache is {0}", PyRevitConfigs.GetBinaryCaches() ? "Enabled" : "Disabled")); @@ -627,7 +634,7 @@ private static void ProcessArguments() { else if (all("checkupdates")) { if (any("enable", "disable")) - PyRevitConfigs.SetCheckUpdates(arguments["enable"].IsTrue); + PyRevitConfigs.SetCheckUpdates(arguments["enable"].IsTrue, revitVersion); else Console.WriteLine(string.Format("Check Updates is {0}", PyRevitConfigs.GetCheckUpdates() ? "Enabled" : "Disabled")); @@ -635,7 +642,7 @@ private static void ProcessArguments() { else if (all("autoupdate")) { if (any("enable", "disable")) - PyRevitConfigs.SetAutoUpdate(arguments["enable"].IsTrue); + PyRevitConfigs.SetAutoUpdate(arguments["enable"].IsTrue, revitVersion); else Console.WriteLine(string.Format("Auto Update is {0}", PyRevitConfigs.GetAutoUpdate() ? "Enabled" : "Disabled")); @@ -643,7 +650,7 @@ private static void ProcessArguments() { else if (all("rocketmode")) { if (any("enable", "disable")) - PyRevitConfigs.SetRocketMode(arguments["enable"].IsTrue); + PyRevitConfigs.SetRocketMode(arguments["enable"].IsTrue, revitVersion); else Console.WriteLine(string.Format("Rocket Mode is {0}", PyRevitConfigs.GetRocketMode() ? "Enabled" : "Disabled")); @@ -651,13 +658,13 @@ private static void ProcessArguments() { else if (all("logs")) { if (all("none")) - PyRevitConfigs.SetLoggingLevel(PyRevitLogLevels.Quiet); + PyRevitConfigs.SetLoggingLevel(PyRevitLogLevels.Quiet, revitVersion); else if (all("verbose")) - PyRevitConfigs.SetLoggingLevel(PyRevitLogLevels.Verbose); + PyRevitConfigs.SetLoggingLevel(PyRevitLogLevels.Verbose, revitVersion); else if (all("debug")) - PyRevitConfigs.SetLoggingLevel(PyRevitLogLevels.Debug); + PyRevitConfigs.SetLoggingLevel(PyRevitLogLevels.Debug, revitVersion); else Console.WriteLine(string.Format("Logging Level is {0}", PyRevitConfigs.GetLoggingLevel().ToString())); @@ -665,7 +672,7 @@ private static void ProcessArguments() { else if (all("filelogging")) { if (any("enable", "disable")) - PyRevitConfigs.SetFileLogging(arguments["enable"].IsTrue); + PyRevitConfigs.SetFileLogging(arguments["enable"].IsTrue, revitVersion); else Console.WriteLine(string.Format("File Logging is {0}", PyRevitConfigs.GetFileLogging() ? "Enabled" : "Disabled")); @@ -676,12 +683,12 @@ private static void ProcessArguments() { Console.WriteLine(string.Format("Startup log timeout is set to: {0}", PyRevitConfigs.GetStartupLogTimeout())); else - PyRevitConfigs.SetStartupLogTimeout(int.Parse(TryGetValue(""))); + PyRevitConfigs.SetStartupLogTimeout(int.Parse(TryGetValue("")), revitVersion); } else if (all("loadbeta")) { if (any("enable", "disable")) - PyRevitConfigs.SetLoadBetaTools(arguments["enable"].IsTrue); + PyRevitConfigs.SetLoadBetaTools(arguments["enable"].IsTrue, revitVersion); else Console.WriteLine(string.Format("Load Beta is {0}", PyRevitConfigs.GetLoadBetaTools() ? "Enabled" : "Disabled")); @@ -692,12 +699,12 @@ private static void ProcessArguments() { Console.WriteLine(string.Format("CPython version is set to: {0}", PyRevitConfigs.GetCpythonEngineVersion())); else - PyRevitConfigs.SetCpythonEngineVersion(int.Parse(TryGetValue(""))); + PyRevitConfigs.SetCpythonEngineVersion(int.Parse(TryGetValue("")), revitVersion); } else if (all("usercanupdate")) { if (any("yes", "no")) - PyRevitConfigs.SetUserCanUpdate(arguments["yes"].IsTrue); + PyRevitConfigs.SetUserCanUpdate(arguments["yes"].IsTrue, revitVersion); else Console.WriteLine(string.Format("User {0} update", PyRevitConfigs.GetUserCanUpdate() ? "CAN" : "CAN NOT")); @@ -705,7 +712,7 @@ private static void ProcessArguments() { else if (all("usercanextend")) { if (any("yes", "no")) - PyRevitConfigs.SetUserCanExtend(arguments["yes"].IsTrue); + PyRevitConfigs.SetUserCanExtend(arguments["yes"].IsTrue, revitVersion); else Console.WriteLine(string.Format("User {0} extend", PyRevitConfigs.GetUserCanExtend() ? "CAN" : "CAN NOT")); @@ -713,7 +720,7 @@ private static void ProcessArguments() { else if (all("usercanconfig")) { if (any("yes", "no")) - PyRevitConfigs.SetUserCanConfig(arguments["yes"].IsTrue); + PyRevitConfigs.SetUserCanConfig(arguments["yes"].IsTrue, revitVersion); else Console.WriteLine(string.Format("User {0} config", PyRevitConfigs.GetUserCanConfig() ? "CAN" : "CAN NOT")); @@ -722,7 +729,7 @@ private static void ProcessArguments() { else if (all("colordocs")) { if (any("enable", "disable")) - PyRevitConfigs.SetColorizeDocs(arguments["enable"].IsTrue); + PyRevitConfigs.SetColorizeDocs(arguments["enable"].IsTrue, revitVersion); else Console.WriteLine(string.Format("Doc Colorizer is {0}", PyRevitConfigs.GetColorizeDocs() ? "Enabled" : "Disabled")); @@ -730,7 +737,7 @@ private static void ProcessArguments() { else if (all("tooltipdebuginfo")) { if (any("enable", "disable")) - PyRevitConfigs.SetAppendTooltipEx(arguments["enable"].IsTrue); + PyRevitConfigs.SetAppendTooltipEx(arguments["enable"].IsTrue, revitVersion); else Console.WriteLine(string.Format("Doc Colorizer is {0}", PyRevitConfigs.GetAppendTooltipEx() ? "Enabled" : "Disabled")); @@ -743,24 +750,24 @@ private static void ProcessArguments() { Console.WriteLine(string.Format("Routes Port: {0}", PyRevitConfigs.GetRoutesServerPort())); } else - PyRevitConfigs.SetRoutesServerPort(int.Parse(portNumber)); + PyRevitConfigs.SetRoutesServerPort(int.Parse(portNumber), revitVersion); } else if (all("coreapi")) { if (all("enable")) - PyRevitConfigs.SetRoutesLoadCoreAPIStatus(true); + PyRevitConfigs.SetRoutesLoadCoreAPIStatus(true, revitVersion); else if (all("disable")) - PyRevitConfigs.SetRoutesLoadCoreAPIStatus(false); + PyRevitConfigs.SetRoutesLoadCoreAPIStatus(false, revitVersion); else Console.WriteLine(string.Format("Routes Core API is {0}", PyRevitConfigs.GetRoutesLoadCoreAPIStatus() ? "Enabled" : "Disabled")); } else if (all("enable")) - PyRevitConfigs.EnableRoutesServer(); + PyRevitConfigs.EnableRoutesServer(revitVersion); else if (all("disable")) - PyRevitConfigs.DisableRoutesServer(); + PyRevitConfigs.DisableRoutesServer(revitVersion); else { Console.WriteLine(string.Format("Routes Server is {0}", @@ -771,7 +778,7 @@ private static void ProcessArguments() { else if (all("telemetry")) { if (all("utc")) { if (any("yes", "no")) - PyRevitConfigs.SetUTCStamps(arguments["yes"].IsTrue); + PyRevitConfigs.SetUTCStamps(arguments["yes"].IsTrue, revitVersion); else Console.WriteLine(PyRevitConfigs.GetUTCStamps() ? "Using UTC timestamps" : "Using Local timestamps"); } @@ -781,7 +788,7 @@ private static void ProcessArguments() { if (destPath is null) Console.WriteLine(string.Format("Telemetry File Path: {0}", PyRevitConfigs.GetAppTelemetryFlags())); else - PyRevitConfigs.EnableTelemetry(telemetryFileDir: destPath); + PyRevitConfigs.EnableTelemetry(telemetryFileDir: destPath, revitVersion: revitVersion); } else if (all("server")) { @@ -789,22 +796,22 @@ private static void ProcessArguments() { if (serverUrl is null) Console.WriteLine(string.Format("Telemetry Server Url: {0}", PyRevitConfigs.GetAppTelemetryFlags())); else - PyRevitConfigs.EnableTelemetry(telemetryServerUrl: serverUrl); + PyRevitConfigs.EnableTelemetry(telemetryServerUrl: serverUrl, revitVersion: revitVersion); } else if (all("hooks")) { if (any("yes", "no")) - PyRevitConfigs.SetTelemetryIncludeHooks(arguments["yes"].IsTrue); + PyRevitConfigs.SetTelemetryIncludeHooks(arguments["yes"].IsTrue, revitVersion); else Console.WriteLine(PyRevitConfigs.GetTelemetryIncludeHooks() ? "Sending telemetry for hooks" : "Not sending telemetry for hooks"); } else if (all("enable")) - PyRevitConfigs.EnableTelemetry(); + PyRevitConfigs.EnableTelemetry(revitVersion: revitVersion); else if (all("disable")) - PyRevitConfigs.DisableTelemetry(); + PyRevitConfigs.DisableTelemetry(revitVersion: revitVersion); else { Console.WriteLine(string.Format("Telemetry is {0}", @@ -820,7 +827,7 @@ private static void ProcessArguments() { if (flagsValue is null) Console.WriteLine(string.Format("App Telemetry Flags: {0}", PyRevitConfigs.GetAppTelemetryFlags())); else - PyRevitConfigs.SetAppTelemetryFlags(flags: flagsValue); + PyRevitConfigs.SetAppTelemetryFlags(flags: flagsValue, revitVersion: revitVersion); } else if (all("server")) { @@ -828,15 +835,15 @@ private static void ProcessArguments() { if (serverPath is null) Console.WriteLine(string.Format("App Telemetry Server: {0}", PyRevitConfigs.GetAppTelemetryServerUrl())); else - PyRevitConfigs.EnableAppTelemetry(apptelemetryServerUrl: serverPath); + PyRevitConfigs.EnableAppTelemetry(apptelemetryServerUrl: serverPath, revitVersion: revitVersion); } else if (all("enable")) - PyRevitConfigs.EnableAppTelemetry(); + PyRevitConfigs.EnableAppTelemetry(revitVersion); else if (all("disable")) - PyRevitConfigs.DisableAppTelemetry(); + PyRevitConfigs.DisableAppTelemetry(revitVersion); else { Console.WriteLine(string.Format("App Telemetry is {0}", @@ -852,7 +859,7 @@ private static void ProcessArguments() { Console.WriteLine(string.Format("Output Style Sheet is set to: {0}", PyRevitConfigs.GetOutputStyleSheet())); else - PyRevitConfigs.SetOutputStyleSheet(TryGetValue("")); + PyRevitConfigs.SetOutputStyleSheet(TryGetValue(""), revitVersion); } else if (all("seed")) @@ -862,12 +869,14 @@ private static void ProcessArguments() { if (arguments[""] != null) { // extract section and option names string orignalOptionValue = TryGetValue(""); - if (orignalOptionValue.Split(':').Count() == 2) { + if (orignalOptionValue.Split(':').Count() == 2) + { string configSection = orignalOptionValue.Split(':')[0]; string configOption = orignalOptionValue.Split(':')[1]; - var cfg = PyRevitConfigs.GetConfigFile(); - cfg.SetValue(configSection, configOption, arguments["enable"].IsTrue); + var cfg = PyRevitConfigs.GetConfigFile(revitVersion); + cfg.SetSectionKeyValue( + revitVersion, configSection, configOption, arguments["enable"].IsTrue); } else PyRevitCLIAppHelps.PrintHelp(PyRevitCLICommandType.Main); @@ -882,18 +891,19 @@ private static void ProcessArguments() { string configSection = orignalOptionValue.Split(':')[0]; string configOption = orignalOptionValue.Split(':')[1]; - var cfg = PyRevitConfigs.GetConfigFile(); + var cfg = PyRevitConfigs.GetConfigFile(revitVersion); // if no value provided, read the value var optValue = TryGetValue(""); - if (optValue != null) - cfg.SetValue(configSection, configOption, optValue); - else if (optValue is null) { - var existingVal = cfg.GetValue(configSection, configOption); - if (existingVal != null) - Console.WriteLine( string.Format("{0} = {1}", configOption, existingVal)); + if (optValue is not null) + cfg.SetSectionKeyValue(revitVersion, configSection, configOption, optValue); + else + { + var existingVal = cfg.GetSectionKeyValueOrDefault(revitVersion, configSection, configOption); + if (!string.IsNullOrEmpty(existingVal)) + Console.WriteLine($"{configOption} = {existingVal}"); else - Console.WriteLine(string.Format("Configuration key \"{0}\" is not set", configOption)); + Console.WriteLine($"Configuration key \"{configOption}\" is not set"); } } else diff --git a/dev/pyRevitLabs/pyRevitCLI/PyRevitCLIExtensionCmds.cs b/dev/pyRevitLabs/pyRevitCLI/PyRevitCLIExtensionCmds.cs index 61cc5bb96..f0cc3de57 100644 --- a/dev/pyRevitLabs/pyRevitCLI/PyRevitCLIExtensionCmds.cs +++ b/dev/pyRevitLabs/pyRevitCLI/PyRevitCLIExtensionCmds.cs @@ -171,7 +171,7 @@ internal static void } internal static void - ToggleExtension(bool enable, string cloneName, string extName) { + ToggleExtension(string revitVersion, bool enable, string cloneName, string extName) { if (extName != null) { PyRevitClone clone = null; if (cloneName != null) @@ -179,15 +179,15 @@ internal static void if (enable) { if (clone != null) - PyRevitExtensions.EnableShippedExtension(clone, extName); + PyRevitExtensions.EnableShippedExtension(revitVersion, clone, extName); else - PyRevitExtensions.EnableInstalledExtension(extName); + PyRevitExtensions.EnableInstalledExtension(revitVersion, extName); } else { if (clone != null) - PyRevitExtensions.DisableShippedExtension(clone, extName); + PyRevitExtensions.DisableShippedExtension(revitVersion, clone, extName); else - PyRevitExtensions.DisableInstalledExtension(extName); + PyRevitExtensions.DisableInstalledExtension(revitVersion, extName); } } } diff --git a/dev/pyRevitLabs/pyRevitCLI/Resources/UsagePatterns.txt b/dev/pyRevitLabs/pyRevitCLI/Resources/UsagePatterns.txt index 651714fd7..a93b0aaaf 100644 --- a/dev/pyRevitLabs/pyRevitCLI/Resources/UsagePatterns.txt +++ b/dev/pyRevitLabs/pyRevitCLI/Resources/UsagePatterns.txt @@ -74,36 +74,36 @@ Usage: pyrevit config (-h | --help) pyrevit config --from= [--log=] pyrevit configs (-h | --help) - pyrevit configs bincache [(enable | disable)] [--log=] - pyrevit configs checkupdates [(enable | disable)] [--log=] - pyrevit configs autoupdate [(enable | disable)] [--log=] - pyrevit configs rocketmode [(enable | disable)] [--log=] - pyrevit configs logs [(none | verbose | debug)] [--log=] - pyrevit configs filelogging [(enable | disable)] [--log=] - pyrevit configs startuptimeout [] [--log=] - pyrevit configs loadbeta [(enable | disable)] [--log=] - pyrevit configs cpyversion [] [--log=] - pyrevit configs usercanupdate [(yes | no)] [--log=] - pyrevit configs usercanextend [(yes | no)] [--log=] - pyrevit configs usercanconfig [(yes | no)] [--log=] - pyrevit configs colordocs [(enable | disable)] [--log=] - pyrevit configs tooltipdebuginfo [(enable | disable)] [--log=] + pyrevit configs bincache [(enable | disable)] [] [--log=] + pyrevit configs checkupdates [(enable | disable)] [] [--log=] + pyrevit configs autoupdate [(enable | disable)] [] [--log=] + pyrevit configs rocketmode [(enable | disable)] [] [--log=] + pyrevit configs logs [(none | verbose | debug)] [] [--log=] + pyrevit configs filelogging [(enable | disable)] [] [--log=] + pyrevit configs startuptimeout [] [] [--log=] + pyrevit configs loadbeta [(enable | disable)] [] [--log=] + pyrevit configs cpyversion [] [] [--log=] + pyrevit configs usercanupdate [(yes | no)] [] [--log=] + pyrevit configs usercanextend [(yes | no)] [] [--log=] + pyrevit configs usercanconfig [(yes | no)] [] [--log=] + pyrevit configs colordocs [(enable | disable)] [] [--log=] + pyrevit configs tooltipdebuginfo [(enable | disable)] [] [--log=] pyrevit configs routes [(-h | --help)] - pyrevit configs routes [(enable | disable)] [--log=] - pyrevit configs routes port [] [--log=] - pyrevit configs routes coreapi [(enable | disable)] [--log=] + pyrevit configs routes [(enable | disable)] [] [--log=] + pyrevit configs routes port [] [] [--log=] + pyrevit configs routes coreapi [(enable | disable)] [] [--log=] pyrevit configs telemetry [(-h | --help)] - pyrevit configs telemetry [(enable | disable)] [--log=] - pyrevit configs telemetry utc [(yes | no)] [--log=] - pyrevit configs telemetry (file | server) [] [--log=] - pyrevit configs telemetry hooks [(yes | no)] [--log=] - pyrevit configs apptelemetry [(enable | disable)] [--log=] - pyrevit configs apptelemetry flags [] [--log=] - pyrevit configs apptelemetry server [] [--log=] - pyrevit configs outputcss [] [--log=] + pyrevit configs telemetry [(enable | disable)] [] [--log=] + pyrevit configs telemetry utc [(yes | no)] [] [--log=] + pyrevit configs telemetry (file | server) [] [] [--log=] + pyrevit configs telemetry hooks [(yes | no)] [] [--log=] + pyrevit configs apptelemetry [(enable | disable)] [] [--log=] + pyrevit configs apptelemetry flags [] [] [--log=] + pyrevit configs apptelemetry server [] [] [--log=] + pyrevit configs outputcss [] [] [--log=] pyrevit configs seed [--lock] [--log=] - pyrevit configs [(enable | disable)] [--log=] - pyrevit configs [] [--log=] + pyrevit configs [(enable | disable)] [] [--log=] + pyrevit configs [] [] [--log=] pyrevit doctor (-h | --help) pyrevit doctor [--list] pyrevit doctor [--dryrun] diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/pyRevitLabs.Common.csproj b/dev/pyRevitLabs/pyRevitLabs.Common/pyRevitLabs.Common.csproj index 1002cf1e1..cd8027a64 100644 --- a/dev/pyRevitLabs/pyRevitLabs.Common/pyRevitLabs.Common.csproj +++ b/dev/pyRevitLabs/pyRevitLabs.Common/pyRevitLabs.Common.csproj @@ -17,9 +17,6 @@ - - - @@ -36,7 +33,6 @@ - diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/Extensions/IniConfigurationExtensions.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/Extensions/IniConfigurationExtensions.cs new file mode 100644 index 000000000..064073c22 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/Extensions/IniConfigurationExtensions.cs @@ -0,0 +1,19 @@ +namespace pyRevitLabs.Configurations.Ini.Extensions; + +public static class IniConfigurationExtensions +{ + public static ConfigurationBuilder AddIniConfiguration( + this ConfigurationBuilder builder, string configurationPath, string conigurationName, bool readOnly = default) + { + if (builder == null) + throw new ArgumentNullException(nameof(builder)); + + if (string.IsNullOrWhiteSpace(configurationPath)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(configurationPath)); + + if (string.IsNullOrWhiteSpace(conigurationName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(conigurationName)); + + return builder.AddConfigurationSource(conigurationName, IniConfiguration.Create(configurationPath, readOnly)); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/IniConfiguration.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/IniConfiguration.cs new file mode 100644 index 000000000..c513ebcf0 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/IniConfiguration.cs @@ -0,0 +1,116 @@ +using System.Text; +using IniParser; +using IniParser.Model; +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Exceptions; +using pyRevitLabs.Json; + +namespace pyRevitLabs.Configurations.Ini; + +public sealed class IniConfiguration : ConfigurationBase +{ + public static readonly string DefaultFileExtension = ".ini"; + public static readonly Encoding DefaultFileEncoding = new UTF8Encoding(false); + + private readonly IniData _iniFile; + private readonly FileIniDataParser _parser; + + /// + /// Create ini configuration instance. + /// + /// Configuration path. + /// Admin configurations + private IniConfiguration(string configurationPath, bool readOnly) + : base(configurationPath, readOnly) + { + _parser = new FileIniDataParser(); + _iniFile = !File.Exists(configurationPath) + ? new IniData() + : _parser.ReadFile(_configurationPath, DefaultFileEncoding); + } + + /// + /// Creates IniConfiguration. + /// + /// Configuration file path. + /// Mark file is readonly. + /// Return new IniConfiguration. + /// When configurationPath is null. + public static IConfiguration Create(string configurationPath, bool readOnly = default) + { + if (configurationPath is null) + throw new ArgumentNullException(nameof(configurationPath)); + + return new IniConfiguration(configurationPath, readOnly); + } + + /// + protected override void SaveConfigurationImpl() + { + SaveConfigurationImpl(_configurationPath); + } + + /// + protected override void SaveConfigurationImpl(string configurationPath) + { + _parser.WriteFile(configurationPath, _iniFile, DefaultFileEncoding); + } + + /// + protected override bool HasSectionImpl(string sectionName) + { + return _iniFile.Sections.ContainsSection(sectionName); + } + + /// + protected override bool HasSectionKeyImpl(string sectionName, string keyName) + { + return HasSection(sectionName) + && _iniFile.Sections[sectionName].ContainsKey(keyName); + } + + protected override IEnumerable GetSectionNamesImpl() + { + return _iniFile.Sections.Select(item => item.SectionName); + } + + protected override IEnumerable GetSectionOptionNamesImpl(string sectionName) + { + return _iniFile.Sections[sectionName].Select(item => item.KeyName); + } + + /// + protected override bool RemoveSectionImpl(string sectionName) + { + return _iniFile.Sections.RemoveSection(sectionName); + } + + /// + protected override bool RemoveOptionImpl(string sectionName, string keyName) + { + return _iniFile[sectionName].RemoveKey(keyName); + } + + /// + protected override void SetValueImpl(string sectionName, string keyName, T value) + { + if (!HasSection(sectionName)) + { + _iniFile.Sections.AddSection(sectionName); + } + + if (!HasSectionKey(sectionName, keyName)) + { + _iniFile[sectionName].AddKey(keyName); + } + + _iniFile[sectionName][keyName] = JsonConvert.SerializeObject(value); + } + + /// + protected override object GetValueImpl(Type typeObject, string sectionName, string keyName) + { + return JsonConvert.DeserializeObject(_iniFile[sectionName][keyName], typeObject) + ?? throw new ConfigurationException("Cannot deserialize value using the specified key."); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/pyRevitLabs.Configurations.Ini.csproj b/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/pyRevitLabs.Configurations.Ini.csproj new file mode 100644 index 000000000..baf5f7f01 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Ini/pyRevitLabs.Configurations.Ini.csproj @@ -0,0 +1,19 @@ + + + + net48;net8.0 + enable + enable + 12 + + + + + + + + + + + + diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/Extensions/JsonConfigurationExtensions.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/Extensions/JsonConfigurationExtensions.cs new file mode 100644 index 000000000..d189bc29b --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/Extensions/JsonConfigurationExtensions.cs @@ -0,0 +1,19 @@ +namespace pyRevitLabs.Configurations.Json.Extensions; + +public static class JsonConfigurationExtensions +{ + public static ConfigurationBuilder AddJsonConfiguration( + this ConfigurationBuilder builder, string configurationPath, string conigurationName, bool readOnly = default) + { + if (builder == null) + throw new ArgumentNullException(nameof(builder)); + + if (string.IsNullOrWhiteSpace(configurationPath)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(configurationPath)); + + if (string.IsNullOrWhiteSpace(conigurationName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(conigurationName)); + + return builder.AddConfigurationSource(conigurationName, JsonConfiguration.Create(configurationPath, readOnly)); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/JsonConfiguration.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/JsonConfiguration.cs new file mode 100644 index 000000000..92eb6e443 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/JsonConfiguration.cs @@ -0,0 +1,131 @@ +using System.Text; +using System.Xml; +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Exceptions; +using pyRevitLabs.Json; +using pyRevitLabs.Json.Linq; +using Formatting = pyRevitLabs.Json.Formatting; + +namespace pyRevitLabs.Configurations.Json; + +public sealed class JsonConfiguration : ConfigurationBase +{ + public static readonly string DefaultFileExtension = ".json"; + public static readonly Encoding DefaultFileEncoding = Encoding.UTF8; + + private readonly JObject _jsonObject; + + /// + /// Create json configuration instance. + /// + /// Configuration path. + /// Readonly configurations + private JsonConfiguration(string configurationPath, bool readOnly) + : base(configurationPath, readOnly) + { + _jsonObject = !File.Exists(configurationPath) + ? new JObject() + : JObject.Parse(File.ReadAllText(_configurationPath, DefaultFileEncoding)); + } + + /// + /// Creates JsonConfiguration. + /// + /// Configuration file path. + /// Mark file is readonly. + /// Return new JsonConfiguration. + /// When configurationPath is null. + public static IConfiguration Create(string configurationPath, bool readOnly = default) + { + if (configurationPath is null) + throw new ArgumentNullException(nameof(configurationPath)); + + return new JsonConfiguration(configurationPath, readOnly); + } + + /// + protected override void SaveConfigurationImpl() + { + SaveConfigurationImpl(_configurationPath); + } + + /// + protected override void SaveConfigurationImpl(string configurationPath) + { + string jsonString = JsonConvert.SerializeObject(_jsonObject, + new JsonSerializerSettings() {Formatting = Formatting.Indented}); + File.WriteAllText(configurationPath, jsonString, DefaultFileEncoding); + } + + /// + protected override bool HasSectionImpl(string sectionName) + { + return _jsonObject.ContainsKey(sectionName); + } + + /// + protected override bool HasSectionKeyImpl(string sectionName, string keyName) + { + JObject? sectionObject = _jsonObject[sectionName] as JObject; + return sectionObject?.ContainsKey(keyName) == true; + } + + protected override IEnumerable GetSectionNamesImpl() + { + return _jsonObject.Properties().Select(x => x.Name); + } + + protected override IEnumerable GetSectionOptionNamesImpl(string sectionName) + { + JObject? sectionObject = _jsonObject[sectionName] as JObject; + return sectionObject?.Properties().Select(x => x.Name) ?? Enumerable.Empty(); + } + + protected override bool RemoveSectionImpl(string sectionName) + { + return _jsonObject.Remove(sectionName); + } + + protected override bool RemoveOptionImpl(string sectionName, string keyName) + { + _jsonObject[sectionName]![keyName] = null; + return true; + } + + /// + protected override void SetValueImpl(string sectionName, string keyName, T value) + { + if (!HasSection(sectionName)) + { + JObject fromObject = new(); + fromObject.Add(keyName, JToken.FromObject(value!)); + + _jsonObject.Add(sectionName, fromObject); + + return; + } + + if (!HasSectionKey(sectionName, keyName)) + { + JObject fromObject = new(); + fromObject.Add(keyName, JToken.FromObject(value!)); + + JObject? sectionObject = (JObject?)_jsonObject[sectionName]; + sectionObject?.Add(keyName, fromObject); + + return; + } + + _jsonObject[sectionName]![keyName] = JToken.FromObject(value!); + } + + /// + protected override object GetValueImpl(Type typeObject, string sectionName, string keyName) + { + JToken? token = _jsonObject[sectionName]?[keyName]; + return token is null + ? throw new ConfigurationException($"Section {sectionName} or keyName {keyName} not found.") + : token.ToObject(typeObject) + ?? throw new ConfigurationException($"Cannot deserialize value with {sectionName} and {keyName}."); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/pyRevitLabs.Configurations.Json.csproj b/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/pyRevitLabs.Configurations.Json.csproj new file mode 100644 index 000000000..8e925e100 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Json/pyRevitLabs.Configurations.Json.csproj @@ -0,0 +1,18 @@ + + + + net48;net8.0 + enable + enable + 12 + + + + + + + + + + + diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/Extensions/YamlConfigurationExtensions.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/Extensions/YamlConfigurationExtensions.cs new file mode 100644 index 000000000..53a14221f --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/Extensions/YamlConfigurationExtensions.cs @@ -0,0 +1,19 @@ +namespace pyRevitLabs.Configurations.Yaml.Extensions; + +public static class YamlConfigurationExtensions +{ + public static ConfigurationBuilder AddYamlConfiguration( + this ConfigurationBuilder builder, string configurationPath, string conigurationName, bool readOnly = default) + { + if (builder == null) + throw new ArgumentNullException(nameof(builder)); + + if (string.IsNullOrWhiteSpace(configurationPath)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(configurationPath)); + + if (string.IsNullOrWhiteSpace(conigurationName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(conigurationName)); + + return builder.AddConfigurationSource(conigurationName, YamlConfiguration.Create(configurationPath, readOnly)); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/YamlConfiguration.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/YamlConfiguration.cs new file mode 100644 index 000000000..b6d416304 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/YamlConfiguration.cs @@ -0,0 +1,174 @@ +using System.Text; +using System.Collections.Generic; +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Exceptions; +using System.Collections; +using YamlDotNet.Core; +using YamlDotNet.RepresentationModel; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace pyRevitLabs.Configurations.Yaml; + +public sealed class YamlConfiguration : ConfigurationBase +{ + public static readonly string DefaultFileExtension = ".yml"; + public static readonly Encoding DefaultFileEncoding = Encoding.UTF8; + + private readonly YamlStream _yamlStream; + private readonly YamlMappingNode _rootNode; + + /// + /// Create yaml configuration instance. + /// + /// Configuration path. + /// Admin configurations + private YamlConfiguration(string configurationPath, bool readOnly) + : base(configurationPath, readOnly) + { + _yamlStream = []; + if (File.Exists(configurationPath)) + { + _yamlStream.Load(new StringReader(File.ReadAllText(_configurationPath, DefaultFileEncoding))); + } + + if (_yamlStream.Documents.Count == 0) + { + _yamlStream.Documents.Add(new YamlDocument(new YamlMappingNode())); + } + + _rootNode = (YamlMappingNode)_yamlStream.Documents[0].RootNode; + } + + /// + /// Creates YamlConfiguration. + /// + /// Configuration file path. + /// Mark file is readonly. + /// Return new YamlConfiguration. + /// When configurationPath is null. + public static IConfiguration Create(string configurationPath, bool readOnly = default) + { + if (configurationPath is null) + throw new ArgumentNullException(nameof(configurationPath)); + + return new YamlConfiguration(configurationPath, readOnly); + } + + /// + protected override void SaveConfigurationImpl() + { + SaveConfigurationImpl(_configurationPath); + } + + /// + protected override void SaveConfigurationImpl(string configurationPath) + { + ISerializer serializer = CreateSerializer(); + string yamlString = serializer.Serialize(_yamlStream); + File.WriteAllText(configurationPath, yamlString, DefaultFileEncoding); + } + + /// + protected override bool HasSectionImpl(string sectionName) + { + return _rootNode.Children.ContainsKey(sectionName) + && _rootNode.Children[sectionName].NodeType == YamlNodeType.Mapping; + } + + /// + protected override bool HasSectionKeyImpl(string sectionName, string keyName) + { + if (!HasSection(sectionName)) + { + return false; + } + + YamlMappingNode sectionNode = (YamlMappingNode)_rootNode.Children[sectionName]; + if (!sectionNode.Children.ContainsKey(keyName)) + { + return false; + } + + YamlNodeType? yamlNodeType = sectionNode.Children[keyName].NodeType; + return yamlNodeType is YamlNodeType.Scalar or YamlNodeType.Sequence or YamlNodeType.Mapping; + } + + protected override IEnumerable GetSectionNamesImpl() + { + return _rootNode.Children.Select(item => item.Key.ToString()); + } + + protected override IEnumerable GetSectionOptionNamesImpl(string sectionName) + { + YamlMappingNode? yamlNode = _rootNode.Children[sectionName] as YamlMappingNode; + return yamlNode?.Children.Select(item => item.Key.ToString()) ?? Enumerable.Empty(); + } + + /// + protected override bool RemoveSectionImpl(string sectionName) + { + return _rootNode.Children.Remove(sectionName); + } + + /// + protected override bool RemoveOptionImpl(string sectionName, string keyName) + { + YamlMappingNode yamlNode = (YamlMappingNode)_rootNode[sectionName]; + return yamlNode.Children.Remove(keyName); + } + + /// + protected override void SetValueImpl(string sectionName, string keyName, T value) + { + if (_yamlStream.Documents.Count == 0) + _yamlStream.Documents.Add(new YamlDocument(new YamlMappingNode())); + + if (value is string stringValue) + { + if (string.IsNullOrEmpty(stringValue)) + { + return; + } + } + + if (!HasSection(sectionName)) + { + _rootNode.Add(sectionName, + new YamlMappingNode( + new KeyValuePair(keyName, YamlMappingNode.FromObject(value!)))); + } + + if (!HasSectionKey(sectionName, keyName)) + { + YamlMappingNode sectionNode = (YamlMappingNode)_rootNode[sectionName]; + sectionNode.Add(keyName, YamlMappingNode.FromObject(value!)); + } + + RemoveOption(sectionName, keyName); + + YamlMappingNode sectionNode1 = (YamlMappingNode)_rootNode[sectionName]; + sectionNode1.Add(keyName, YamlMappingNode.FromObject(value!)); + } + + private static ISerializer CreateSerializer() + { + return new SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + } + + private static IDeserializer CreateDeserializer() + { + return new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + } + + protected override object GetValueImpl(Type typeObject, string sectionName, string keyName) + { + YamlNode yamlNode = _rootNode[sectionName][keyName]; + return CreateDeserializer().Deserialize(yamlNode.ToString(), typeObject) + ?? throw new ConfigurationException($"Cannot deserialize value with {sectionName} and {keyName}."); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/pyRevitLabs.Configurations.Yaml.csproj b/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/pyRevitLabs.Configurations.Yaml.csproj new file mode 100644 index 000000000..0cdc75615 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations.Yaml/pyRevitLabs.Configurations.Yaml.csproj @@ -0,0 +1,18 @@ + + + + net48;net8.0 + enable + enable + 12 + + + + + + + + + + + diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Abstractions/IConfiguration.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Abstractions/IConfiguration.cs new file mode 100644 index 000000000..82f58ff26 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Abstractions/IConfiguration.cs @@ -0,0 +1,26 @@ +namespace pyRevitLabs.Configurations.Abstractions; + +public interface IConfiguration +{ + string ConfigurationPath { get; } + + bool HasSection(string sectionName); + bool HasSectionKey(string sectionName, string keyName); + + IEnumerable GetSectionNames(); + IEnumerable GetSectionOptionNames(string sectionName); + + T GetValue(string sectionName, string keyName); + T? GetValueOrDefault(string sectionName, string keyName, T? defaultValue = default); + + internal object GetValue(Type typeObject, string sectionName, string keyName); + internal object? GetValueOrDefault(Type typeObject, string sectionName, string keyName, object? defaultValue = default); + + bool RemoveSection(string sectionName); + bool RemoveOption(string sectionName, string keyName); + + void SetValue(string sectionName, string keyName, T? value); + + void SaveConfiguration(); + void SaveConfiguration(string configurationPath); +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Abstractions/IConfigurationService.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Abstractions/IConfigurationService.cs new file mode 100644 index 000000000..c0cd9896f --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Abstractions/IConfigurationService.cs @@ -0,0 +1,23 @@ +namespace pyRevitLabs.Configurations.Abstractions; + +public interface IConfigurationService +{ + bool ReadOnly { get; } + + IEnumerable ConfigurationNames { get; } + IEnumerable Configurations { get; } + IConfiguration this[string configurationName] { get; } + + CoreSection Core { get; } + RoutesSection Routes { get; } + TelemetrySection Telemetry { get; } + EnvironmentSection Environment { get; } + + void ReloadLoadConfigurations(); + + T GetSection(); + void SaveSection(string configurationName, T sectionValue); + + void SetSectionKeyValue(string configurationName, string sectionName, string keyName, T keyValue); + T? GetSectionKeyValueOrDefault(string configurationName, string sectionName, string keyName, T? defaultValue = default); +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Attributes/KeyNameAttribute.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Attributes/KeyNameAttribute.cs new file mode 100644 index 000000000..7301808e8 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Attributes/KeyNameAttribute.cs @@ -0,0 +1,8 @@ +namespace pyRevitLabs.Configurations.Attributes; + +[AttributeUsage(AttributeTargets.Property)] +internal sealed class KeyNameAttribute(string keyName) : Attribute +{ + + public string KeyName { get; } = keyName; +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Attributes/SectionNameAttribute.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Attributes/SectionNameAttribute.cs new file mode 100644 index 000000000..a660705b1 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Attributes/SectionNameAttribute.cs @@ -0,0 +1,7 @@ +namespace pyRevitLabs.Configurations.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +internal sealed class SectionNameAttribute(string sectionName) : Attribute +{ + public string SectionName { get; } = sectionName; +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationBase.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationBase.cs new file mode 100644 index 000000000..e03de87ec --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationBase.cs @@ -0,0 +1,198 @@ +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Exceptions; + +namespace pyRevitLabs.Configurations; + +public abstract class ConfigurationBase : IConfiguration +{ + protected readonly string _configurationPath; + + protected ConfigurationBase(string configurationPath, bool readOnly) + { + _configurationPath = configurationPath; + ReadOnly = readOnly; + } + + public bool ReadOnly { get; } + public string ConfigurationPath => _configurationPath; + + public void SaveConfiguration() + { + if (ReadOnly) + { + return; + } + + SaveConfigurationImpl(); + } + + public void SaveConfiguration(string configurationPath) + { + if (configurationPath == null) + throw new ArgumentNullException(nameof(configurationPath)); + + SaveConfigurationImpl(configurationPath); + } + + /// + public bool HasSection(string sectionName) + { + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); + + return HasSectionImpl(sectionName); + } + + /// + public bool HasSectionKey(string sectionName, string keyName) + { + if (string.IsNullOrWhiteSpace(keyName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(keyName)); + + return HasSectionKeyImpl(sectionName, keyName); + } + + public IEnumerable GetSectionNames() + { + return GetSectionNamesImpl(); + } + + public IEnumerable GetSectionOptionNames(string sectionName) + { + if (!HasSection(sectionName)) + { + return Enumerable.Empty(); + } + + return GetSectionOptionNamesImpl(sectionName); + } + + public bool RemoveSection(string sectionName) + { + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); + + + bool result = HasSection(sectionName) + && RemoveSectionImpl(sectionName); + + return result; + } + + /// + public bool RemoveOption(string sectionName, string keyName) + { + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); + + if (string.IsNullOrWhiteSpace(keyName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(keyName)); + + bool result = HasSection(sectionName) + && HasSectionKey(sectionName, keyName) + && RemoveOptionImpl(sectionName, keyName); + + return result; + } + + /// + public T GetValue(string sectionName, string keyName) + { + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); + + if (string.IsNullOrWhiteSpace(keyName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(keyName)); + + if (!HasSection(sectionName)) + throw new ConfigurationSectionNotFoundException(sectionName); + + if (!HasSectionKey(sectionName, keyName)) + throw new ConfigurationSectionKeyNotFoundException(sectionName, keyName); + + return (T) GetValueImpl(typeof(T), sectionName, keyName); + } + + /// + public T? GetValueOrDefault(string sectionName, string keyName, T? defaultValue = default) + { + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); + + if (string.IsNullOrWhiteSpace(keyName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(keyName)); + + if (!HasSection(sectionName)) + return defaultValue; + + if (!HasSectionKey(sectionName, keyName)) + return defaultValue; + + return (T) GetValueImpl(typeof(T), sectionName, keyName); + } + + public object? GetValueOrDefault(Type typeObject, string sectionName, string keyName, object? defaultValue = default) + { + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); + + if (string.IsNullOrWhiteSpace(keyName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(keyName)); + + if (!HasSection(sectionName)) + return defaultValue; + + if (!HasSectionKey(sectionName, keyName)) + return defaultValue; + + return GetValueImpl(typeObject, sectionName, keyName); + } + + /// + public object GetValue(Type typeObject, string sectionName, string keyName) + { + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); + + if (string.IsNullOrWhiteSpace(keyName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(keyName)); + + if (!HasSection(sectionName)) + throw new ConfigurationSectionNotFoundException(sectionName); + + if (!HasSectionKey(sectionName, keyName)) + throw new ConfigurationSectionKeyNotFoundException(sectionName, keyName); + + return GetValueImpl(typeObject, sectionName, keyName); + } + + /// + public void SetValue(string sectionName, string keyName, T? value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (string.IsNullOrWhiteSpace(sectionName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(sectionName)); + + if (string.IsNullOrWhiteSpace(keyName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(keyName)); + + SetValueImpl(sectionName, keyName, value); + } + + protected abstract void SaveConfigurationImpl(); + protected abstract void SaveConfigurationImpl(string configurationPath); + + protected abstract bool HasSectionImpl(string sectionName); + protected abstract bool HasSectionKeyImpl(string sectionName, string keyName); + + protected abstract IEnumerable GetSectionNamesImpl(); + protected abstract IEnumerable GetSectionOptionNamesImpl(string sectionName); + + protected abstract bool RemoveSectionImpl(string sectionName); + protected abstract bool RemoveOptionImpl(string sectionName, string keyName); + + protected abstract void SetValueImpl(string sectionName, string keyName, T value); + protected abstract object GetValueImpl(Type typeObject, string sectionName, string keyName); +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationBuilder.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationBuilder.cs new file mode 100644 index 000000000..d70562baa --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationBuilder.cs @@ -0,0 +1,35 @@ +using System.Collections.Specialized; +using pyRevitLabs.Configurations.Abstractions; + +namespace pyRevitLabs.Configurations; + +public sealed class ConfigurationBuilder +{ + private readonly bool _readOnly; + private readonly List _names = []; + private readonly Dictionary _configurations = []; + + public ConfigurationBuilder(bool readOnly) + { + _readOnly = readOnly; + } + + public ConfigurationBuilder AddConfigurationSource(string configurationName, IConfiguration configuration) + { + if (configuration == null) + throw new ArgumentNullException(nameof(configuration)); + + if (string.IsNullOrWhiteSpace(configurationName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(configurationName)); + + _names.Add(new ConfigurationName() {Index = _configurations.Count, Name = configurationName}); + _configurations.Add(configurationName, configuration); + + return this; + } + + public IConfigurationService Build() + { + return ConfigurationService.Create(_readOnly, _names, _configurations); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationName.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationName.cs new file mode 100644 index 000000000..44ab7d756 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationName.cs @@ -0,0 +1,7 @@ +namespace pyRevitLabs.Configurations; + +internal sealed record ConfigurationName +{ + public int Index { get; set; } + public string? Name { get; set; } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationService.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationService.cs new file mode 100644 index 000000000..51ae89ec0 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/ConfigurationService.cs @@ -0,0 +1,192 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Attributes; +using pyRevitLabs.Configurations.Exceptions; + +namespace pyRevitLabs.Configurations; + +public sealed class ConfigurationService : IConfigurationService +{ + private readonly List _names; + private readonly IDictionary _configurations; + + public const string DefaultConfigurationName = "Default"; + + internal ConfigurationService(bool readOnly, + List names, + IDictionary configurations) + { + _names = names; + _configurations = configurations; + + ReadOnly = readOnly; + + // TODO: Change behavior + ReloadLoadConfigurations(); + } + + internal static IConfigurationService Create(bool readOnly, List names, + IDictionary configurations) + { + return new ConfigurationService(readOnly, names, configurations); + } + + public bool ReadOnly { get; } + + public IEnumerable ConfigurationNames => _configurations.Keys; + + public IEnumerable Configurations => _names + .Select(item => _configurations[item.Name!]) + .ToArray(); + + public IConfiguration this[string configurationName] + { + get + { + if (string.IsNullOrWhiteSpace(configurationName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(configurationName)); + + if (!_configurations.TryGetValue(configurationName, out IConfiguration? configuration)) + throw new InvalidOperationException($"Configuration {configurationName} not found"); + + return configuration; + } + } + + public CoreSection Core { get; private set; } = new(); + public RoutesSection Routes { get; private set; } = new(); + public TelemetrySection Telemetry { get; private set; } = new(); + public EnvironmentSection Environment { get; private set; } = new(); + + public void ReloadLoadConfigurations() + { + Core = GetSection(); + Routes = GetSection(); + Telemetry = GetSection(); + Environment = GetSection(); + } + + public T GetSection() + { + Type configurationType = typeof(T); + return (T) CreateSection(configurationType, Configurations.Reverse().ToArray()); + } + + public void SaveSection(string configurationName, T sectionValue) + { + if (sectionValue is null) + throw new ArgumentNullException(nameof(sectionValue)); + + if (string.IsNullOrWhiteSpace(configurationName)) + throw new ArgumentException("Value cannot be null or whitespace.", nameof(configurationName)); + + if (!_configurations.TryGetValue(configurationName, out IConfiguration? configuration)) + throw new ArgumentException($"Configuration with name {configurationName} not found"); + + Type configurationType = typeof(T); + SaveSection(configurationType, sectionValue, configuration); + } + + public void SetSectionKeyValue(string configurationName, string sectionName, string keyName, T keyValue) + { + if (keyValue == null) + throw new ArgumentNullException(nameof(keyValue)); + + if (string.IsNullOrEmpty(configurationName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(configurationName)); + + if (string.IsNullOrEmpty(sectionName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(sectionName)); + + if (string.IsNullOrEmpty(keyName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(keyName)); + + if (!_configurations.TryGetValue(configurationName, out IConfiguration? configuration)) + throw new ArgumentException($"Configuration with name {configurationName} not found"); + + configuration.SetValue(sectionName, keyName, keyValue); + } + + public T? GetSectionKeyValueOrDefault( + string configurationName, + string sectionName, + string keyName, + T? defaultValue = default) + { + if (string.IsNullOrEmpty(configurationName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(configurationName)); + + if (string.IsNullOrEmpty(sectionName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(sectionName)); + + if (string.IsNullOrEmpty(keyName)) + throw new ArgumentException("Value cannot be null or empty.", nameof(keyName)); + + if (!_configurations.TryGetValue(configurationName, out IConfiguration? configuration)) + throw new ArgumentException($"Configuration with name {configurationName} not found"); + + return configuration.GetValue(sectionName, keyName); + } + + private void SaveSection(Type configurationType, object sectionValue, IConfiguration configuration) + { + string sectionName = + GetCustomAttribute(configurationType)?.SectionName ?? configurationType.Name; + + foreach (var propertyInfo in GetProperties(configurationType)) + { + string keyName = GetCustomAttribute(propertyInfo)?.KeyName ?? propertyInfo.Name; + object? defaultValue = GetKeyValue(Configurations, propertyInfo, sectionName, keyName); + + object? keyValue = propertyInfo.GetValue(sectionValue); + if (keyValue is null) + configuration.RemoveOption(sectionName, keyName); + else if(!keyValue.Equals(defaultValue)) + configuration.SetValue(sectionName, keyName, keyValue); + } + + configuration.SaveConfiguration(); + } + + private static object CreateSection(Type configurationType, params IConfiguration[] configurations) + { + string sectionName = + GetCustomAttribute(configurationType)?.SectionName ?? configurationType.Name; + + var sectionConfiguration = Activator.CreateInstance(configurationType); + + foreach (var propertyInfo in GetProperties(configurationType)) + { + string keyName = GetCustomAttribute(propertyInfo)?.KeyName ?? propertyInfo.Name; + + object? keyValue = GetKeyValue(configurations, propertyInfo, sectionName, keyName); + propertyInfo.SetValue(sectionConfiguration, keyValue ?? propertyInfo.GetValue(sectionConfiguration)); + } + + return sectionConfiguration!; + } + + private static object? GetKeyValue( + IEnumerable configurations, + PropertyInfo propertyInfo, + string sectionName, string keyName) + { + return configurations + .Select(item=> item.GetValueOrDefault(propertyInfo.PropertyType, sectionName, keyName)) + .FirstOrDefault(item => item != default); + } + + private static IEnumerable GetProperties(Type configurationType) + { + var flags = BindingFlags.Instance | BindingFlags.Public; + return configurationType.GetProperties(flags) + .Where(item => item.CanWrite && item.CanRead); + } + + private static T? GetCustomAttribute(MemberInfo memberInfo) where T : Attribute + { + return memberInfo.GetCustomAttributes(typeof(T), false).FirstOrDefault() as T; + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Constants.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Constants.cs new file mode 100644 index 000000000..6a62a45fb --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Constants.cs @@ -0,0 +1,11 @@ +namespace pyRevitLabs.Configurations; + +internal static class Constants +{ + // pyrevit.exe specific + public const string EnvSectionName = "environment"; + public const string EnvInstalledClonesKey = "clones"; + public const string EnvExtensionLookupSourcesKey = "sources"; + public const string EnvTemplateSourcesKey = "templates"; + public const string EnvExtensionDBFileName = "PyRevitExtensionsDB.json"; +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/CoreSection.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/CoreSection.cs new file mode 100644 index 000000000..2d3c808f5 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/CoreSection.cs @@ -0,0 +1,75 @@ +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Attributes; + +namespace pyRevitLabs.Configurations; + +public enum LogLevels +{ + Quiet, + Verbose, + Debug +} + +[SectionName("core")] +public sealed record CoreSection +{ + [KeyName("bincache")] + public bool? BinCache { get; set; } + + [KeyName("loadbeta")] + public bool? LoadBeta { get; set; } + + [KeyName("autoupdate")] + public bool? AutoUpdate { get; set; } + + [KeyName("checkupdates")] + public bool? CheckUpdates { get; set; } + + [KeyName("usercanupdate")] + public bool? UserCanUpdate { get; set; } = true; + + [KeyName("usercanextend")] + public bool? UserCanExtend { get; set; } = true; + + [KeyName("usercanconfig")] + public bool? UserCanConfig { get; set; } = true; + + [KeyName("rocketmode")] + public bool? RocketMode { get; set; } = true; + + [KeyName("user_locale")] + public string? UserLocale { get; set; } = "en_us"; + + [KeyName("debug")] + public bool? Debug { get; set; } + + [KeyName("verbose")] + public bool? Verbose { get; set; } + + [KeyName("filelogging")] + public bool? FileLogging { get; set; } + + [KeyName("startuplogtimeout")] + public int? StartupLogTimeout { get; set; } = 10; + + [KeyName("cpyengine")] + public int? CpythonEngineVersion { get; set; } + + [KeyName("requiredhostbuild")] + public string? RequiredHostBuild { get; set; } + + [KeyName("minhostdrivefreespace")] + public long? MinHostDriveFreeSpace { get; set; } + + [KeyName("colorize_docs")] + public bool? ColorizeDocs { get; set; } + + [KeyName("tooltip_debug_info")] + public bool? TooltipDebugInfo { get; set; } + + [KeyName("outputstylesheet")] + public string? OutputStyleSheet { get; set; } + + [KeyName("userextensions")] + public List UserExtensions { get; set; } = new(); +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/EnvironmentSection.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/EnvironmentSection.cs new file mode 100644 index 000000000..d9c84a777 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/EnvironmentSection.cs @@ -0,0 +1,13 @@ +using pyRevitLabs.Configurations.Attributes; + +namespace pyRevitLabs.Configurations; + +[SectionName("environment")] +public record EnvironmentSection +{ + [KeyName("sources")] + public List Sources { get; set; } = new(); + + [KeyName("clones")] + public Dictionary Clones { get; set; } = new(); +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationException.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationException.cs new file mode 100644 index 000000000..30658795b --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationException.cs @@ -0,0 +1,10 @@ +namespace pyRevitLabs.Configurations.Exceptions; + +/// +public class ConfigurationException(string message) : Exception(message) +{ + public ConfigurationException() : this("") + { + + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationSectionKeyNotFoundException.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationSectionKeyNotFoundException.cs new file mode 100644 index 000000000..00775453c --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationSectionKeyNotFoundException.cs @@ -0,0 +1,13 @@ +namespace pyRevitLabs.Configurations.Exceptions; + +public sealed class ConfigurationSectionKeyNotFoundException(string message, string keyName, string sectionName) + : ConfigurationException(message) +{ + public string KeyName { get; } = keyName; + public string SectionName { get; } = sectionName; + + public ConfigurationSectionKeyNotFoundException(string keyName, string sectionName) + : this("", keyName, sectionName) + { + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationSectionNotFoundException.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationSectionNotFoundException.cs new file mode 100644 index 000000000..e75c71a64 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Exceptions/ConfigurationSectionNotFoundException.cs @@ -0,0 +1,13 @@ +namespace pyRevitLabs.Configurations.Exceptions; + +public sealed class ConfigurationSectionNotFoundException(string message, string sectionName) + : ConfigurationException(message) +{ + public string SectionName { get; } = sectionName; + + public ConfigurationSectionNotFoundException(string sectionName) + : this("", sectionName) + { + + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/Extensions/ConfigurationExtensions.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/Extensions/ConfigurationExtensions.cs new file mode 100644 index 000000000..36651e23c --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/Extensions/ConfigurationExtensions.cs @@ -0,0 +1,6 @@ +namespace pyRevitLabs.Configurations.Extensions; + +public static class ConfigurationExtensions +{ + +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/RoutesSection.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/RoutesSection.cs new file mode 100644 index 000000000..f1ff7d360 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/RoutesSection.cs @@ -0,0 +1,19 @@ +using pyRevitLabs.Configurations.Attributes; + +namespace pyRevitLabs.Configurations; + +[SectionName("routes")] +public sealed record RoutesSection +{ + [KeyName("enabled")] + public bool? Status { get; set; } + + [KeyName("host")] + public string? Host { get; set; } + + [KeyName("port")] + public int? Port { get; set; } = 48884; + + [KeyName("core_api")] + public bool? LoadCoreApi { get; set; } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/TelemetrySection.cs b/dev/pyRevitLabs/pyRevitLabs.Configurations/TelemetrySection.cs new file mode 100644 index 000000000..daa4bcadf --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/TelemetrySection.cs @@ -0,0 +1,31 @@ +using pyRevitLabs.Configurations.Attributes; + +namespace pyRevitLabs.Configurations; + +[SectionName("telemetry")] +public record TelemetrySection +{ + [KeyName("active")] + public bool? TelemetryStatus { get; set; } + + [KeyName("utc_timestamps")] + public bool? TelemetryUseUtcTimeStamps { get; set; } + + [KeyName("telemetry_file_dir")] + public string? TelemetryFileDir { get; set; } + + [KeyName("telemetry_server_url")] + public string? TelemetryServerUrl { get; set; } + + [KeyName("include_hooks")] + public bool? TelemetryIncludeHooks { get; set; } + + [KeyName("active_app")] + public bool? AppTelemetryStatus { get; set; } + + [KeyName("apptelemetry_server_url")] + public string? AppTelemetryServerUrl { get; set; } + + [KeyName("apptelemetry_event_flags")] + public int? AppTelemetryEventFlags { get; set; } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.Configurations/pyRevitLabs.Configurations.csproj b/dev/pyRevitLabs/pyRevitLabs.Configurations/pyRevitLabs.Configurations.csproj new file mode 100644 index 000000000..890effb41 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Configurations/pyRevitLabs.Configurations.csproj @@ -0,0 +1,16 @@ + + + + net48;net8.0 + enable + enable + 12 + + + + + <_Parameter1>pyRevitLabs.Configurations.Tests + + + + diff --git a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitAttachments.cs b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitAttachments.cs index a51339782..c3f0d5771 100644 --- a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitAttachments.cs +++ b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitAttachments.cs @@ -2,15 +2,13 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Text.RegularExpressions; +using System.Linq; using System.Security.Principal; using System.Text; -using System.Linq; +using System.Text.RegularExpressions; using pyRevitLabs.Common; using pyRevitLabs.Common.Extensions; - -using MadMilkman.Ini; using pyRevitLabs.Json.Linq; using pyRevitLabs.NLog; using pyRevitLabs.TargetApps.Revit; diff --git a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitCaches.cs b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitCaches.cs index c91f20dfe..e40f4714d 100644 --- a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitCaches.cs +++ b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitCaches.cs @@ -1,22 +1,12 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; +using System.IO; using System.Text.RegularExpressions; -using System.Security.Principal; -using System.Text; - using pyRevitLabs.Common; -using pyRevitLabs.Common.Extensions; - -using MadMilkman.Ini; -using pyRevitLabs.Json.Linq; using pyRevitLabs.NLog; using pyRevitLabs.TargetApps.Revit; namespace pyRevitLabs.PyRevit { public static class PyRevitCaches { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); // pyrevit cache folder // @reviewed diff --git a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitClones.cs b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitClones.cs index 8d58d8028..649d61b0b 100644 --- a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitClones.cs +++ b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitClones.cs @@ -2,14 +2,14 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Text.RegularExpressions; +using System.Linq; using System.Security.Principal; using System.Text; +using System.Text.RegularExpressions; using pyRevitLabs.Common; using pyRevitLabs.Common.Extensions; - -using MadMilkman.Ini; +using pyRevitLabs.Configurations; using pyRevitLabs.Json.Linq; using pyRevitLabs.NLog; @@ -100,30 +100,27 @@ public static List GetRegisteredClones() { // safely get clone list var cfg = PyRevitConfigs.GetConfigFile(); - var clonesList = cfg.GetDictValue(PyRevitConsts.EnvConfigsSectionName, PyRevitConsts.EnvConfigsInstalledClonesKey); - - if (clonesList != null) { - // verify all registered clones, protect against tampering - foreach (var cloneKeyValue in clonesList) { - var clonePath = cloneKeyValue.Value.NormalizeAsPath(); - if (CommonUtils.VerifyPath(clonePath)) { - try { - var clone = new PyRevitClone(clonePath, name: cloneKeyValue.Key); - if (clone.IsValid && !validatedClones.Contains(clone)) { - logger.Debug("Verified clone \"{0}={1}\"", clone.Name, clone.ClonePath); - validatedClones.Add(clone); - } - } - catch { - logger.Debug("Error occured when processing registered clone \"{0}\" at \"{1}\"", cloneKeyValue.Key, clonePath); + + // verify all registered clones, protect against tampering + foreach (var cloneKeyValue in cfg.Environment.Clones) { + var clonePath = cloneKeyValue.Value.NormalizeAsPath(); + if (CommonUtils.VerifyPath(clonePath)) { + try { + var clone = new PyRevitClone(clonePath, name: cloneKeyValue.Key); + if (clone.IsValid && !validatedClones.Contains(clone)) { + logger.Debug("Verified clone \"{@CloneName}={@ClonePath}\"", clone.Name, clone.ClonePath); + validatedClones.Add(clone); } } + catch { + logger.Debug("Error occured when processing registered clone \"{@CloneName}\" at \"{@ClonePath}\"", cloneKeyValue.Key, clonePath); + } } - - // rewrite the verified clones list back to config file - SaveRegisteredClones(validatedClones); } + // rewrite the verified clones list back to config file + SaveRegisteredClones(validatedClones); + return validatedClones; } @@ -553,12 +550,10 @@ public static void UpdateAllClones(GitInstallerCredentials credentials) { // updates the config value for registered clones public static void SaveRegisteredClones(IEnumerable clonesList) { var cfg = PyRevitConfigs.GetConfigFile(); - var newValueDic = new Dictionary(); - foreach (var clone in clonesList) - newValueDic[clone.Name] = clone.ClonePath; - - cfg.SetValue(PyRevitConsts.EnvConfigsSectionName, PyRevitConsts.EnvConfigsInstalledClonesKey, newValueDic); + cfg.SaveSection( + ConfigurationService.DefaultConfigurationName, + new EnvironmentSection() + {Clones = clonesList.ToDictionary(item => item.Name, item => item.ClonePath)}); } - } } diff --git a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfig.cs b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfig.cs index 8ccc23e7d..5f282702b 100644 --- a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfig.cs +++ b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfig.cs @@ -1,150 +1 @@ -using System; -using System.Collections.Generic; -using System.IO; - -using pyRevitLabs.Common; -using pyRevitLabs.Common.Extensions; - -using MadMilkman.Ini; -using pyRevitLabs.NLog; - - -namespace pyRevitLabs.PyRevit { - public class PyRevitConfig { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - - private IniFile _config; - private bool _adminMode; - - public string ConfigFilePath { get; private set; } - - public PyRevitConfig(string cfgFilePath, bool adminMode = false) { - if (cfgFilePath is null) - throw new PyRevitException("Config file path can not be null."); - - if (CommonUtils.VerifyFile(cfgFilePath)) - { - ConfigFilePath = cfgFilePath; - - // INI formatting - var cfgOps = new IniOptions(); - cfgOps.KeySpaceAroundDelimiter = true; - cfgOps.Encoding = CommonUtils.GetUTF8NoBOMEncoding(); - _config = new IniFile(cfgOps); - - using (var cfgStream = File.Open(cfgFilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) { - _config.Load(cfgStream); - } - _adminMode = adminMode; - } - else - throw new PyRevitException($"Can not access config file at {cfgFilePath}"); - } - - // save config file to standard location - public void SaveConfigFile() { - if (_adminMode) { - logger.Debug("Config is in admin mode. Skipping save"); - return; - } - - logger.Debug("Saving config file \"{0}\"", PyRevitConsts.ConfigFilePath); - try { - _config.Save(PyRevitConsts.ConfigFilePath); - } - catch (Exception ex) { - throw new PyRevitException(string.Format("Failed to save config to \"{0}\". | {1}", - PyRevitConsts.ConfigFilePath, ex.Message)); - } - } - - // get config key value - public string GetValue(string sectionName, string keyName) { - logger.Debug(string.Format("Try getting config \"{0}:{1}\"", sectionName, keyName)); - if (_config.Sections.Contains(sectionName) && _config.Sections[sectionName].Keys.Contains(keyName)) { - var cfgValue = _config.Sections[sectionName].Keys[keyName].Value as string; - logger.Debug(string.Format("Config \"{0}:{1}\" = \"{2}\"", sectionName, keyName, cfgValue)); - return cfgValue; - } - else { - logger.Debug(string.Format("Config \"{0}:{1}\" not set.", sectionName, keyName)); - return null; - } - } - - // get config key value and make a string list out of it - public List GetListValue(string sectionName, string keyName) { - logger.Debug("Try getting config as list \"{0}:{1}\"", sectionName, keyName); - var stringValue = GetValue(sectionName, keyName); - if (stringValue != null) - return stringValue.ConvertFromTomlListString(); - else - return null; - } - - // get config key value and make a string dictionary out of it - public Dictionary GetDictValue(string sectionName, string keyName) { - logger.Debug("Try getting config as dict \"{0}:{1}\"", sectionName, keyName); - var stringValue = GetValue(sectionName, keyName); - if (stringValue != null) - return stringValue.ConvertFromTomlDictString(); - else - return null; - } - - // set config key value, creates the config if not set yet - public void SetValue(string sectionName, string keyName, string stringValue) { - if (stringValue != null) { - if (!_config.Sections.Contains(sectionName)) { - logger.Debug("Adding config section \"{0}\"", sectionName); - _config.Sections.Add(sectionName); - } - - if (!_config.Sections[sectionName].Keys.Contains(keyName)) { - logger.Debug("Adding config key \"{0}:{1}\"", sectionName, keyName); - _config.Sections[sectionName].Keys.Add(keyName); - } - - logger.Debug("Updating config \"{0}:{1} = {2}\"", sectionName, keyName, stringValue); - _config.Sections[sectionName].Keys[keyName].Value = stringValue; - - SaveConfigFile(); - } - else - logger.Debug("Can not set null value for \"{0}:{1}\"", sectionName, keyName); - } - - // sets config key value as bool - public void SetValue(string sectionName, string keyName, bool boolValue) { - SetValue(sectionName, keyName, boolValue.ConvertToTomlBoolString()); - } - - // sets config key value as int - public void SetValue(string sectionName, string keyName, int intValue) { - SetValue(sectionName, keyName, intValue.ConvertToTomlIntString()); - } - - // sets config key value as string list - public void SetValue(string sectionName, string keyName, IEnumerable listString) { - SetValue(sectionName, keyName, listString.ConvertToTomlListString()); - } - - // sets config key value as string dictionary - public void SetValue(string sectionName, string keyName, IDictionary dictString) { - SetValue(sectionName, keyName, dictString.ConvertToTomlDictString()); - } - - // removes a value from config file - public bool DeleteValue(string sectionName, string keyName) { - logger.Debug(string.Format("Try getting config \"{0}:{1}\"", sectionName, keyName)); - if (_config.Sections.Contains(sectionName) && _config.Sections[sectionName].Keys.Contains(keyName)) { - logger.Debug(string.Format("Removing config \"{0}:{1}\"", sectionName, keyName)); - return _config.Sections[sectionName].Keys.Remove(keyName); - } - else { - logger.Debug(string.Format("Config \"{0}:{1}\" not set.", sectionName, keyName)); - return false; - } - } - } -} + \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfigs.cs b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfigs.cs index 3e03db265..556d79ae0 100644 --- a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfigs.cs +++ b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConfigs.cs @@ -4,30 +4,14 @@ using System.Security.AccessControl; using pyRevitLabs.Common; +using pyRevitLabs.Configurations; +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Ini; +using pyRevitLabs.Configurations.Ini.Extensions; using pyRevitLabs.NLog; namespace pyRevitLabs.PyRevit { - public class PyRevitConfigValueNotSet : PyRevitException - { - public PyRevitConfigValueNotSet(string sectionName, string keyName) - { - ConfigSection = sectionName; - ConfigKey = keyName; - } - - public string ConfigSection { get; set; } - public string ConfigKey { get; set; } - - public override string Message - { - get - { - return String.Format("Config value not set \"{0}:{1}\"", ConfigSection, ConfigKey); - } - } - } - public enum PyRevitLogLevels { Quiet, @@ -37,43 +21,60 @@ public enum PyRevitLogLevels public static class PyRevitConfigs { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - // get config file - public static PyRevitConfig GetConfigFile() + /// + /// Returns config file. + /// + /// Returns admin config if admin config exists and readonly and user config not found. + /// + /// Seeds admin config file when user config not found and admin config not readonly. + /// + public static IConfigurationService GetConfigFile(string overrideName = default) { - // make sure the file exists and if not create an empty one string userConfig = PyRevitConsts.ConfigFilePath; string adminConfig = PyRevitConsts.AdminConfigFilePath; - if (!CommonUtils.VerifyFile(userConfig)) + // create admin config + if (!File.Exists(userConfig) + && File.Exists(adminConfig) + && new FileInfo(adminConfig).IsReadOnly) { - if (CommonUtils.VerifyFile(adminConfig)) - { - if (new FileInfo(adminConfig).IsReadOnly) - return new PyRevitConfig(adminConfig, adminMode: true); - else - SetupConfig(adminConfig); - } - else - SetupConfig(); + _logger.Debug("Creating admin config service {@ConfigPath}...", adminConfig); + return CreateConfiguration(adminConfig, true, overrideName); + } + + // copy admin config to user config + // first run when user config not created + if (!File.Exists(userConfig) + && !new FileInfo(adminConfig).IsReadOnly) + { + _logger.Debug("Copy admin config file..."); + SetupConfig(adminConfig); } - return new PyRevitConfig(userConfig); + _logger.Debug("Creating user config service {@ConfigPath}...", userConfig); + return CreateConfiguration(userConfig, false, overrideName); } - // deletes config file + /// + /// Removes user config file. + /// + /// public static void DeleteConfig() { - if (File.Exists(PyRevitConsts.ConfigFilePath)) - try - { - File.Delete(PyRevitConsts.ConfigFilePath); - } - catch (Exception ex) - { - throw new PyRevitException(string.Format("Failed deleting config file \"{0}\" | {1}", PyRevitConsts.ConfigFilePath, ex.Message)); - } + if (!File.Exists(PyRevitConsts.ConfigFilePath)) return; + + _logger.Info("Deleting config {@ConfigPath}...", PyRevitConsts.ConfigFilePath); + + try + { + File.Delete(PyRevitConsts.ConfigFilePath); + } + catch (Exception ex) + { + throw new PyRevitException($"Failed deleting config file \"{PyRevitConsts.ConfigFilePath}\"", ex); + } } // copy config file into all users directory as seed config file @@ -82,37 +83,33 @@ public static void SeedConfig(bool lockSeedConfig = false) string sourceFile = PyRevitConsts.ConfigFilePath; string targetFile = PyRevitConsts.AdminConfigFilePath; - logger.Debug("Seeding config file \"{0}\" to \"{1}\"", sourceFile, targetFile); + _logger.Debug("Seeding config file \"{@SourceFile}\" to \"{@TargetFile}\"", sourceFile, targetFile); + + if (!File.Exists(sourceFile)) return; try { - if (File.Exists(sourceFile)) - { - File.Copy(sourceFile, targetFile, true); + File.Copy(sourceFile, targetFile, true); - if (lockSeedConfig) + if (lockSeedConfig) + { + try + { + File.SetAttributes(targetFile, FileAttributes.ReadOnly); + } + catch (InvalidOperationException ex) { var currentUser = WindowsIdentity.GetCurrent(); - try - { - File.SetAttributes(targetFile, FileAttributes.ReadOnly); - } - catch (InvalidOperationException ex) - { - logger.Error( - string.Format( - "You cannot assign ownership to user \"{0}\"." + - "Either you don't have TakeOwnership permissions, " + - "or it is not your user account. | {1}", currentUser.Name, ex.Message - ) - ); - } + _logger.Error(ex, + $"You cannot assign ownership to user \"{currentUser.Name}\"." + + "Either you don't have TakeOwnership permissions, " + + "or it is not your user account."); } } } catch (Exception ex) { - throw new PyRevitException(string.Format("Failed seeding config file. | {0}", ex.Message)); + throw new PyRevitException("Failed seeding config file.", ex); } } @@ -123,489 +120,552 @@ public static void SetupConfig(string templateConfigFilePath = null) string sourceFile = templateConfigFilePath; string targetFile = PyRevitConsts.ConfigFilePath; - if (sourceFile is string) + if (string.IsNullOrEmpty(sourceFile)) { - logger.Debug("Seeding config file \"{0}\" to \"{1}\"", sourceFile, targetFile); + CommonUtils.EnsureFile(targetFile); + return; + } - try - { - File.WriteAllText(targetFile, File.ReadAllText(sourceFile)); - } - catch (Exception ex) - { - throw new PyRevitException( - $"Failed configuring config file from template at {sourceFile} | {ex.Message}" - ); - } + + _logger.Debug("Seeding config file \"{@SourceFile}\" to \"{@TargetFile}\"", sourceFile, targetFile); + + try + { + File.WriteAllText(targetFile, File.ReadAllText(sourceFile)); } - else - CommonUtils.EnsureFile(targetFile); + catch (Exception ex) + { + throw new PyRevitException($"Failed configuring config file from template at {sourceFile}...", ex); + } + } + + private static IConfigurationService CreateConfiguration(string configPath, bool readOnly, string overrideName) + { + var builder = new ConfigurationBuilder(readOnly) + .AddIniConfiguration(configPath, ConfigurationService.DefaultConfigurationName, readOnly); + + if (!string.IsNullOrEmpty(overrideName) + && overrideName?.Equals(ConfigurationService.DefaultConfigurationName) != true) + { + builder.AddIniConfiguration( + Path.ChangeExtension(configPath, + $"{overrideName}{IniConfiguration.DefaultFileExtension}"), overrideName, readOnly); + } + + return builder.Build(); } // specific configuration public access ====================================================================== // general telemetry public static bool GetUTCStamps() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryUTCTimestampsKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsTelemetryUTCTimestampsDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Telemetry?.TelemetryUseUtcTimeStamps ?? false; } - public static void SetUTCStamps(bool state) + public static void SetUTCStamps(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - logger.Debug("Setting telemetry utc timestamps..."); - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryUTCTimestampsKey, state); + _logger.Debug("Setting telemetry utc timestamps to {@TelemetryStatus}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new TelemetrySection() {TelemetryStatus = state}); } // routes public static bool GetRoutesServerStatus() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsRoutesSection, PyRevitConsts.ConfigsRoutesServerKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsRoutesServerDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Routes.Status ?? false; } - public static void SetRoutesServerStatus(bool state) + public static void SetRoutesServerStatus(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsRoutesSection, PyRevitConsts.ConfigsRoutesServerKey, state); + _logger.Debug("Setting routes server status to {@Status}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new RoutesSection() {Status = state}); } - public static void EnableRoutesServer() => SetRoutesServerStatus(true); + public static void EnableRoutesServer(string revitVersion = ConfigurationService.DefaultConfigurationName) + => SetRoutesServerStatus(true, revitVersion); - public static void DisableRoutesServer() => SetRoutesServerStatus(false); + public static void DisableRoutesServer(string revitVersion = ConfigurationService.DefaultConfigurationName) + => SetRoutesServerStatus(false, revitVersion); public static string GetRoutesServerHost() { - var cfg = GetConfigFile(); - return cfg.GetValue(PyRevitConsts.ConfigsRoutesSection, PyRevitConsts.ConfigsRoutesHostKey); + IConfigurationService cfg = GetConfigFile(); + return cfg.Routes.Host; } - public static void SetRoutesServerHost(string host) + public static void SetRoutesServerHost(string host, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsRoutesSection, PyRevitConsts.ConfigsRoutesHostKey, host); + _logger.Debug("Setting routes server host to {@Host}...", host); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new RoutesSection() {Host = host}); } public static int GetRoutesServerPort() { - var cfg = GetConfigFile(); - var port = cfg.GetValue(PyRevitConsts.ConfigsRoutesSection, PyRevitConsts.ConfigsRoutesPortKey); - return port != null ? int.Parse(port) : PyRevitConsts.ConfigsRoutesPortDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Routes.Port ?? 48884; } - public static void SetRoutesServerPort(int port) + public static void SetRoutesServerPort(int port, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsRoutesSection, PyRevitConsts.ConfigsRoutesPortKey, port); + _logger.Debug("Setting routes server port to {@Port}...", port); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new RoutesSection() {Port = port}); } public static bool GetRoutesLoadCoreAPIStatus() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsRoutesSection, PyRevitConsts.ConfigsLoadCoreAPIKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsRoutesServerDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Routes.LoadCoreApi ?? false; } - public static void SetRoutesLoadCoreAPIStatus(bool state) + public static void SetRoutesLoadCoreAPIStatus(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsRoutesSection, PyRevitConsts.ConfigsLoadCoreAPIKey, state); + _logger.Debug("Setting routes load core API status to {@LoadCoreApi}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new RoutesSection() {LoadCoreApi = state}); } // telemetry public static bool GetTelemetryStatus() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryStatusKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsTelemetryStatusDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Telemetry?.TelemetryStatus ?? false; } - public static void SetTelemetryStatus(bool state) + public static void SetTelemetryStatus(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryStatusKey, state); + _logger.Debug("Setting telemetry status to {@TelemetryStatus}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new TelemetrySection() {TelemetryStatus = state}); } public static string GetTelemetryFilePath() { - var cfg = GetConfigFile(); - return cfg.GetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryFileDirKey) ?? string.Empty; + IConfigurationService cfg = GetConfigFile(); + return cfg.Telemetry.TelemetryFileDir; } public static string GetTelemetryServerUrl() { - var cfg = GetConfigFile(); - return cfg.GetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryServerUrlKey) ?? string.Empty; + IConfigurationService cfg = GetConfigFile(); + return cfg.Telemetry.TelemetryServerUrl; } - public static void EnableTelemetry(string telemetryFileDir = null, string telemetryServerUrl = null) + public static void EnableTelemetry(string telemetryFileDir = null, + string telemetryServerUrl = null, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - logger.Debug(string.Format("Enabling telemetry... path: \"{0}\" server: {1}", - telemetryFileDir, telemetryServerUrl)); - SetTelemetryStatus(true); + _logger.Debug("Enabling telemetry..."); - if (telemetryFileDir != null) + if (!Directory.Exists(telemetryFileDir)) { - if (telemetryFileDir == string.Empty) - { - // set empty value - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryFileDirKey, telemetryFileDir); - } - else - { - if (CommonUtils.VerifyPath(telemetryFileDir)) - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryFileDirKey, telemetryFileDir); - else - logger.Debug("Invalid log path \"{0}\"", telemetryFileDir); - } + _logger.Warn("Directory \"{@TelemetryFileDir}\" does not exist", telemetryFileDir); + telemetryFileDir = default; } - if (telemetryServerUrl != null) - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryServerUrlKey, telemetryServerUrl); + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, + new TelemetrySection() + { + TelemetryStatus = true, + TelemetryFileDir = telemetryFileDir, + TelemetryServerUrl = telemetryServerUrl + }); } public static bool GetTelemetryIncludeHooks() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryIncludeHooksKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsTelemetryIncludeHooksDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Telemetry.TelemetryIncludeHooks ?? false; } - public static void SetTelemetryIncludeHooks(bool state) + public static void SetTelemetryIncludeHooks(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryIncludeHooksKey, state); + _logger.Debug("Setting telemetry include hooks to {@TelemetryIncludeHooks}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new TelemetrySection() {TelemetryIncludeHooks = state}); } - public static void DisableTelemetry() + public static void DisableTelemetry(string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - logger.Debug("Disabling telemetry..."); - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsTelemetryStatusKey, false); + _logger.Debug("Disabling telemetry..."); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new TelemetrySection() {TelemetryStatus = false}); } // app telemetry public static bool GetAppTelemetryStatus() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsAppTelemetryStatusKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsAppTelemetryStatusDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Telemetry.AppTelemetryStatus ?? false; } - public static void SetAppTelemetryStatus(bool state) + public static void SetAppTelemetryStatus(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsAppTelemetryStatusKey, state); + _logger.Debug("Setting app telemetry status to {@AppTelemetryStatus}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new TelemetrySection() {AppTelemetryStatus = state}); } public static string GetAppTelemetryServerUrl() { - var cfg = GetConfigFile(); - return cfg.GetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsAppTelemetryServerUrlKey) ?? string.Empty; + IConfigurationService cfg = GetConfigFile(); + return cfg.Telemetry.AppTelemetryServerUrl; } - public static void EnableAppTelemetry(string apptelemetryServerUrl = null) + public static void EnableAppTelemetry(string apptelemetryServerUrl = null, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - logger.Debug(string.Format("Enabling app telemetry... server: {0}", apptelemetryServerUrl)); - SetAppTelemetryStatus(true); + _logger.Debug("Enabling app telemetry..."); - if (apptelemetryServerUrl != null) - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsAppTelemetryServerUrlKey, apptelemetryServerUrl); + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new TelemetrySection() {AppTelemetryServerUrl = apptelemetryServerUrl}); } - public static void DisableAppTelemetry() + public static void DisableAppTelemetry(string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - logger.Debug("Disabling app telemetry..."); - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsAppTelemetryStatusKey, false); + _logger.Debug("Disabling app telemetry..."); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new TelemetrySection() {AppTelemetryStatus = false}); } public static string GetAppTelemetryFlags() { - var cfg = GetConfigFile(); - return cfg.GetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsAppTelemetryEventFlagsKey) ?? string.Empty; + IConfigurationService cfg = GetConfigFile(); + return cfg.Telemetry.AppTelemetryEventFlags?.ToString("X") ?? string.Empty; } - public static void SetAppTelemetryFlags(string flags) + public static void SetAppTelemetryFlags(string flags, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - logger.Debug("Setting app telemetry flags..."); - if (flags != null) - cfg.SetValue(PyRevitConsts.ConfigsTelemetrySection, PyRevitConsts.ConfigsAppTelemetryEventFlagsKey, flags); + _logger.Debug("Setting app telemetry flags to {@AppTelemetryEventFlags}...", flags); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, + new TelemetrySection() + {AppTelemetryEventFlags = int.Parse(flags, System.Globalization.NumberStyles.HexNumber)}); } // caching public static bool GetBinaryCaches() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsBinaryCacheKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsBinaryCacheDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.BinCache ?? false; } - public static void SetBinaryCaches(bool state) + public static void SetBinaryCaches(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsBinaryCacheKey, state); + _logger.Debug("Setting binary caches {@BinCache}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {BinCache = state}); } // update checking config public static bool GetCheckUpdates() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCheckUpdatesKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsCheckUpdatesDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.CheckUpdates ?? false; } - public static void SetCheckUpdates(bool state) + public static void SetCheckUpdates(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCheckUpdatesKey, state); + _logger.Debug("Setting check updates to {@CheckUpdates}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {CheckUpdates = state}); } // auto update config public static bool GetAutoUpdate() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsAutoUpdateKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsAutoUpdateDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.AutoUpdate ?? false; } - public static void SetAutoUpdate(bool state) + public static void SetAutoUpdate(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsAutoUpdateKey, state); + _logger.Debug("Setting auto update to {@AutoUpdate}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {AutoUpdate = state}); } // rocket mode config public static bool GetRocketMode() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsRocketModeKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsRocketModeDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.RocketMode ?? false; } - public static void SetRocketMode(bool state) + public static void SetRocketMode(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsRocketModeKey, state); + _logger.Debug("Setting rocket mode to {@RocketMode}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {RocketMode = state}); } // logging level config public static PyRevitLogLevels GetLoggingLevel() { - var cfg = GetConfigFile(); - var verboseCfg = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsVerboseKey); - bool verbose = verboseCfg != null ? bool.Parse(verboseCfg) : PyRevitConsts.ConfigsVerboseDefault; - - var debugCfg = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsDebugKey); - bool debug = debugCfg != null ? bool.Parse(debugCfg) : PyRevitConsts.ConfigsDebugDefault; + IConfigurationService cfg = GetConfigFile(); - if (verbose && !debug) + if (cfg.Core.Verbose == true + && cfg.Core.Debug != true) return PyRevitLogLevels.Verbose; - else if (debug) + else if (cfg.Core.Debug == true) return PyRevitLogLevels.Debug; return PyRevitLogLevels.Quiet; } - public static void SetLoggingLevel(PyRevitLogLevels level) + public static void SetLoggingLevel(PyRevitLogLevels level, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); + _logger.Debug("Setting logging level to {@LogLevel}...", level); + + IConfigurationService cfg = GetConfigFile(revitVersion); if (level == PyRevitLogLevels.Quiet) { - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsVerboseKey, false); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsDebugKey, false); + cfg.SaveSection(revitVersion, new CoreSection() {Debug = false, Verbose = false}); } - - if (level == PyRevitLogLevels.Verbose) + else if (level == PyRevitLogLevels.Verbose) { - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsVerboseKey, true); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsDebugKey, false); + cfg.SaveSection(revitVersion, new CoreSection() {Debug = false, Verbose = true}); } - - if (level == PyRevitLogLevels.Debug) + else if (level == PyRevitLogLevels.Debug) { - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsVerboseKey, true); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsDebugKey, true); + cfg.SaveSection(revitVersion, new CoreSection() {Debug = true, Verbose = false}); } } // file logging config public static bool GetFileLogging() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsFileLoggingKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsFileLoggingDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.FileLogging ?? false; } - public static void SetFileLogging(bool state) + public static void SetFileLogging(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsFileLoggingKey, state); + _logger.Debug("Setting file logging to {@FileLogging}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {FileLogging = state}); } // misc startup public static int GetStartupLogTimeout() { - var cfg = GetConfigFile(); - var timeout = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsStartupLogTimeoutKey); - return timeout != null ? int.Parse(timeout) : PyRevitConsts.ConfigsStartupLogTimeoutDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.StartupLogTimeout ?? 0; } - public static void SetStartupLogTimeout(int timeout) + public static void SetStartupLogTimeout(int timeout, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsStartupLogTimeoutKey, timeout); + _logger.Debug("Setting startup log timeout to {@StartupLogTimeout}...", timeout); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {StartupLogTimeout = timeout}); } public static string GetRequiredHostBuild() { - var cfg = GetConfigFile(); - var timeout = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsRequiredHostBuildKey); - return timeout != null ? timeout : string.Empty; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.RequiredHostBuild ?? string.Empty; } - public static void SetRequiredHostBuild(string buildnumber) + public static void SetRequiredHostBuild(string buildnumber, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsRequiredHostBuildKey, buildnumber); + _logger.Debug("Setting required host build to {@RequiredHostBuild}...", buildnumber); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {RequiredHostBuild = buildnumber}); } - public static int GetMinHostDriveFreeSpace() + public static long GetMinHostDriveFreeSpace() { - var cfg = GetConfigFile(); - var timeout = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsMinDriveSpaceKey); - return timeout != null ? int.Parse(timeout) : 0; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.MinHostDriveFreeSpace ?? 0; } - public static void SetMinHostDriveFreeSpace(int freespace) + public static void SetMinHostDriveFreeSpace(long freespace, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsMinDriveSpaceKey, freespace); + _logger.Debug("Setting min host drive free space to {@MinHostDriveFreeSpace}...", freespace); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {MinHostDriveFreeSpace = freespace}); } // load beta config public static bool GetLoadBetaTools() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsLoadBetaKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsLoadBetaDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.LoadBeta ?? false; } - public static void SetLoadBetaTools(bool state) + public static void SetLoadBetaTools(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsLoadBetaKey, state); + _logger.Debug("Setting load beta tools to {@LoadBeta}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {LoadBeta = state}); } // cpythonengine public static int GetCpythonEngineVersion() { - var cfg = GetConfigFile(); - var timeout = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCPythonEngineKey); - return timeout != null ? int.Parse(timeout) : PyRevitConsts.ConfigsCPythonEngineDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.CpythonEngineVersion ?? 0; } - public static void SetCpythonEngineVersion(int version) + public static void SetCpythonEngineVersion(int version, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsCPythonEngineKey, version); + _logger.Debug("Setting cpyhon engine version to {@CpythonEngineVersion}...", version); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {CpythonEngineVersion = version}); } // ux ui public static string GetUserLocale() { - var cfg = GetConfigFile(); - return cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsLocaleKey) ?? string.Empty; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.UserLocale ?? "en_us"; } - public static void SetUserLocale(string localCode) + public static void SetUserLocale(string localCode, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsLocaleKey, localCode); + _logger.Debug("Setting user locale to {@LocalCode}...", localCode); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {UserLocale = localCode}); } public static string GetOutputStyleSheet() { - var cfg = GetConfigFile(); - return cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsOutputStyleSheet) ?? string.Empty; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.OutputStyleSheet ?? string.Empty; } - public static void SetOutputStyleSheet(string outputCSSFilePath) + public static void SetOutputStyleSheet(string outputCssFilePath, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - if (File.Exists(outputCSSFilePath)) - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsOutputStyleSheet, outputCSSFilePath); + _logger.Debug("Setting output style sheet to {@OutputCssFilePath}...", outputCssFilePath); + + IConfigurationService cfg = GetConfigFile(revitVersion); + if (File.Exists(outputCssFilePath)) + cfg.SaveSection(revitVersion, new CoreSection() {OutputStyleSheet = outputCssFilePath}); } // user access to tools public static bool GetUserCanUpdate() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserCanUpdateKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsUserCanUpdateDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.UserCanUpdate ?? false; } public static bool GetUserCanExtend() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserCanExtendKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsUserCanExtendDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.UserCanExtend ?? false; } public static bool GetUserCanConfig() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserCanConfigKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsUserCanConfigDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.UserCanConfig ?? false; } - public static void SetUserCanUpdate(bool state) + public static void SetUserCanUpdate(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserCanUpdateKey, state); + _logger.Debug("Setting user can install to {@UserCanUpdate}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {UserCanUpdate = state}); } - public static void SetUserCanExtend(bool state) + public static void SetUserCanExtend(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserCanExtendKey, state); + _logger.Debug("Setting user can install to {@UserCanExtend}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {UserCanExtend = state}); } - public static void SetUserCanConfig(bool state) + public static void SetUserCanConfig(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserCanConfigKey, state); + _logger.Debug("Setting user can install to {@UserCanConfig}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {UserCanConfig = state}); } public static bool GetColorizeDocs() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsColorizeDocsKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsColorizeDocsDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.ColorizeDocs ?? false; } - public static void SetColorizeDocs(bool state) + public static void SetColorizeDocs(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsColorizeDocsKey, state); + _logger.Debug("Setting colorize docs to {@ColorizeDocs}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {ColorizeDocs = state}); } public static bool GetAppendTooltipEx() { - var cfg = GetConfigFile(); - var status = cfg.GetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsAppendTooltipExKey); - return status != null ? bool.Parse(status) : PyRevitConsts.ConfigsAppendTooltipExDefault; + IConfigurationService cfg = GetConfigFile(); + return cfg.Core.TooltipDebugInfo ?? false; } - public static void SetAppendTooltipEx(bool state) + public static void SetAppendTooltipEx(bool state, + string revitVersion = ConfigurationService.DefaultConfigurationName) { - var cfg = GetConfigFile(); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsAppendTooltipExKey, state); + _logger.Debug("Setting tooltip debug info to {@TooltipDebugInfo}...", state); + + IConfigurationService cfg = GetConfigFile(revitVersion); + cfg.SaveSection(revitVersion, new CoreSection() {TooltipDebugInfo = state}); } } } diff --git a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConsts.cs b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConsts.cs index 7a9379536..1d1fd4f2a 100644 --- a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConsts.cs +++ b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitConsts.cs @@ -198,21 +198,10 @@ public static string FindConfigFileInDirectory(string sourcePath) { // pyRevit config file path // @reviewed - public static string ConfigFilePath { - get { - string configRoot = UserEnv.IsRunAsElevated() ? PyRevitLabsConsts.PyRevitProgramDataPath : PyRevitLabsConsts.PyRevitPath; - var cfgFile = FindConfigFileInDirectory(configRoot); - return cfgFile != null ? cfgFile : Path.Combine(configRoot, DefaultConfigsFileName); - } - } + public static string ConfigFilePath => Path.Combine(PyRevitLabsConsts.PyRevitPath, DefaultConfigsFileName); // pyRevit config file path // @reviewed - public static string AdminConfigFilePath { - get { - var cfgFile = FindConfigFileInDirectory(PyRevitLabsConsts.PyRevitProgramDataPath); - return cfgFile != null ? cfgFile : Path.Combine(PyRevitLabsConsts.PyRevitProgramDataPath, DefaultConfigsFileName); - } - } + public static string AdminConfigFilePath => Path.Combine(PyRevitLabsConsts.PyRevitProgramDataPath, DefaultConfigsFileName); } } diff --git a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitExtensions.cs b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitExtensions.cs index 3e059c4ad..8a4edf1fb 100644 --- a/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitExtensions.cs +++ b/dev/pyRevitLabs/pyRevitLabs.PyRevit/PyRevitExtensions.cs @@ -2,16 +2,18 @@ using System.Collections.Generic; using System.IO; using System.IO.Compression; -using System.Text.RegularExpressions; +using System.Linq; using System.Security.Principal; using System.Text; +using System.Text.RegularExpressions; using pyRevitLabs.Common; using pyRevitLabs.Common.Extensions; - -using MadMilkman.Ini; +using pyRevitLabs.Configurations; +using pyRevitLabs.Configurations.Abstractions; using pyRevitLabs.Json.Linq; using pyRevitLabs.NLog; +using Environment = System.Environment; /* * There are 3 types of extension functions here @@ -22,7 +24,7 @@ namespace pyRevitLabs.PyRevit { public static class PyRevitExtensions { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); // managing extensions ======================================================================================= // check if extension name matches the given pattern @@ -36,10 +38,10 @@ private static bool CompareExtensionNames(string extName, string searchTerm) { public static List FindExtensions(string searchPath) { var installedExtensions = new List(); - logger.Debug("Looking for installed extensions under \"{0}\"...", searchPath); + _logger.Debug("Looking for installed extensions under \"{0}\"...", searchPath); foreach (var subdir in Directory.GetDirectories(searchPath)) { if (PyRevitExtension.IsExtensionDirectory(subdir)) { - logger.Debug("Found installed extension \"{0}\"...", subdir); + _logger.Debug("Found installed extension \"{0}\"...", subdir); installedExtensions.Add(new PyRevitExtension(subdir)); } } @@ -68,7 +70,7 @@ public static List LookupRegisteredExtensions(string searchPattern); } catch (Exception ex) { - logger.Error( + _logger.Error( string.Format( "Error looking up extension with pattern \"{0}\" in default extension source." + " | {1}", searchPattern, ex.Message) @@ -85,7 +87,7 @@ public static List LookupRegisteredExtensions(string return matchedExtensions; } catch (Exception ex) { - logger.Error( + _logger.Error( string.Format( "Error looking up extension with pattern \"{0}\" in extension lookup source \"{1}\"" + " | {2}", searchPattern, extLookupSrc, ex.Message) @@ -100,13 +102,13 @@ public static List LookupRegisteredExtensions(string // lookup registered extension by name // @handled @logs public static PyRevitExtensionDefinition FindRegisteredExtension(string extensionName) { - logger.Debug("Looking up registered extension \"{0}\"...", extensionName); + _logger.Debug("Looking up registered extension \"{0}\"...", extensionName); var matchingExts = LookupRegisteredExtensions(extensionName); if (matchingExts.Count == 0) { throw new PyRevitException(string.Format("Can not find extension \"{0}\"", extensionName)); } else if (matchingExts.Count == 1) { - logger.Debug("Extension found \"{0}\"...", matchingExts[0].Name); + _logger.Debug("Extension found \"{0}\"...", matchingExts[0].Name); return matchingExts[0]; } else if (matchingExts.Count > 1) @@ -134,11 +136,11 @@ public static List GetInstalledExtensions(string searchPath = // find extension installed under registered search paths // @handled @logs public static PyRevitExtension GetInstalledExtension(string searchPattern) { - logger.Debug("Looking up installed extension \"{0}\"...", searchPattern); + _logger.Debug("Looking up installed extension \"{0}\"...", searchPattern); foreach (var ext in GetInstalledExtensions()) { - logger.Debug("-----------> {0}", ext.Name); + _logger.Debug("-----------> {0}", ext.Name); if (CompareExtensionNames(ext.Name, searchPattern)) { - logger.Debug(string.Format("\"{0}\" Matched installed extension \"{1}\"", + _logger.Debug(string.Format("\"{0}\" Matched installed extension \"{1}\"", searchPattern, ext.Name)); return ext; } @@ -183,8 +185,8 @@ public static void InstallExtension(string extensionName, PyRevitExtensionTypes // determine branch name branchName = branchName ?? PyRevitConsts.DefaultExtensionRepoDefaultBranch; - logger.Debug("Extension branch name determined as \"{0}\"", branchName); - logger.Debug("Installing extension into \"{0}\"", finalExtRepoPath); + _logger.Debug("Extension branch name determined as \"{0}\"", branchName); + _logger.Debug("Installing extension into \"{0}\"", finalExtRepoPath); // start the clone process var repo = GitInstaller.Clone(repoPath, branchName, finalExtRepoPath, credentials); @@ -194,16 +196,16 @@ public static void InstallExtension(string extensionName, PyRevitExtensionTypes // make sure to delete the repo if error occured after cloning var clonedPath = repo.Info.WorkingDirectory; if (GitInstaller.IsValidRepo(clonedPath)) { - logger.Debug("Clone successful \"{0}\"", clonedPath); + _logger.Debug("Clone successful \"{0}\"", clonedPath); RegisterExtensionSearchPath(destPath.NormalizeAsPath()); } else { - logger.Debug("Invalid repo after cloning. Deleting clone \"{0}\"", repoPath); + _logger.Debug("Invalid repo after cloning. Deleting clone \"{0}\"", repoPath); try { CommonUtils.DeleteDirectory(repoPath); } catch (Exception delEx) { - logger.Error(string.Format("Error post-install cleanup on \"{0}\" | {1}", + _logger.Error(string.Format("Error post-install cleanup on \"{0}\" | {1}", repoPath, delEx.Message)); } } @@ -218,7 +220,7 @@ public static void InstallExtension(string extensionName, PyRevitExtensionTypes // @handled @logs public static void InstallExtension(PyRevitExtensionDefinition extDef, string destPath = null, string branchName = null) { - logger.Debug("Installing extension \"{0}\"", extDef.Name); + _logger.Debug("Installing extension \"{0}\"", extDef.Name); InstallExtension(extDef.Name, extDef.Type, extDef.Url, destPath, branchName); } @@ -226,7 +228,7 @@ public static void InstallExtension(PyRevitExtensionDefinition extDef, // @handled @logs public static void RemoveExtension(string repoPath, bool removeSearchPath = false) { if (repoPath != null) { - logger.Debug("Uninstalling extension at \"{0}\"", repoPath); + _logger.Debug("Uninstalling extension at \"{0}\"", repoPath); CommonUtils.DeleteDirectory(repoPath); // remove search path if requested if (removeSearchPath) @@ -245,7 +247,7 @@ public static void UninstallExtension(PyRevitExtension ext, bool removeSearchPat // uninstalls an extension by name // @handled @logs public static void UninstallExtension(string extensionName, bool removeSearchPath = false) { - logger.Debug("Uninstalling extension \"{0}\"", extensionName); + _logger.Debug("Uninstalling extension \"{0}\"", extensionName); var ext = GetInstalledExtension(extensionName); RemoveExtension(ext.InstallPath, removeSearchPath: removeSearchPath); } @@ -253,8 +255,8 @@ public static void UninstallExtension(string extensionName, bool removeSearchPat // force update extension // @handled @logs public static void UpdateExtension(PyRevitExtension ext, GitInstallerCredentials credentials = null) { - logger.Debug("Updating extension \"{0}\"", ext.Name); - logger.Debug("Updating extension repo at \"{0}\"", ext.InstallPath); + _logger.Debug("Updating extension \"{0}\"", ext.Name); + _logger.Debug("Updating extension repo at \"{0}\"", ext.InstallPath); var res = GitInstaller.ForcedUpdate(ext.InstallPath, credentials); if (res <= UpdateStatus.Conflicts) throw new PyRevitException( @@ -270,7 +272,7 @@ public static void UpdateExtension(string extName, GitInstallerCredentials crede // force update all extensions // @handled @logs public static void UpdateAllInstalledExtensions(GitInstallerCredentials credentials = null) { - logger.Debug("Updating all installed extensions."); + _logger.Debug("Updating all installed extensions."); // update all installed extensions foreach (var ext in GetInstalledExtensions()) UpdateExtension(ext, credentials); @@ -278,58 +280,66 @@ public static void UpdateAllInstalledExtensions(GitInstallerCredentials credenti // enable extension in config // @handled @logs - private static void ToggleExtension(PyRevitExtension ext, bool state) { - logger.Debug("{0} extension \"{1}\"", state ? "Enabling" : "Disabling", ext.Name); - var cfg = PyRevitConfigs.GetConfigFile(); - cfg.SetValue(ext.ConfigName, PyRevitConsts.ExtensionDisabledKey, !state); + private static void ToggleExtension(string revitVersion, PyRevitExtension ext, bool state) { + _logger.Debug("{@State} extension \"{@ExtensionName}\"", state ? "Enabling" : "Disabling", ext.Name); + + IConfigurationService cfg = PyRevitConfigs.GetConfigFile(); + cfg.SetSectionKeyValue(revitVersion, ext.ConfigName, PyRevitConsts.ExtensionDisabledKey, !state); } // disable installed extension in config // @handled @logs - public static void EnableInstalledExtension(string searchPattern) { + public static void EnableInstalledExtension(string revitVersion, string searchPattern) { var ext = GetInstalledExtension(searchPattern); - ToggleExtension(ext, true); + ToggleExtension(revitVersion, ext, true); } // disable installed extension in config // @handled @logs - public static void DisableInstalledExtension(string searchPattern) { + public static void DisableInstalledExtension(string revitVersion, string searchPattern) { var ext = GetInstalledExtension(searchPattern); - ToggleExtension(ext, false); + ToggleExtension(revitVersion, ext, false); } // disable shipped extension in config // @handled @logs - public static void EnableShippedExtension(PyRevitClone clone, string searchPattern) { + public static void EnableShippedExtension(string revitVersion, PyRevitClone clone, string searchPattern) { var ext = GetShippedExtension(clone, searchPattern); - ToggleExtension(ext, true); + ToggleExtension(revitVersion, ext, true); } // disable shipped extension in config // @handled @logs - public static void DisableShippedExtension(PyRevitClone clone, string searchPattern) { + public static void DisableShippedExtension(string revitVersion, PyRevitClone clone, string searchPattern) { var ext = GetShippedExtension(clone, searchPattern); - ToggleExtension(ext, false); + ToggleExtension(revitVersion, ext, false); } // get list of registered extension search paths // @handled @logs public static List GetRegisteredExtensionSearchPaths() { + // TODO: Make apply config to revit version var validatedPaths = new List(); var cfg = PyRevitConfigs.GetConfigFile(); - var searchPaths = cfg.GetListValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserExtensionsKey); + + var searchPaths = cfg.GetSectionKeyValueOrDefault( + ConfigurationService.DefaultConfigurationName, + PyRevitConsts.ConfigsCoreSection, + PyRevitConsts.ConfigsUserExtensionsKey); + if (searchPaths != null) { // make sure paths exist foreach (var path in searchPaths) { var normPath = path.NormalizeAsPath(); if (CommonUtils.VerifyPath(path) && !validatedPaths.Contains(normPath)) { - logger.Debug("Verified extension search path \"{0}\"", normPath); + _logger.Debug("Verified extension search path \"{@ExtensionsSource}\"", normPath); validatedPaths.Add(normPath); } } // rewrite verified list - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserExtensionsKey, validatedPaths); + cfg.SaveSection( + ConfigurationService.DefaultConfigurationName, new CoreSection() {UserExtensions = validatedPaths}); } return validatedPaths; } @@ -337,12 +347,14 @@ public static List GetRegisteredExtensionSearchPaths() { // add extension search path // @handled @logs public static void RegisterExtensionSearchPath(string searchPath) { + // TODO: Make apply config to revit version var cfg = PyRevitConfigs.GetConfigFile(); if (CommonUtils.VerifyPath(searchPath)) { - logger.Debug("Adding extension search path \"{0}\"", searchPath); + _logger.Debug("Adding extension search path \"{@ExtensionSource}\"", searchPath); var searchPaths = GetRegisteredExtensionSearchPaths(); searchPaths.Add(searchPath.NormalizeAsPath()); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserExtensionsKey, searchPaths); + cfg.SaveSection( + ConfigurationService.DefaultConfigurationName, new CoreSection() {UserExtensions = searchPaths}); } else throw new pyRevitResourceMissingException(searchPath); @@ -353,10 +365,14 @@ public static void RegisterExtensionSearchPath(string searchPath) { public static void UnregisterExtensionSearchPath(string searchPath) { var cfg = PyRevitConfigs.GetConfigFile(); var normPath = searchPath.NormalizeAsPath(); - logger.Debug("Removing extension search path \"{0}\"", normPath); + _logger.Debug("Removing extension search path \"{@ExtensionSource}\"", normPath); var searchPaths = GetRegisteredExtensionSearchPaths(); searchPaths.Remove(normPath); - cfg.SetValue(PyRevitConsts.ConfigsCoreSection, PyRevitConsts.ConfigsUserExtensionsKey, searchPaths); + cfg.SetSectionKeyValue( + ConfigurationService.DefaultConfigurationName, + PyRevitConsts.ConfigsCoreSection, + PyRevitConsts.ConfigsUserExtensionsKey, + searchPaths); } // managing extension sources ================================================================================ @@ -370,16 +386,15 @@ public static string GetDefaultExtensionLookupSource() { // @handled @logs public static List GetRegisteredExtensionLookupSources() { var cfg = PyRevitConfigs.GetConfigFile(); + var normSources = new List(); - var sources = cfg.GetListValue(PyRevitConsts.EnvConfigsSectionName, PyRevitConsts.EnvConfigsExtensionLookupSourcesKey); - if (sources != null) { - foreach (var src in sources) { - var normSrc = src.NormalizeAsPath(); - logger.Debug("Extension lookup source \"{0}\"", normSrc); - normSources.Add(normSrc); - SaveExtensionLookupSources(normSources); - } + foreach (var src in cfg.Environment.Sources) { + var normSrc = src.NormalizeAsPath(); + _logger.Debug("Extension lookup source \"{@ExtensionSource}\"", normSrc); + normSources.Add(normSrc); + SaveExtensionLookupSources(normSources); } + return normSources; } @@ -389,12 +404,12 @@ public static void RegisterExtensionLookupSource(string extLookupSource) { var normSource = extLookupSource.NormalizeAsPath(); var sources = GetRegisteredExtensionLookupSources(); if (!sources.Contains(normSource)) { - logger.Debug("Registering extension lookup source \"{0}\"", normSource); + _logger.Debug("Registering extension lookup source \"{0}\"", normSource); sources.Add(normSource); SaveExtensionLookupSources(sources); } else - logger.Debug("Extension lookup source already exists. Skipping registration."); + _logger.Debug("Extension lookup source already exists. Skipping registration."); } // unregister extension lookup source @@ -403,12 +418,12 @@ public static void UnregisterExtensionLookupSource(string extLookupSource) { var normSource = extLookupSource.NormalizeAsPath(); var sources = GetRegisteredExtensionLookupSources(); if (sources.Contains(normSource)) { - logger.Debug("Unregistering extension lookup source \"{0}\"", normSource); + _logger.Debug("Unregistering extension lookup source \"{0}\"", normSource); sources.Remove(normSource); SaveExtensionLookupSources(sources); } else - logger.Debug("Extension lookup source does not exist. Skipping unregistration."); + _logger.Debug("Extension lookup source does not exist. Skipping unregistration."); } // unregister all extension lookup sources @@ -429,20 +444,20 @@ private static List LookupExtensionInDefinitionFile( string filePath = null; // determine if path is file or uri - logger.Debug("Determining file or remote source \"{0}\"", fileOrUri); + _logger.Debug("Determining file or remote source \"{0}\"", fileOrUri); Uri uriResult; var validPath = Uri.TryCreate(fileOrUri, UriKind.Absolute, out uriResult); if (validPath) { if (uriResult.IsFile) { filePath = fileOrUri; - logger.Debug("Source is a file \"{0}\"", filePath); + _logger.Debug("Source is a file \"{0}\"", filePath); } else if (uriResult.HostNameType == UriHostNameType.Dns || uriResult.HostNameType == UriHostNameType.IPv4 || uriResult.HostNameType == UriHostNameType.IPv6) { - logger.Debug("Source is a remote resource \"{0}\"", fileOrUri); - logger.Debug("Downloading remote resource \"{0}\"...", fileOrUri); + _logger.Debug("Source is a remote resource \"{0}\"", fileOrUri); + _logger.Debug("Downloading remote resource \"{0}\"...", fileOrUri); // download the resource into TEMP try { filePath = @@ -466,7 +481,7 @@ private static List LookupExtensionInDefinitionFile( // process file now if (filePath != null) { if (Path.GetExtension(filePath).ToLower() == ".json") { - logger.Debug("Parsing extension metadata file..."); + _logger.Debug("Parsing extension metadata file..."); dynamic extensionsObj; if (filePath != null) { @@ -481,10 +496,10 @@ private static List LookupExtensionInDefinitionFile( foreach (JObject extObj in extensionsObj.extensions) { var extDef = new PyRevitExtensionDefinition(extObj); - logger.Debug("Registered extension \"{0}\"", extDef.Name); + _logger.Debug("Registered extension \"{0}\"", extDef.Name); if (searchPattern != null) { if (CompareExtensionNames(extDef.Name, searchPattern)) { - logger.Debug(string.Format("\"{0}\" Matched registered extension \"{1}\"", + _logger.Debug(string.Format("\"{0}\" Matched registered extension \"{1}\"", searchPattern, extDef.Name)); pyrevtExts.Add(extDef); } @@ -507,7 +522,9 @@ private static List LookupExtensionInDefinitionFile( // save list of source exensio private static void SaveExtensionLookupSources(IEnumerable sources) { var cfg = PyRevitConfigs.GetConfigFile(); - cfg.SetValue(PyRevitConsts.EnvConfigsSectionName, PyRevitConsts.EnvConfigsExtensionLookupSourcesKey, sources); + cfg.SaveSection( + ConfigurationService.DefaultConfigurationName, + new EnvironmentSection() {Sources = sources.ToList()}); } } } diff --git a/dev/pyRevitLabs/pyRevitLabs.PyRevit/pyRevitLabs.PyRevit.csproj b/dev/pyRevitLabs/pyRevitLabs.PyRevit/pyRevitLabs.PyRevit.csproj index 5370f8149..f7617ba10 100644 --- a/dev/pyRevitLabs/pyRevitLabs.PyRevit/pyRevitLabs.PyRevit.csproj +++ b/dev/pyRevitLabs/pyRevitLabs.PyRevit/pyRevitLabs.PyRevit.csproj @@ -17,14 +17,14 @@ - - - + + + diff --git a/dev/pyRevitLabs/pyRevitLabs.sln b/dev/pyRevitLabs/pyRevitLabs.sln index 8a90951a0..55aaeff72 100644 --- a/dev/pyRevitLabs/pyRevitLabs.sln +++ b/dev/pyRevitLabs/pyRevitLabs.sln @@ -51,6 +51,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "pyRevitLabs.UnitTests", "py EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "pyRevitLabs.PyRevit.Runtime.Shared", "pyRevitLabs.PyRevit.Runtime.Shared\pyRevitLabs.PyRevit.Runtime.Shared.csproj", "{B239FF1F-F32A-4828-87B8-E5DAC6809567}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DE5976E3-62D9-4D0E-AC25-37EC56205E91}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pyRevitLabs.Configurations.Ini.Tests", "tests\pyRevitLabs.Configurations.Ini.Tests\pyRevitLabs.Configurations.Ini.Tests.csproj", "{314579D4-4AA8-4E67-B56B-D69BC26DD50F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pyRevitLabs.Configurations.Json.Tests", "tests\pyRevitLabs.Configurations.Json.Tests\pyRevitLabs.Configurations.Json.Tests.csproj", "{3DC4256D-2003-4905-A3E9-6E03B28476EF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pyRevitLabs.Configurations.Tests", "tests\pyRevitLabs.Configurations.Tests\pyRevitLabs.Configurations.Tests.csproj", "{CE552C0D-86B3-47E9-8A6F-8835A60A15B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pyRevitLabs.Configurations.Yaml.Tests", "tests\pyRevitLabs.Configurations.Yaml.Tests\pyRevitLabs.Configurations.Yaml.Tests.csproj", "{A9FA927C-8D54-4ED1-B1F7-D736A9089445}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pyRevitLabs.Configurations", "pyRevitLabs.Configurations\pyRevitLabs.Configurations.csproj", "{088A33E4-CB8B-4C39-A307-A73E3C93D673}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pyRevitLabs.Configurations.Ini", "pyRevitLabs.Configurations.Ini\pyRevitLabs.Configurations.Ini.csproj", "{EB427B0D-FCFD-423D-B95E-CAD0E6BA8532}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pyRevitLabs.Configurations.Json", "pyRevitLabs.Configurations.Json\pyRevitLabs.Configurations.Json.csproj", "{BA542A4C-B24B-4794-8D90-936D89B72F31}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "pyRevitLabs.Configurations.Yaml", "pyRevitLabs.Configurations.Yaml\pyRevitLabs.Configurations.Yaml.csproj", "{E6B0F5EC-1E4E-4BB0-8D9E-640BC3B39A57}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -113,6 +131,38 @@ Global {B239FF1F-F32A-4828-87B8-E5DAC6809567}.Debug|x64.Build.0 = Debug|x64 {B239FF1F-F32A-4828-87B8-E5DAC6809567}.Release|x64.ActiveCfg = Release|x64 {B239FF1F-F32A-4828-87B8-E5DAC6809567}.Release|x64.Build.0 = Release|x64 + {314579D4-4AA8-4E67-B56B-D69BC26DD50F}.Debug|x64.ActiveCfg = Debug|Any CPU + {314579D4-4AA8-4E67-B56B-D69BC26DD50F}.Debug|x64.Build.0 = Debug|Any CPU + {314579D4-4AA8-4E67-B56B-D69BC26DD50F}.Release|x64.ActiveCfg = Release|Any CPU + {314579D4-4AA8-4E67-B56B-D69BC26DD50F}.Release|x64.Build.0 = Release|Any CPU + {3DC4256D-2003-4905-A3E9-6E03B28476EF}.Debug|x64.ActiveCfg = Debug|Any CPU + {3DC4256D-2003-4905-A3E9-6E03B28476EF}.Debug|x64.Build.0 = Debug|Any CPU + {3DC4256D-2003-4905-A3E9-6E03B28476EF}.Release|x64.ActiveCfg = Release|Any CPU + {3DC4256D-2003-4905-A3E9-6E03B28476EF}.Release|x64.Build.0 = Release|Any CPU + {CE552C0D-86B3-47E9-8A6F-8835A60A15B7}.Debug|x64.ActiveCfg = Debug|Any CPU + {CE552C0D-86B3-47E9-8A6F-8835A60A15B7}.Debug|x64.Build.0 = Debug|Any CPU + {CE552C0D-86B3-47E9-8A6F-8835A60A15B7}.Release|x64.ActiveCfg = Release|Any CPU + {CE552C0D-86B3-47E9-8A6F-8835A60A15B7}.Release|x64.Build.0 = Release|Any CPU + {A9FA927C-8D54-4ED1-B1F7-D736A9089445}.Debug|x64.ActiveCfg = Debug|Any CPU + {A9FA927C-8D54-4ED1-B1F7-D736A9089445}.Debug|x64.Build.0 = Debug|Any CPU + {A9FA927C-8D54-4ED1-B1F7-D736A9089445}.Release|x64.ActiveCfg = Release|Any CPU + {A9FA927C-8D54-4ED1-B1F7-D736A9089445}.Release|x64.Build.0 = Release|Any CPU + {088A33E4-CB8B-4C39-A307-A73E3C93D673}.Debug|x64.ActiveCfg = Debug|Any CPU + {088A33E4-CB8B-4C39-A307-A73E3C93D673}.Debug|x64.Build.0 = Debug|Any CPU + {088A33E4-CB8B-4C39-A307-A73E3C93D673}.Release|x64.ActiveCfg = Release|Any CPU + {088A33E4-CB8B-4C39-A307-A73E3C93D673}.Release|x64.Build.0 = Release|Any CPU + {EB427B0D-FCFD-423D-B95E-CAD0E6BA8532}.Debug|x64.ActiveCfg = Debug|Any CPU + {EB427B0D-FCFD-423D-B95E-CAD0E6BA8532}.Debug|x64.Build.0 = Debug|Any CPU + {EB427B0D-FCFD-423D-B95E-CAD0E6BA8532}.Release|x64.ActiveCfg = Release|Any CPU + {EB427B0D-FCFD-423D-B95E-CAD0E6BA8532}.Release|x64.Build.0 = Release|Any CPU + {BA542A4C-B24B-4794-8D90-936D89B72F31}.Debug|x64.ActiveCfg = Debug|Any CPU + {BA542A4C-B24B-4794-8D90-936D89B72F31}.Debug|x64.Build.0 = Debug|Any CPU + {BA542A4C-B24B-4794-8D90-936D89B72F31}.Release|x64.ActiveCfg = Release|Any CPU + {BA542A4C-B24B-4794-8D90-936D89B72F31}.Release|x64.Build.0 = Release|Any CPU + {E6B0F5EC-1E4E-4BB0-8D9E-640BC3B39A57}.Debug|x64.ActiveCfg = Debug|Any CPU + {E6B0F5EC-1E4E-4BB0-8D9E-640BC3B39A57}.Debug|x64.Build.0 = Debug|Any CPU + {E6B0F5EC-1E4E-4BB0-8D9E-640BC3B39A57}.Release|x64.ActiveCfg = Release|Any CPU + {E6B0F5EC-1E4E-4BB0-8D9E-640BC3B39A57}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -120,4 +170,10 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {29169547-9C0C-4620-A37C-77CB6B30F4F0} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {314579D4-4AA8-4E67-B56B-D69BC26DD50F} = {DE5976E3-62D9-4D0E-AC25-37EC56205E91} + {3DC4256D-2003-4905-A3E9-6E03B28476EF} = {DE5976E3-62D9-4D0E-AC25-37EC56205E91} + {CE552C0D-86B3-47E9-8A6F-8835A60A15B7} = {DE5976E3-62D9-4D0E-AC25-37EC56205E91} + {A9FA927C-8D54-4ED1-B1F7-D736A9089445} = {DE5976E3-62D9-4D0E-AC25-37EC56205E91} + EndGlobalSection EndGlobal diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/IniConfigurationUnitTests.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/IniConfigurationUnitTests.cs new file mode 100644 index 000000000..812a59cdb --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/IniConfigurationUnitTests.cs @@ -0,0 +1,50 @@ +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Ini.Extensions; +using pyRevitLabs.Configurations.Tests; + +namespace pyRevitLabs.Configurations.Ini.Tests; + +public class IniConfigurationUnitTests : ConfigurationTests, IClassFixture +{ + private readonly string _configPath; + + public IniConfigurationUnitTests(IniCreateFixture iniCreateFixture) + : base(iniCreateFixture.Configuration) + { + _configPath = IniCreateFixture.ConfigPath; + } + + [Fact] + public void CreateIniConfiguration_ShouldCreate() + { + Assert.NotNull(IniConfiguration.Create(_configPath)); + } + + [Fact] + public void CreateIniConfiguration_ShouldThrowException() + { + Assert.Throws(() => IniConfiguration.Create(default!)); + } + + [Fact] + public void CreateIniConfigurationByBuilder_ShouldThrowException() + { + Assert.Throws(() => + { + new ConfigurationBuilder(false) + .AddIniConfiguration(default!, default!) + .Build(); + }); + } + + [Fact] + public void CreateIniConfigurationByNullBuilder_ShouldThrowException() + { + Assert.Throws(() => + { + IniConfigurationExtensions + .AddIniConfiguration(default!, default!, default!) + .Build(); + }); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/IniCreateFixture.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/IniCreateFixture.cs new file mode 100644 index 000000000..997e3d4a9 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/IniCreateFixture.cs @@ -0,0 +1,21 @@ +using pyRevitLabs.Configurations.Abstractions; + +namespace pyRevitLabs.Configurations.Ini.Tests +{ + public sealed class IniCreateFixture : IDisposable + { + public const string ConfigPath = "pyRevit_config.ini"; + + public IniCreateFixture() + { + Configuration = IniConfiguration.Create(ConfigPath); + } + + public IConfiguration Configuration { get; } + + public void Dispose() + { + File.Delete(ConfigPath); + } + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/pyRevitLabs.Configurations.Ini.Tests.csproj b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/pyRevitLabs.Configurations.Ini.Tests.csproj new file mode 100644 index 000000000..535928273 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Ini.Tests/pyRevitLabs.Configurations.Ini.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/JsonConfigurationUnitTests.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/JsonConfigurationUnitTests.cs new file mode 100644 index 000000000..4c74721f3 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/JsonConfigurationUnitTests.cs @@ -0,0 +1,50 @@ +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Json.Extensions; +using pyRevitLabs.Configurations.Tests; + +namespace pyRevitLabs.Configurations.Json.Tests; + +public class JsonConfigurationUnitTests : ConfigurationTests, IClassFixture +{ + private readonly string _configPath; + + public JsonConfigurationUnitTests(JsonCreateFixture createFixture) + : base(createFixture.Configuration) + { + _configPath = JsonCreateFixture.ConfigPath; + } + + [Fact] + public void CreateIniConfiguration_ShouldCreate() + { + Assert.NotNull(JsonConfiguration.Create(_configPath)); + } + + [Fact] + public void CreateJsonConfiguration_ShouldThrowException() + { + Assert.Throws(() => JsonConfiguration.Create(default!)); + } + + [Fact] + public void CreateJsonConfigurationByBuilder_ShouldThrowException() + { + Assert.Throws(() => + { + new ConfigurationBuilder(false) + .AddJsonConfiguration(default!, default!) + .Build(); + }); + } + + [Fact] + public void CreateJsonConfigurationByNullBuilder_ShouldThrowException() + { + Assert.Throws(() => + { + JsonConfigurationExtensions + .AddJsonConfiguration(default!, default!, default!) + .Build(); + }); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/JsonCreateFixture.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/JsonCreateFixture.cs new file mode 100644 index 000000000..be14f78b3 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/JsonCreateFixture.cs @@ -0,0 +1,21 @@ +using pyRevitLabs.Configurations.Abstractions; + +namespace pyRevitLabs.Configurations.Json.Tests +{ + public sealed class JsonCreateFixture : IDisposable + { + public const string ConfigPath = "pyRevit_config.json"; + + public JsonCreateFixture() + { + Configuration = JsonConfiguration.Create(ConfigPath); + } + + public IConfiguration Configuration { get; } + + public void Dispose() + { + File.Delete(ConfigPath); + } + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/pyRevitLabs.Configurations.Json.Tests.csproj b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/pyRevitLabs.Configurations.Json.Tests.csproj new file mode 100644 index 000000000..fd4377eee --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Json.Tests/pyRevitLabs.Configurations.Json.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationServiceFixture.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationServiceFixture.cs new file mode 100644 index 000000000..a3920f1c0 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationServiceFixture.cs @@ -0,0 +1,79 @@ +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using pyRevitLabs.Configurations.Abstractions; + +namespace pyRevitLabs.Configurations.Tests +{ + public sealed class ConfigurationServiceFixture : IDisposable + { + public ConfigurationServiceFixture() + { + Configuration = new ConfigurationService(false, new List(), + new Dictionary() + {{"default", new TestRunConfiguration()}}); + } + + public ConfigurationService Configuration { get; } + + public void Dispose() { } + + private class TestRunConfiguration : IConfiguration + { + public string ConfigurationPath { get; } + + public bool HasSection(string sectionName) + { + throw new NotImplementedException(); + } + + public bool HasSectionKey(string sectionName, string keyName) + { + throw new NotImplementedException(); + } + + public T GetValue(string sectionName, string keyName) + { + throw new NotImplementedException(); + } + + public T? GetValueOrDefault(string sectionName, string keyName, T? defaultValue = default) + { + throw new NotImplementedException(); + } + + public object GetValue(Type typeObject, string sectionName, string keyName) + { + throw new NotImplementedException(); + } + + public object? GetValueOrDefault(Type typeObject, string sectionName, string keyName, object? defaultValue = default) + { + throw new NotImplementedException(); + } + + public bool RemoveSection(string sectionName) + { + throw new NotImplementedException(); + } + + public bool RemoveOption(string sectionName, string keyName) + { + throw new NotImplementedException(); + } + + public void SetValue(string sectionName, string keyName, T? value) + { + throw new NotImplementedException(); + } + + public void SaveConfiguration() + { + throw new NotImplementedException(); + } + + public void SaveConfiguration(string configurationPath) + { + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationServiceUnitTests.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationServiceUnitTests.cs new file mode 100644 index 000000000..a40e63a43 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationServiceUnitTests.cs @@ -0,0 +1,14 @@ +using pyRevitLabs.Configurations.Abstractions; + +namespace pyRevitLabs.Configurations.Tests +{ + public sealed class ConfigurationServiceUnitTests : IClassFixture + { + private readonly ConfigurationServiceFixture _configurationServiceFixture; + + public ConfigurationServiceUnitTests(ConfigurationServiceFixture configurationServiceFixture) + { + _configurationServiceFixture = configurationServiceFixture; + } + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationTests.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationTests.cs new file mode 100644 index 000000000..c3d0ff885 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/ConfigurationTests.cs @@ -0,0 +1,91 @@ +using pyRevitLabs.Configurations.Abstractions; + +namespace pyRevitLabs.Configurations.Tests; + +public abstract class ConfigurationTests +{ + protected readonly IConfiguration _configuration; + + public ConfigurationTests(IConfiguration configuration) + { + _configuration = configuration; + StartUp(); + } + + private void StartUp() + { + // core + _configuration.SetValue("core", "userextensions", new List()); + _configuration.SetValue("core", "user_locale", "ru"); + _configuration.SetValue("core", "rocketmode", true); + _configuration.SetValue("core", "autoupdate", true); + _configuration.SetValue("core", "checkupdates", true); + _configuration.SetValue("core", "usercanextend", true); + _configuration.SetValue("core", "usercanconfig", true); + + // environment + _configuration.SetValue("environment", "clones", + new Dictionary() {{"master", "C:\\Users\\user\\AppData\\Roaming\\pyRevit-Master"}}); + + // pyRevitBundlesCreatorExtension.extension + _configuration.SetValue("pyRevitBundlesCreatorExtension.extension", "disabled", true); + _configuration.SetValue("pyRevitBundlesCreatorExtension.extension", "private_repo", true); + _configuration.SetValue("pyRevitBundlesCreatorExtension.extension", "username", ""); + _configuration.SetValue("pyRevitBundlesCreatorExtension.extension", "password", ""); + + // telemetry + _configuration.SetValue("telemetry", "active", true); + _configuration.SetValue("telemetry", "utc_timestamps", true); + _configuration.SetValue("telemetry", "active_app", true); + _configuration.SetValue("telemetry", "apptelemetry_event_flags", "0x4000400004003"); + _configuration.SetValue("telemetry", "apptelemetry_event_flags", "0x4000400004003"); + _configuration.SetValue("telemetry", "telemetry_server_url", "http://pyrevitlabs.io/api/v2/scripts"); + _configuration.SetValue("telemetry", "apptelemetry_server_url", "http://pyrevitlabs/api/v2/events"); + } + + [Fact] + public void NewCreateValue_ShouldReturnSameValue() + { + _configuration.SetValue("new", "create", "value"); + + string value = _configuration.GetValue("new", "create"); + + Assert.Equal("value", value); + } + + [Fact] + public void GetValueOrDefault_ShouldReturnSameValue() + { + _configuration.SetValue("create", "default", "value"); + + string? value = _configuration.GetValueOrDefault("create", "default"); + + Assert.Equal("value", value); + } + + [Fact] + public void GetValueOrDefault_ShouldReturnDefaultValue() + { + string? value = _configuration.GetValueOrDefault("not_exits", "key", "defaultValue"); + + Assert.Equal("defaultValue", value); + } + + [Fact] + public void RemoveExitsValue_ShouldReturnTrue() + { + _configuration.SetValue("remove", "default", "value"); + + bool result = _configuration.RemoveOption("remove", "default"); + + Assert.True(result); + } + + [Fact] + public void RemoveNotExitsValue_ShouldReturnFalse() + { + bool result = _configuration.RemoveOption("remove", "not-exits"); + + Assert.False(result); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/pyRevitLabs.Configurations.Tests.csproj b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/pyRevitLabs.Configurations.Tests.csproj new file mode 100644 index 000000000..a04d5ec92 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Tests/pyRevitLabs.Configurations.Tests.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/YamlConfigurationUnitTests.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/YamlConfigurationUnitTests.cs new file mode 100644 index 000000000..aa09cd270 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/YamlConfigurationUnitTests.cs @@ -0,0 +1,50 @@ +using pyRevitLabs.Configurations.Abstractions; +using pyRevitLabs.Configurations.Tests; +using pyRevitLabs.Configurations.Yaml.Extensions; + +namespace pyRevitLabs.Configurations.Yaml.Tests; + +public class YamlConfigurationUnitTests : ConfigurationTests, IClassFixture +{ + private readonly string _configPath; + + public YamlConfigurationUnitTests(YamlCreateFixture createFixture) + : base(createFixture.Configuration) + { + _configPath = YamlCreateFixture.ConfigPath; + } + + [Fact] + public void CreateYamlConfiguration_ShouldCreate() + { + Assert.NotNull(YamlConfiguration.Create(_configPath)); + } + + [Fact] + public void CreateYamlConfiguration_ShouldThrowException() + { + Assert.Throws(() => YamlConfiguration.Create(default!)); + } + + [Fact] + public void CreateYamlConfigurationByBuilder_ShouldThrowException() + { + Assert.Throws(() => + { + new ConfigurationBuilder(false) + .AddYamlConfiguration(default!, default!) + .Build(); + }); + } + + [Fact] + public void CreateYamlConfigurationByNullBuilder_ShouldThrowException() + { + Assert.Throws(() => + { + YamlConfigurationExtensions + .AddYamlConfiguration(default!, default!, default!) + .Build(); + }); + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/YamlCreateFixture.cs b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/YamlCreateFixture.cs new file mode 100644 index 000000000..592d31c20 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/YamlCreateFixture.cs @@ -0,0 +1,21 @@ +using pyRevitLabs.Configurations.Abstractions; + +namespace pyRevitLabs.Configurations.Yaml.Tests +{ + public sealed class YamlCreateFixture : IDisposable + { + public const string ConfigPath = "pyRevit_config.yml"; + + public YamlCreateFixture() + { + Configuration = YamlConfiguration.Create(ConfigPath); + } + + public IConfiguration Configuration { get; } + + public void Dispose() + { + File.Delete(ConfigPath); + } + } +} \ No newline at end of file diff --git a/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/pyRevitLabs.Configurations.Yaml.Tests.csproj b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/pyRevitLabs.Configurations.Yaml.Tests.csproj new file mode 100644 index 000000000..a9c5955d3 --- /dev/null +++ b/dev/pyRevitLabs/tests/pyRevitLabs.Configurations.Yaml.Tests/pyRevitLabs.Configurations.Yaml.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + + + diff --git a/pyrevitlib/pyrevit/coreutils/configparser.py b/pyrevitlib/pyrevit/coreutils/configparser.py index c2fc6b92d..4880f867c 100644 --- a/pyrevitlib/pyrevit/coreutils/configparser.py +++ b/pyrevitlib/pyrevit/coreutils/configparser.py @@ -1,116 +1,55 @@ """Base module for pyRevit config parsing.""" import json -import codecs -from pyrevit.compat import configparser -from pyrevit import PyRevitException, PyRevitIOError from pyrevit import coreutils +from pyrevit.labs import ConfigurationService -#pylint: disable=W0703,C0302 -KEY_VALUE_TRUE = "True" -KEY_VALUE_FALSE = "False" - -class PyRevitConfigSectionParser(object): - """Config section parser object. Handle section options.""" - def __init__(self, config_parser, section_name): - self._parser = config_parser - self._section_name = section_name +class ConfigSection(object): + def __init__(self, section_name, configuration): + self.__section_name = section_name + self.__configuration = configuration def __iter__(self): - return iter(self._parser.options(self._section_name)) + for option_name in self.__configuration.GetSectionOptionNames(self.__section_name): + yield option_name def __str__(self): - return self._section_name + return self.__section_name def __repr__(self): return '' \ - .format(id(self), self._section_name) + .format(id(self), self.__section_name) def __getattr__(self, param_name): - try: - value = self._parser.get(self._section_name, param_name) - try: - try: - return json.loads(value) #pylint: disable=W0123 - except Exception: - # try fix legacy formats - # cleanup python style true, false values - if value == KEY_VALUE_TRUE: - value = json.dumps(True) - elif value == KEY_VALUE_FALSE: - value = json.dumps(False) - # cleanup string representations - value = value.replace('\'', '"').encode('string-escape') - # try parsing again - try: - return json.loads(value) #pylint: disable=W0123 - except Exception: - # if failed again then the value is a string - # but is not encapsulated in quotes - # e.g. option = C:\Users\Desktop - value = value.strip() - if not value.startswith('(') \ - or not value.startswith('[') \ - or not value.startswith('{'): - value = "\"%s\"" % value - return json.loads(value) #pylint: disable=W0123 - except Exception: - return value - except (configparser.NoOptionError, configparser.NoSectionError): - raise AttributeError('Parameter does not exist in config file: {}' - .format(param_name)) + return self.get_option(param_name) def __setattr__(self, param_name, value): - # check agaist used attribute names - if param_name in ['_parser', '_section_name']: - super(PyRevitConfigSectionParser, self).__setattr__(param_name, - value) - else: - # if not used by this object, then set a config section - try: - return self._parser.set(self._section_name, - param_name, - json.dumps(value, - separators=(',', ':'), - ensure_ascii=False)) - except Exception as set_err: - raise PyRevitException('Error setting parameter value. ' - '| {}'.format(set_err)) + return self.set_option(param_name, value) @property def header(self): - """Section header.""" - return self._section_name + return self.__section_name @property def subheader(self): - """Section sub-header e.g. Section.SubSection.""" return coreutils.get_canonical_parts(self.header)[-1] def has_option(self, option_name): - """Check if section contains given option.""" - return self._parser.has_option(self._section_name, option_name) + return self.__configuration.HasSectionKey(self.__section_name, option_name) def get_option(self, op_name, default_value=None): - """Get option value or return default.""" - try: - return self.__getattr__(op_name) - except Exception as opt_get_err: - if default_value is not None: - return default_value - else: - raise opt_get_err + value = self.__configuration.GetValueOrDefault(op_name, "") + return json.load(value) if value else default_value def set_option(self, op_name, value): - """Set value of given option.""" - self.__setattr__(op_name, value) + self.__configuration.SetValue(self.__section_name, op_name, + json.dumps(value, separators=(',', ':'), ensure_ascii=False)) def remove_option(self, option_name): - """Remove given option from section.""" - return self._parser.remove_option(self._section_name, option_name) + return self.__configuration.RemoveOption(self.__section_name, option_name) def has_subsection(self, section_name): """Check if section has any subsections.""" @@ -123,121 +62,42 @@ def add_subsection(self, section_name): ) def get_subsections(self): - """Get all subsections.""" subsections = [] - for section_name in self._parser.sections(): + for section_name in self.__configuration.GetSectionNames(): if section_name.startswith(self._section_name + '.'): - subsec = PyRevitConfigSectionParser(self._parser, section_name) + subsec = ConfigSection(self._parser, section_name) subsections.append(subsec) + return subsections def get_subsection(self, section_name): - """Get subsection with given name.""" for subsection in self.get_subsections(): if subsection.subheader == section_name: return subsection -class PyRevitConfigParser(object): - """Config parser object. Handle config sections and io.""" - def __init__(self, cfg_file_path=None): - self._cfg_file_path = cfg_file_path - self._parser = configparser.ConfigParser() - if self._cfg_file_path: - try: - with codecs.open(self._cfg_file_path, 'r', 'utf-8') as cfg_file: - try: - self._parser.readfp(cfg_file) - except AttributeError: - self._parser.read_file(cfg_file) - except (OSError, IOError): - raise PyRevitIOError() - except Exception as read_err: - raise PyRevitException(read_err) +class ConfigSections(object): + def __init__(self, configuration_service): + self.__configuration_service = configuration_service def __iter__(self): - return iter([self.get_section(x) for x in self._parser.sections()]) + for section_name in self.__get_default_config().GetSectionNames(): + yield section_name def __getattr__(self, section_name): - if self._parser.has_section(section_name): - # build a section parser object and return - return PyRevitConfigSectionParser(self._parser, section_name) - else: - raise AttributeError( - 'Section \"{}\" does not exist in config file.' - .format(section_name)) - - def get_config_file_hash(self): - """Get calculated unique hash for this config. - - Returns: - (str): hash of the config. - """ - with codecs.open(self._cfg_file_path, 'r', 'utf-8') as cfg_file: - cfg_hash = coreutils.get_str_hash(cfg_file.read()) - - return cfg_hash + return self.get_section(section_name) def has_section(self, section_name): - """Check if config contains given section.""" - try: - self.get_section(section_name) - return True - except Exception: - return False + return self.__get_default_config().HasSection(section_name) def add_section(self, section_name): - """Add section with given name to config.""" - self._parser.add_section(section_name) - return PyRevitConfigSectionParser(self._parser, section_name) + return ConfigSection(section_name, self.__get_default_config()) def get_section(self, section_name): - """Get section with given name. - - Raises: - AttributeError: if section is missing - """ - # check is section with full name is available - if self._parser.has_section(section_name): - return PyRevitConfigSectionParser(self._parser, section_name) - - # if not try to match with section_name.subsection - # if there is a section_name.subsection defined, that should be - # the sign that the section exists - # section obj then supports getting all subsections - for cfg_section_name in self._parser.sections(): - master_section = coreutils.get_canonical_parts(cfg_section_name)[0] - if section_name == master_section: - return PyRevitConfigSectionParser(self._parser, - master_section) - - # if no match happened then raise exception - raise AttributeError('Section does not exist in config file.') + return ConfigSection(section_name, self.__get_default_config()) def remove_section(self, section_name): - """Remove section from config.""" - cfg_section = self.get_section(section_name) - for cfg_subsection in cfg_section.get_subsections(): - self._parser.remove_section(cfg_subsection.header) - self._parser.remove_section(cfg_section.header) - - def reload(self, cfg_file_path=None): - """Reload config from original or given file.""" - try: - with codecs.open(cfg_file_path \ - or self._cfg_file_path, 'r', 'utf-8') as cfg_file: - try: - self._parser.readfp(cfg_file) - except AttributeError: - self._parser.read_file(cfg_file) - except (OSError, IOError): - raise PyRevitIOError() - - def save(self, cfg_file_path=None): - """Save config to original or given file.""" - try: - with codecs.open(cfg_file_path \ - or self._cfg_file_path, 'w', 'utf-8') as cfg_file: - self._parser.write(cfg_file) - except (OSError, IOError): - raise PyRevitIOError() + self.__get_default_config().RemoveSection(section_name) + + def __get_default_config(self): + return self.__configuration_service[ConfigurationService.DefaultConfigurationName] diff --git a/pyrevitlib/pyrevit/extensions/extpackages.py b/pyrevitlib/pyrevit/extensions/extpackages.py index 10d0f4573..4e5d53ad3 100644 --- a/pyrevitlib/pyrevit/extensions/extpackages.py +++ b/pyrevitlib/pyrevit/extensions/extpackages.py @@ -244,7 +244,7 @@ def config(self): All config parameters will be saved in user config file. Returns: - (pyrevit.coreutils.configparser.PyRevitConfigSectionParser): + (pyrevit.coreutils.configparser.ConfigSection): Config section handler """ try: diff --git a/pyrevitlib/pyrevit/labs.py b/pyrevitlib/pyrevit/labs.py index 9df9d026a..05c82712a 100644 --- a/pyrevitlib/pyrevit/labs.py +++ b/pyrevitlib/pyrevit/labs.py @@ -9,7 +9,6 @@ # try loading pyrevitlabs clr.AddReference('Nett') -clr.AddReference('MadMilkman.Ini') clr.AddReference('OpenMcdf') clr.AddReference('YamlDotNet') clr.AddReference('pyRevitLabs.NLog') @@ -38,8 +37,11 @@ clr.AddReference('pyRevitLabs.TargetApps.Revit') clr.AddReference('pyRevitLabs.PyRevit') clr.AddReference('PythonStubsBuilder') + +# configurations +clr.AddReference('pyRevitLabs.Configurations') + import Nett -import MadMilkman.Ini import OpenMcdf import YamlDotNet as libyaml import pyRevitLabs.MahAppsMetro @@ -54,6 +56,9 @@ from pyRevitLabs import PyRevit from PythonStubs import PythonStubsBuilder +from pyRevitLabs import Configurations +from pyRevitLabs.Configurations import ConfigurationService + from pyrevit import coreutils from pyrevit.coreutils import logger diff --git a/pyrevitlib/pyrevit/loader/sessioninfo.py b/pyrevitlib/pyrevit/loader/sessioninfo.py index e68a30bef..0e62115ae 100644 --- a/pyrevitlib/pyrevit/loader/sessioninfo.py +++ b/pyrevitlib/pyrevit/loader/sessioninfo.py @@ -187,5 +187,3 @@ def report_env(): mlogger.info('Home Directory is: %s', HOME_DIR) mlogger.info('Session uuid is: %s', get_session_uuid()) mlogger.info('Runtime assembly is: %s', runtime.RUNTIME_ASSM_NAME) - mlogger.info('Config file is (%s): %s', - user_config.config_type, user_config.config_file) diff --git a/pyrevitlib/pyrevit/script.py b/pyrevitlib/pyrevit/script.py index 68a238d39..d3201a350 100644 --- a/pyrevitlib/pyrevit/script.py +++ b/pyrevitlib/pyrevit/script.py @@ -165,7 +165,7 @@ def get_config(section=None): it will default to the command name plus the 'config' suffix. Returns: - (pyrevit.coreutils.configparser.PyRevitConfigSectionParser): + (pyrevit.coreutils.configparser.ConfigSection): Config section parser object """ from pyrevit.userconfig import user_config diff --git a/pyrevitlib/pyrevit/userconfig.py b/pyrevitlib/pyrevit/userconfig.py index 7af641dfa..9632bea85 100644 --- a/pyrevitlib/pyrevit/userconfig.py +++ b/pyrevitlib/pyrevit/userconfig.py @@ -40,16 +40,14 @@ from pyrevit import EXEC_PARAMS, HOME_DIR, HOST_APP from pyrevit import PyRevitException from pyrevit import EXTENSIONS_DEFAULT_DIR, THIRDPARTY_EXTENSIONS_DEFAULT_DIR -from pyrevit import PYREVIT_ALLUSER_APP_DIR, PYREVIT_APP_DIR from pyrevit.compat import winreg as wr +from pyrevit.coreutils.configparser import ConfigSections from pyrevit.labs import PyRevit +from pyrevit.labs import ConfigurationService from pyrevit import coreutils -from pyrevit.coreutils import appdata -from pyrevit.coreutils import configparser from pyrevit.coreutils import logger -from pyrevit.versionmgr import upgrade DEFAULT_CSV_SEPARATOR = ',' @@ -61,16 +59,15 @@ CONSTS = PyRevit.PyRevitConsts -class PyRevitConfig(configparser.PyRevitConfigParser): +class PyRevitConfig(object): """Provide read/write access to pyRevit configuration. Args: - cfg_file_path (str): full path to config file to be used. - config_type (str): type of config file + config_service (IConfigurationService): configuration service. Examples: ```python - cfg = PyRevitConfig(cfg_file_path) + cfg = PyRevitConfig(config_service) cfg.add_section('sectionname') cfg.sectionname.property = value cfg.sectionname.get_option('property', default_value) @@ -78,16 +75,16 @@ class PyRevitConfig(configparser.PyRevitConfigParser): ``` """ - def __init__(self, cfg_file_path=None, config_type='Unknown'): - """Load settings from provided config file and setup parser.""" - # try opening and reading config file in order. - super(PyRevitConfig, self).__init__(cfg_file_path=cfg_file_path) - + def __init__(self, config_service): # set log mode on the logger module based on # user settings (overriding the defaults) + self.config_service = config_service + self.config_sections = ConfigSections(self.config_service) + self._update_env() - self._admin = config_type == 'Admin' - self.config_type = config_type + + self._admin = self.config_service.ReadOnly + self.config_type = "Admin" if self.config_service.ReadOnly else "User" def _update_env(self): # update the debug level based on user config @@ -96,13 +93,13 @@ def _update_env(self): try: # first check to see if command is not in forced debug mode if not EXEC_PARAMS.debug_mode: - if self.core.debug: + if self.core.Debug: mlogger.set_debug_mode() mlogger.debug('Debug mode is enabled in user settings.') - elif self.core.verbose: + elif self.core.Verbose: mlogger.set_verbose_mode() - logger.set_file_logging(self.core.filelogging) + logger.set_file_logging(self.core.FileLogging) except Exception as env_update_err: mlogger.debug('Error updating env variable per user config. | %s', env_update_err) @@ -110,500 +107,309 @@ def _update_env(self): @property def config_file(self): """Current config file path.""" - return self._cfg_file_path + return PyRevit.PyRevitConsts.ConfigFilePath @property def environment(self): """Environment section.""" - if not self.has_section(CONSTS.EnvConfigsSectionName): - self.add_section(CONSTS.EnvConfigsSectionName) - return self.get_section(CONSTS.EnvConfigsSectionName) + return self.config_service.Environment @property def core(self): """Core section.""" - if not self.has_section(CONSTS.ConfigsCoreSection): - self.add_section(CONSTS.ConfigsCoreSection) - return self.get_section(CONSTS.ConfigsCoreSection) + return self.config_service.Core @property def routes(self): """Routes section.""" - if not self.has_section(CONSTS.ConfigsRoutesSection): - self.add_section(CONSTS.ConfigsRoutesSection) - return self.get_section(CONSTS.ConfigsRoutesSection) + return self.config_service.Routes @property def telemetry(self): """Telemetry section.""" - if not self.has_section(CONSTS.ConfigsTelemetrySection): - self.add_section(CONSTS.ConfigsTelemetrySection) - return self.get_section(CONSTS.ConfigsTelemetrySection) + return self.config_service.Telemetry @property def bin_cache(self): """"Whether to use the cache for extensions.""" - return self.core.get_option( - CONSTS.ConfigsBinaryCacheKey, - default_value=CONSTS.ConfigsBinaryCacheDefault, - ) + return self.core.BinCache @bin_cache.setter def bin_cache(self, state): - self.core.set_option( - CONSTS.ConfigsBinaryCacheKey, - value=state - ) + self.core.BinCache = state @property def check_updates(self): """Whether to check for updates.""" - return self.core.get_option( - CONSTS.ConfigsCheckUpdatesKey, - default_value=CONSTS.ConfigsCheckUpdatesDefault, - ) + return self.core.CheckUpdates @check_updates.setter def check_updates(self, state): - self.core.set_option( - CONSTS.ConfigsCheckUpdatesKey, - value=state - ) + self.core.CheckUpdates = state @property def auto_update(self): """Whether to automatically update pyRevit.""" - return self.core.get_option( - CONSTS.ConfigsAutoUpdateKey, - default_value=CONSTS.ConfigsAutoUpdateDefault, - ) + return self.core.AutoUpdate @auto_update.setter def auto_update(self, state): - self.core.set_option( - CONSTS.ConfigsAutoUpdateKey, - value=state - ) + self.core.AutoUpdate = state @property def rocket_mode(self): """Whether to enable rocket mode.""" - return self.core.get_option( - CONSTS.ConfigsRocketModeKey, - default_value=CONSTS.ConfigsRocketModeDefault, - ) + return self.core.RocketMode @rocket_mode.setter def rocket_mode(self, state): - self.core.set_option( - CONSTS.ConfigsRocketModeKey, - value=state - ) + self.core.RocketMode = state @property def log_level(self): """Logging level.""" - if self.core.get_option( - CONSTS.ConfigsDebugKey, - default_value=CONSTS.ConfigsDebugDefault, - ): + if self.core.Debug: return PyRevit.PyRevitLogLevels.Debug - elif self.core.get_option( - CONSTS.ConfigsVerboseKey, - default_value=CONSTS.ConfigsVerboseDefault, - ): + elif self.core.Verbose: return PyRevit.PyRevitLogLevels.Verbose return PyRevit.PyRevitLogLevels.Quiet @log_level.setter def log_level(self, state): if state == PyRevit.PyRevitLogLevels.Debug: - self.core.set_option(CONSTS.ConfigsDebugKey, True) - self.core.set_option(CONSTS.ConfigsVerboseKey, True) + self.core.Debug = True + self.core.Verbose = True elif state == PyRevit.PyRevitLogLevels.Verbose: - self.core.set_option(CONSTS.ConfigsDebugKey, False) - self.core.set_option(CONSTS.ConfigsVerboseKey, True) + self.core.Debug = False + self.core.Verbose = True else: - self.core.set_option(CONSTS.ConfigsDebugKey, False) - self.core.set_option(CONSTS.ConfigsVerboseKey, False) + self.core.Debug = False + self.core.Verbose = False @property def file_logging(self): """Whether to enable file logging.""" - return self.core.get_option( - CONSTS.ConfigsFileLoggingKey, - default_value=CONSTS.ConfigsFileLoggingDefault, - ) + return self.core.FileLogging @file_logging.setter def file_logging(self, state): - self.core.set_option( - CONSTS.ConfigsFileLoggingKey, - value=state - ) + self.core.FileLogging = state @property def startuplog_timeout(self): """Timeout for the startup log.""" - return self.core.get_option( - CONSTS.ConfigsStartupLogTimeoutKey, - default_value=CONSTS.ConfigsStartupLogTimeoutDefault, - ) + return self.core.StartupLogTimeout @startuplog_timeout.setter def startuplog_timeout(self, timeout): - self.core.set_option( - CONSTS.ConfigsStartupLogTimeoutKey, - value=timeout - ) + self.core.StartupLogTimeout = timeout @property def required_host_build(self): """Host build required to run the commands.""" - return self.core.get_option( - CONSTS.ConfigsRequiredHostBuildKey, - default_value="", - ) + return self.core.RequiredHostBuild @required_host_build.setter def required_host_build(self, buildnumber): - self.core.set_option( - CONSTS.ConfigsRequiredHostBuildKey, - value=buildnumber - ) + self.core.RequiredHostBuild = buildnumber @property def min_host_drivefreespace(self): """Minimum free space for running the commands.""" - return self.core.get_option( - CONSTS.ConfigsMinDriveSpaceKey, - default_value=CONSTS.ConfigsMinDriveSpaceDefault, - ) + return self.core.MinHostDriveFreeSpace @min_host_drivefreespace.setter def min_host_drivefreespace(self, freespace): - self.core.set_option( - CONSTS.ConfigsMinDriveSpaceKey, - value=freespace - ) + self.core.MinHostDriveFreeSpace = freespace @property def load_beta(self): """Whether to load commands in beta.""" - return self.core.get_option( - CONSTS.ConfigsLoadBetaKey, - default_value=CONSTS.ConfigsLoadBetaDefault, - ) + return self.core.LoadBeta @load_beta.setter def load_beta(self, state): - self.core.set_option( - CONSTS.ConfigsLoadBetaKey, - value=state - ) + self.core.LoadBeta = state @property def cpython_engine_version(self): """CPython engine version to use.""" - return self.core.get_option( - CONSTS.ConfigsCPythonEngineKey, - default_value=CONSTS.ConfigsCPythonEngineDefault, - ) + return self.core.CpythonEngineVersion @cpython_engine_version.setter def cpython_engine_version(self, version): - self.core.set_option( - CONSTS.ConfigsCPythonEngineKey, - value=int(version) - ) + self.core.CpythonEngineVersion = int(version) @property def user_locale(self): """User locale.""" - return self.core.get_option( - CONSTS.ConfigsLocaleKey, - default_value="", - ) + return self.core.UserLocale @user_locale.setter def user_locale(self, local_code): - self.core.set_option( - CONSTS.ConfigsLocaleKey, - value=local_code - ) + self.core.UserLocale = local_code @property def output_stylesheet(self): """Stylesheet used for output.""" - return self.core.get_option( - CONSTS.ConfigsOutputStyleSheet, - default_value="", - ) + return self.core.OutputStyleSheet @output_stylesheet.setter def output_stylesheet(self, stylesheet_filepath): - if stylesheet_filepath: - self.core.set_option( - CONSTS.ConfigsOutputStyleSheet, - value=stylesheet_filepath - ) - else: - self.core.remove_option(CONSTS.ConfigsOutputStyleSheet) + self.core.OutputStyleSheet = stylesheet_filepath @property def routes_host(self): """Routes API host.""" - return self.routes.get_option( - CONSTS.ConfigsRoutesHostKey, - default_value=CONSTS.ConfigsRoutesHostDefault, - ) + return self.routes.Host @routes_host.setter def routes_host(self, routes_host): - self.routes.set_option( - CONSTS.ConfigsRoutesHostKey, - value=routes_host - ) + self.routes.Host = routes_host @property def routes_port(self): """API routes port.""" - return self.routes.get_option( - CONSTS.ConfigsRoutesPortKey, - default_value=CONSTS.ConfigsRoutesPortDefault, - ) + return self.routes.Port @routes_port.setter def routes_port(self, port): - self.routes.set_option( - CONSTS.ConfigsRoutesPortKey, - value=port - ) + self.routes.Port = port @property def load_core_api(self): """Whether to load pyRevit core api.""" - return self.routes.get_option( - CONSTS.ConfigsLoadCoreAPIKey, - default_value=CONSTS.ConfigsConfigsLoadCoreAPIDefault, - ) + return self.routes.LoadCoreApi @load_core_api.setter def load_core_api(self, state): - self.routes.set_option( - CONSTS.ConfigsLoadCoreAPIKey, - value=state - ) + self.routes.LoadCoreApi = state @property def telemetry_utc_timestamp(self): """Whether to use UTC timestamps in telemetry.""" - return self.telemetry.get_option( - CONSTS.ConfigsTelemetryUTCTimestampsKey, - default_value=CONSTS.ConfigsTelemetryUTCTimestampsDefault, - ) + return self.telemetry.TelemetryUseUtcTimeStamps @telemetry_utc_timestamp.setter def telemetry_utc_timestamp(self, state): - self.telemetry.set_option( - CONSTS.ConfigsTelemetryUTCTimestampsKey, - value=state - ) + self.telemetry.TelemetryUseUtcTimeStamps = state @property def telemetry_status(self): """Telemetry status.""" - return self.telemetry.get_option( - CONSTS.ConfigsTelemetryStatusKey, - default_value=CONSTS.ConfigsTelemetryStatusDefault, - ) + return self.telemetry.TelemetryStatus @telemetry_status.setter def telemetry_status(self, state): - self.telemetry.set_option( - CONSTS.ConfigsTelemetryStatusKey, - value=state - ) + self.telemetry.TelemetryStatus = state @property def telemetry_file_dir(self): """Telemetry file directory.""" - return self.telemetry.get_option( - CONSTS.ConfigsTelemetryFileDirKey, - default_value="", - ) + return self.telemetry.TelemetryFileDir @telemetry_file_dir.setter def telemetry_file_dir(self, filepath): - self.telemetry.set_option( - CONSTS.ConfigsTelemetryFileDirKey, - value=filepath - ) + self.telemetry.TelemetryFileDir = filepath @property def telemetry_server_url(self): """Telemetry server URL.""" - return self.telemetry.get_option( - CONSTS.ConfigsTelemetryServerUrlKey, - default_value="", - ) + return self.telemetry.TelemetryServerUrl @telemetry_server_url.setter def telemetry_server_url(self, server_url): - self.telemetry.set_option( - CONSTS.ConfigsTelemetryServerUrlKey, - value=server_url - ) + self.telemetry.TelemetryServerUrl = server_url @property def telemetry_include_hooks(self): """Whether to include hooks in telemetry.""" - return self.telemetry.get_option( - CONSTS.ConfigsTelemetryIncludeHooksKey, - default_value=CONSTS.ConfigsTelemetryIncludeHooksDefault, - ) + return self.telemetry.TelemetryIncludeHooks @telemetry_include_hooks.setter def telemetry_include_hooks(self, state): - self.telemetry.set_option( - CONSTS.ConfigsTelemetryIncludeHooksKey, - value=state - ) + self.telemetry.TelemetryIncludeHooks = state @property def apptelemetry_status(self): """Telemetry status.""" - return self.telemetry.get_option( - CONSTS.ConfigsAppTelemetryStatusKey, - default_value=CONSTS.ConfigsAppTelemetryStatusDefault, - ) + return self.telemetry.AppTelemetryStatus @apptelemetry_status.setter def apptelemetry_status(self, state): - self.telemetry.set_option( - CONSTS.ConfigsAppTelemetryStatusKey, - value=state - ) + self.telemetry.AppTelemetryStatus = state @property def apptelemetry_server_url(self): """App telemetry server URL.""" - return self.telemetry.get_option( - CONSTS.ConfigsAppTelemetryServerUrlKey, - default_value="", - ) + return self.telemetry.AppTelemetryServerUrl @apptelemetry_server_url.setter def apptelemetry_server_url(self, server_url): - self.telemetry.set_option( - CONSTS.ConfigsAppTelemetryServerUrlKey, - value=server_url - ) + self.telemetry.AppTelemetryServerUrl = server_url @property def apptelemetry_event_flags(self): """Telemetry event flags.""" - return self.telemetry.get_option( - CONSTS.ConfigsAppTelemetryEventFlagsKey, - default_value="", - ) + return str(hex(self.telemetry.AppTelemetryEventFlags)) @apptelemetry_event_flags.setter def apptelemetry_event_flags(self, flags): - self.telemetry.set_option( - CONSTS.ConfigsAppTelemetryEventFlagsKey, - value=flags - ) + self.telemetry.AppTelemetryEventFlags = int(flags, 16) @property def user_can_update(self): """Whether the user can update pyRevit repos.""" - return self.core.get_option( - CONSTS.ConfigsUserCanUpdateKey, - default_value=CONSTS.ConfigsUserCanUpdateDefault, - ) + return self.core.UserCanUpdate @user_can_update.setter def user_can_update(self, state): - self.core.set_option( - CONSTS.ConfigsUserCanUpdateKey, - value=state - ) + self.core.UserCanUpdate = state @property def user_can_extend(self): """Whether the user can manage pyRevit Extensions.""" - return self.core.get_option( - CONSTS.ConfigsUserCanExtendKey, - default_value=CONSTS.ConfigsUserCanExtendDefault, - ) + return self.core.UserCanExtend @user_can_extend.setter def user_can_extend(self, state): - self.core.set_option( - CONSTS.ConfigsUserCanExtendKey, - value=state - ) + self.core.UserCanExtend = state @property def user_can_config(self): """Whether the user can access the configuration.""" - return self.core.get_option( - CONSTS.ConfigsUserCanConfigKey, - default_value=CONSTS.ConfigsUserCanConfigDefault, - ) + return self.core.UserCanConfig @user_can_config.setter def user_can_config(self, state): - self.core.set_option( - CONSTS.ConfigsUserCanConfigKey, - value=state - ) + self.core.UserCanConfig = state @property def colorize_docs(self): """Whether to enable the document colorizer.""" - return self.core.get_option( - CONSTS.ConfigsColorizeDocsKey, - default_value=CONSTS.ConfigsColorizeDocsDefault, - ) + return self.core.ColorizeDocs @colorize_docs.setter def colorize_docs(self, state): - self.core.set_option( - CONSTS.ConfigsColorizeDocsKey, - value=state - ) + self.core.ColorizeDocs = state @property def tooltip_debug_info(self): """Whether to append debug info on tooltips.""" - return self.core.get_option( - CONSTS.ConfigsAppendTooltipExKey, - default_value=CONSTS.ConfigsAppendTooltipExDefault, - ) + return self.core.TooltipDebugInfo @tooltip_debug_info.setter def tooltip_debug_info(self, state): - self.core.set_option( - CONSTS.ConfigsAppendTooltipExKey, - value=state - ) + self.core.TooltipDebugInfo = state @property def routes_server(self): """Whether the server routes are enabled.""" - return self.routes.get_option( - CONSTS.ConfigsRoutesServerKey, - default_value=CONSTS.ConfigsRoutesServerDefault, - ) + return self.routes.Status @routes_server.setter def routes_server(self, state): - self.routes.set_option( - CONSTS.ConfigsRoutesServerKey, - value=state - ) + self.routes.Status = state @property def respect_language_direction(self): @@ -614,14 +420,6 @@ def respect_language_direction(self): def respect_language_direction(self, state): pass - def get_config_version(self): - """Return version of config file used for change detection. - - Returns: - (str): hash of the config file - """ - return self.get_config_file_hash() - def get_thirdparty_ext_root_dirs(self, include_default=True): """Return a list of external extension directories set by the user. @@ -660,11 +458,7 @@ def get_ext_root_dirs(self): def get_ext_sources(self): """Return a list of extension definition source files.""" - ext_sources = self.environment.get_option( - CONSTS.EnvConfigsExtensionLookupSourcesKey, - default_value=[], - ) - return list(set(ext_sources)) + return list(set(self.environment.Sources)) def set_thirdparty_ext_root_dirs(self, path_list): """Updates list of external extension directories in config file. @@ -677,7 +471,7 @@ def set_thirdparty_ext_root_dirs(self, path_list): raise PyRevitException("Path \"%s\" does not exist." % ext_path) try: - self.core.userextensions = \ + self.core.UserExtensions = \ [op.normpath(x) for x in path_list] except Exception as write_err: mlogger.error('Error setting list of user extension folders. | %s', @@ -741,12 +535,13 @@ def is_readonly(self): def save_changes(self): """Save user config into associated config file.""" - if not self._admin and self.config_file: - try: - super(PyRevitConfig, self).save() - except Exception as save_err: - mlogger.error('Can not save user config to: %s | %s', - self.config_file, save_err) + if not self._admin: + self.config_service.SaveSection(ConfigurationService.DefaultConfigurationName, self.core) + self.config_service.SaveSection(ConfigurationService.DefaultConfigurationName, self.routes) + self.config_service.SaveSection(ConfigurationService.DefaultConfigurationName, self.telemetry) + + # save all sections (need to dynamic section on python) + self.config_service[ConfigurationService.DefaultConfigurationName].SaveConfiguration() # adjust environment per user configurations self._update_env() @@ -764,106 +559,33 @@ def get_list_separator(): except Exception: return DEFAULT_CSV_SEPARATOR + """Default config section code""" + def __iter__(self): + return self.config_sections.__iter__() -def find_config_file(target_path): - """Find config file in target path.""" - return PyRevit.PyRevitConsts.FindConfigFileInDirectory(target_path) + def __getattr__(self, section_name): + return self.config_sections.__getattr__(section_name) + def has_section(self, section_name): + return self.config_sections.has_section(section_name) -def verify_configs(config_file_path=None): - """Create a user settings file. + def add_section(self, section_name): + return self.config_sections.add_section(section_name) - if config_file_path is not provided, configs will be in memory only - - Args: - config_file_path (str, optional): config file full name and path + def get_section(self, section_name): + return self.config_sections.get_section(section_name) - Returns: - (pyrevit.userconfig.PyRevitConfig): pyRevit config file handler - """ - if config_file_path: - mlogger.debug('Creating default config file at: %s', config_file_path) - coreutils.touch(config_file_path) + def remove_section(self, section_name): + self.config_sections.remove_section(section_name) - try: - parser = PyRevitConfig(cfg_file_path=config_file_path) - except Exception as read_err: - # can not create default user config file under appdata folder - mlogger.warning('Can not create config file under: %s | %s', - config_file_path, read_err) - parser = PyRevitConfig() - return parser +def find_config_file(target_path): + """Find config file in target path.""" + return PyRevit.PyRevitConsts.FindConfigFileInDirectory(target_path) -LOCAL_CONFIG_FILE = ADMIN_CONFIG_FILE = USER_CONFIG_FILE = CONFIG_FILE = '' user_config = None # location for default pyRevit config files -LOCAL_CONFIG_FILE = find_config_file(HOME_DIR) -ADMIN_CONFIG_FILE = find_config_file(PYREVIT_ALLUSER_APP_DIR) -USER_CONFIG_FILE = find_config_file(PYREVIT_APP_DIR) - -# decide which config file to use -# check if a config file is inside the repo. for developers config override -if LOCAL_CONFIG_FILE: - CONFIG_TYPE = 'Local' - CONFIG_FILE = LOCAL_CONFIG_FILE - -# check to see if there is any config file provided by admin -elif ADMIN_CONFIG_FILE \ - and os.access(ADMIN_CONFIG_FILE, os.W_OK) \ - and not USER_CONFIG_FILE: - # if yes, copy that and use as default - # if admin config file is writable it means it is provided - # to bootstrap the first pyRevit run - try: - CONFIG_TYPE = 'Seed' - # make a local copy if one does not exist - PyRevit.PyRevitConfigs.SetupConfig(ADMIN_CONFIG_FILE) - CONFIG_FILE = find_config_file(PYREVIT_APP_DIR) - except Exception as adminEx: - # if init operation failed, make a new config file - CONFIG_TYPE = 'New' - # setup config file name and path - CONFIG_FILE = appdata.get_universal_data_file(file_id='config', - file_ext='ini') - mlogger.warning( - 'Failed to initialize config from seed file at %s\n' - 'Using default config file', - ADMIN_CONFIG_FILE - ) - -# unless it's locked. then read that config file and set admin-mode -elif ADMIN_CONFIG_FILE \ - and not os.access(ADMIN_CONFIG_FILE, os.W_OK): - CONFIG_TYPE = 'Admin' - CONFIG_FILE = ADMIN_CONFIG_FILE - -# if a config file is available for user use that -elif USER_CONFIG_FILE: - CONFIG_TYPE = 'User' - CONFIG_FILE = USER_CONFIG_FILE - -# if nothing can be found, make a new one -else: - CONFIG_TYPE = 'New' - # setup config file name and path - CONFIG_FILE = appdata.get_universal_data_file(file_id='config', - file_ext='ini') - -mlogger.debug('Using %s config file: %s', CONFIG_TYPE, CONFIG_FILE) - -# read config, or setup default config file if not available -# this pushes reading settings at first import of this module. -try: - verify_configs(CONFIG_FILE) - user_config = PyRevitConfig(cfg_file_path=CONFIG_FILE, - config_type=CONFIG_TYPE) - upgrade.upgrade_user_config(user_config) - user_config.save_changes() -except Exception as cfg_err: - mlogger.debug('Can not read confing file at: %s | %s', - CONFIG_FILE, cfg_err) - mlogger.debug('Using configs in memory...') - user_config = verify_configs() +if not EXEC_PARAMS.doc_mode: + user_config = PyRevitConfig(PyRevit.PyRevitConfigs.GetConfigFile()) \ No newline at end of file