1 // 2 // Copyright (c) 2010-2022 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 using Nini.Config; 9 using System.IO; 10 using System; 11 using Antmicro.Renode.Logging; 12 using System.Collections.Generic; 13 using System.Linq; 14 using Antmicro.Renode.Exceptions; 15 16 namespace Antmicro.Renode.Utilities 17 { 18 public sealed class ConfigurationManager 19 { ConfigurationManager()20 static ConfigurationManager() 21 { 22 Initialize(Path.Combine(Emulator.UserDirectoryPath, "config")); 23 } 24 25 public static ConfigurationManager Instance { get; private set; } 26 Initialize(string configFile)27 public static void Initialize(string configFile) 28 { 29 Instance = new ConfigurationManager(configFile); 30 } 31 ConfigurationManager(string configFile)32 private ConfigurationManager(string configFile) 33 { 34 Config = new ConfigSource(configFile); 35 } 36 Get(string group, string name, T defaultValue, Func<T, bool> validation = null)37 public T Get<T>(string group, string name, T defaultValue, Func<T, bool> validation = null) 38 { 39 T result; 40 if(!TryFindInCache(group, name, out result)) 41 { 42 var config = VerifyValue(group, name, defaultValue); 43 try 44 { 45 if(typeof(T) == typeof(int)) 46 { 47 result = (T)(object)config.GetInt(name); 48 } 49 else if(typeof(T) == typeof(string)) 50 { 51 result = (T)(object)config.GetString(name); 52 } 53 else if(typeof(T) == typeof(bool)) 54 { 55 result = (T)(object)config.GetBoolean(name); 56 } 57 else if(typeof(T).IsEnum) 58 { 59 var value = Get<string>(group, name, defaultValue.ToString()); 60 if(!Enum.IsDefined(typeof(T), value)) 61 { 62 throw new ConfigurationException(String.Format("Could not apply value '{0}' for type {1}. Verify your configuration file {5} in section {2}->{3}. Available options are: {4}.", 63 value, typeof(T).Name, group, name, Enum.GetNames(typeof(T)).Aggregate((x, y) => x + ", " + y), Config.FileName)); 64 } 65 result = (T)Enum.Parse(typeof(T), value); 66 } 67 else 68 { 69 throw new ConfigurationException("Unsupported type: " + typeof(T)); 70 } 71 AddToCache(group, name, result); 72 } 73 catch(FormatException) 74 { 75 throw new ConfigurationException(String.Format("Field {0}->{1} is not of type {2}.", group, name, typeof(T).Name)); 76 } 77 } 78 if(validation != null && !validation(result)) 79 { 80 throw new ConfigurationException(String.Format("Value '{0}' is not valid for entry in section {1}->{2}.", result.ToString(), group, name)); 81 } 82 return result; 83 } 84 SetNonPersistent(string group, string name, T value)85 public void SetNonPersistent<T>(string group, string name, T value) 86 { 87 AddToCache(group, name, value); 88 } 89 Set(string group, string name, T value)90 public void Set<T>(string group, string name, T value) 91 { 92 var config = VerifyValue(group, name, value); 93 AddToCache(group, name, value); 94 config.Set(name, value); 95 } 96 97 public string FilePath => Config.FileName; 98 VerifyValue(string group, string name, object defaultValue)99 private IConfig VerifyValue(string group, string name, object defaultValue) 100 { 101 if(defaultValue == null) 102 { 103 throw new ArgumentException("Default value cannot be null", "defaultValue"); 104 } 105 var config = VerifyGroup(group); 106 if(!config.Contains(name)) 107 { 108 config.Set(name, defaultValue); 109 } 110 return config; 111 } 112 VerifyGroup(string group)113 private IConfig VerifyGroup(string group) 114 { 115 var config = Config.Source.Configs[group]; 116 return config ?? Config.Source.AddConfig(group); 117 } 118 AddToCache(string group, string name, T value)119 private void AddToCache<T>(string group, string name, T value) 120 { 121 cachedValues[Tuple.Create(group, name)] = value; 122 } 123 TryFindInCache(string group, string name, out T value)124 private bool TryFindInCache<T>(string group, string name, out T value) 125 { 126 value = default(T); 127 object obj; 128 var result = cachedValues.TryGetValue(Tuple.Create(group, name), out obj); 129 if(result) 130 { 131 value = (T)obj; 132 } 133 return result; 134 } 135 136 private readonly Dictionary<Tuple<string, string>, object> cachedValues = new Dictionary<Tuple<string, string>, object>(); 137 138 private readonly ConfigSource Config; 139 } 140 141 public class ConfigSource 142 { ConfigSource(string filePath)143 public ConfigSource(string filePath) 144 { 145 FileName = filePath; 146 } 147 148 public IConfigSource Source 149 { 150 get 151 { 152 if(source == null) 153 { 154 using(var locker = new FileLocker(FileName + ConfigurationLockSuffix)) 155 { 156 if(File.Exists(FileName)) 157 { 158 try 159 { 160 source = new IniConfigSource(FileName); 161 } 162 catch(Exception) 163 { 164 Logger.Log(LogLevel.Warning, "Configuration file {0} exists, but it cannot be read.", FileName); 165 } 166 } 167 else 168 { 169 source = new IniConfigSource(); 170 source.Save(FileName); 171 } 172 } 173 } 174 source.AutoSave = !Emulator.InCIMode; 175 return source; 176 } 177 178 } 179 180 public string FileName { get; private set; } 181 182 private IniConfigSource source; 183 184 private const string ConfigurationLockSuffix = ".lock"; 185 } 186 } 187 188