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