ConfigurationManager.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. global using static Terminal.Gui.ConfigurationManager;
  2. global using CM = Terminal.Gui.ConfigurationManager;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Reflection;
  10. using System.Text;
  11. using System.Text.Json;
  12. using System.Text.Json.Serialization;
  13. #nullable enable
  14. namespace Terminal.Gui;
  15. /// <summary>
  16. /// <para>
  17. /// Classes should implement this interface on any class that should be notified when the configuration changes.
  18. /// This is an alternative to using the <see cref="ConfigurationManager.Updated"/> and
  19. /// <see cref="ConfigurationManager.Applied"/> events.
  20. /// <para>
  21. /// When the configuration has changed, the <see cref="ConfigurationManager"/> will call the <see cref="OnUpdated"/>
  22. /// method. When the configuration is being applied, the <see cref="OnApplied"/> method will be called.
  23. ///
  24. /// </para>
  25. /// </summary>
  26. public interface ISupportConfigChanges {
  27. /// <summary>
  28. /// Called when the configuration has been updated.
  29. /// </summary>
  30. public void OnUpdated ();
  31. /// <summary>
  32. /// Called when the configuration is being applied.
  33. /// </summary>
  34. public void OnApplied ();
  35. }
  36. /// <summary>
  37. /// Provides settings and configuration management for Terminal.Gui applications.
  38. /// <para>
  39. /// Users can set Terminal.Gui settings on a global or per-application basis by providing JSON formatted configuration files.
  40. /// The configuration files can be placed in at <c>.tui</c> folder in the user's home directory (e.g. <c>C:/Users/username/.tui</c>,
  41. /// or <c>/usr/username/.tui</c>),
  42. /// the folder where the Terminal.Gui application was launched from (e.g. <c>./.tui</c>), or as a resource
  43. /// within the Terminal.Gui application's main assembly.
  44. /// </para>
  45. /// <para>
  46. /// Settings are defined in JSON format, according to this schema:
  47. /// https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
  48. /// </para>
  49. /// <para>
  50. /// Settings that will apply to all applications (global settings) reside in files named <c>config.json</c>. Settings
  51. /// that will apply to a specific Terminal.Gui application reside in files named <c>appname.config.json</c>,
  52. /// where <c>appname</c> is the assembly name of the application (e.g. <c>UICatalog.config.json</c>).
  53. /// </para>
  54. /// Settings are applied using the following precedence (higher precedence settings
  55. /// overwrite lower precedence settings):
  56. /// <para>
  57. /// 1. Application configuration found in the users's home directory (<c>~/.tui/appname.config.json</c>) -- Highest precedence
  58. /// </para>
  59. /// <para>
  60. /// 2. Application configuration found in the directory the app was launched from (<c>./.tui/appname.config.json</c>).
  61. /// </para>
  62. /// <para>
  63. /// 3. Application configuration found in the applications's resources (<c>Resources/config.json</c>).
  64. /// </para>
  65. /// <para>
  66. /// 4. Global configuration found in the user's home directory (<c>~/.tui/config.json</c>).
  67. /// </para>
  68. /// <para>
  69. /// 5. Global configuration found in the directory the app was launched from (<c>./.tui/config.json</c>).
  70. /// </para>
  71. /// <para>
  72. /// 6. Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
  73. /// </para>
  74. /// </summary>
  75. public static partial class ConfigurationManager {
  76. private static readonly string _configFilename = "config.json";
  77. internal static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions {
  78. ReadCommentHandling = JsonCommentHandling.Skip,
  79. PropertyNameCaseInsensitive = true,
  80. DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
  81. WriteIndented = true,
  82. Converters = {
  83. // We override the standard Rune converter to support specifying Glyphs in
  84. // a flexible way
  85. new RuneJsonConverter(),
  86. },
  87. };
  88. /// <summary>
  89. /// A dictionary of all properties in the Terminal.Gui project that are decorated with the <see cref="SerializableConfigurationProperty"/> attribute.
  90. /// The keys are the property names pre-pended with the class that implements the property (e.g. <c>Application.UseSystemConsole</c>).
  91. /// The values are instances of <see cref="ConfigProperty"/> which hold the property's value and the
  92. /// <see cref="PropertyInfo"/> that allows <see cref="ConfigurationManager"/> to get and set the property's value.
  93. /// </summary>
  94. /// <remarks>
  95. /// Is <see langword="null"/> until <see cref="Initialize"/> is called.
  96. /// </remarks>
  97. internal static Dictionary<string, ConfigProperty>? _allConfigProperties;
  98. /// <summary>
  99. /// The backing property for <see cref="Settings"/>.
  100. /// </summary>
  101. /// <remarks>
  102. /// Is <see langword="null"/> until <see cref="Reset"/> is called. Gets set to a new instance by
  103. /// deserialization (see <see cref="Load"/>).
  104. /// </remarks>
  105. private static SettingsScope? _settings;
  106. /// <summary>
  107. /// The root object of Terminal.Gui configuration settings / JSON schema. Contains only properties with the <see cref="SettingsScope"/>
  108. /// attribute value.
  109. /// </summary>
  110. public static SettingsScope? Settings {
  111. get {
  112. if (_settings == null) {
  113. throw new InvalidOperationException ("ConfigurationManager has not been initialized. Call ConfigurationManager.Reset() before accessing the Settings property.");
  114. }
  115. return _settings;
  116. }
  117. set {
  118. _settings = value!;
  119. }
  120. }
  121. /// <summary>
  122. /// The root object of Terminal.Gui themes manager. Contains only properties with the <see cref="ThemeScope"/>
  123. /// attribute value.
  124. /// </summary>
  125. public static ThemeManager? Themes => ThemeManager.Instance;
  126. /// <summary>
  127. /// Application-specific configuration settings scope.
  128. /// </summary>
  129. [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true), JsonPropertyName ("AppSettings")]
  130. public static AppScope? AppSettings { get; set; }
  131. /// <summary>
  132. /// The set of glyphs used to draw checkboxes, lines, borders, etc...See also <seealso cref="Terminal.Gui.GlyphDefinitions"/>.
  133. /// </summary>
  134. [SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true),
  135. JsonPropertyName ("Glyphs")]
  136. public static GlyphDefinitions Glyphs { get; set; } = new GlyphDefinitions ();
  137. /// <summary>
  138. /// Initializes the internal state of ConfigurationManager. Nominally called once as part of application
  139. /// startup to initialize global state. Also called from some Unit Tests to ensure correctness (e.g. Reset()).
  140. /// </summary>
  141. internal static void Initialize ()
  142. {
  143. _allConfigProperties = new Dictionary<string, ConfigProperty> ();
  144. _settings = null;
  145. Dictionary<string, Type> classesWithConfigProps = new Dictionary<string, Type> (StringComparer.InvariantCultureIgnoreCase);
  146. // Get Terminal.Gui.dll classes
  147. var types = from assembly in AppDomain.CurrentDomain.GetAssemblies ()
  148. from type in assembly.GetTypes ()
  149. where type.GetProperties ().Any (prop => prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) != null)
  150. select type;
  151. foreach (var classWithConfig in types) {
  152. classesWithConfigProps.Add (classWithConfig.Name, classWithConfig);
  153. }
  154. Debug.WriteLine ($"ConfigManager.getConfigProperties found {classesWithConfigProps.Count} classes:");
  155. classesWithConfigProps.ToList ().ForEach (x => Debug.WriteLine ($" Class: {x.Key}"));
  156. foreach (var p in from c in classesWithConfigProps
  157. let props = c.Value.GetProperties (BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).Where (prop =>
  158. prop.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty)
  159. let enumerable = props
  160. from p in enumerable
  161. select p) {
  162. if (p.GetCustomAttribute (typeof (SerializableConfigurationProperty)) is SerializableConfigurationProperty scp) {
  163. if (p.GetGetMethod (true)!.IsStatic) {
  164. // If the class name is omitted, JsonPropertyName is allowed.
  165. _allConfigProperties!.Add (scp.OmitClassName ? ConfigProperty.GetJsonPropertyName (p) : $"{p.DeclaringType?.Name}.{p.Name}", new ConfigProperty {
  166. PropertyInfo = p,
  167. PropertyValue = null
  168. });
  169. } else {
  170. throw new Exception ($"Property {p.Name} in class {p.DeclaringType?.Name} is not static. All SerializableConfigurationProperty properties must be static.");
  171. }
  172. }
  173. }
  174. _allConfigProperties = _allConfigProperties!.OrderBy (x => x.Key).ToDictionary (x => x.Key, x => x.Value, StringComparer.InvariantCultureIgnoreCase);
  175. Debug.WriteLine ($"ConfigManager.Initialize found {_allConfigProperties.Count} properties:");
  176. //_allConfigProperties.ToList ().ForEach (x => Debug.WriteLine ($" Property: {x.Key}"));
  177. AppSettings = new AppScope ();
  178. }
  179. /// <summary>
  180. /// Creates a JSON document with the configuration specified.
  181. /// </summary>
  182. /// <returns></returns>
  183. internal static string ToJson ()
  184. {
  185. Debug.WriteLine ($"ConfigurationManager.ToJson()");
  186. return JsonSerializer.Serialize<SettingsScope> (Settings!, _serializerOptions);
  187. }
  188. internal static Stream ToStream ()
  189. {
  190. var json = JsonSerializer.Serialize<SettingsScope> (Settings!, _serializerOptions);
  191. // turn it into a stream
  192. var stream = new MemoryStream ();
  193. var writer = new StreamWriter (stream);
  194. writer.Write (json);
  195. writer.Flush ();
  196. stream.Position = 0;
  197. return stream;
  198. }
  199. /// <summary>
  200. /// Gets or sets whether the <see cref="ConfigurationManager"/> should throw an exception if it encounters
  201. /// an error on deserialization. If <see langword="false"/> (the default), the error is logged and printed to the
  202. /// console when <see cref="Application.Shutdown"/> is called.
  203. /// </summary>
  204. [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
  205. public static bool? ThrowOnJsonErrors { get; set; } = false;
  206. internal static StringBuilder jsonErrors = new StringBuilder ();
  207. internal static void AddJsonError (string error)
  208. {
  209. Debug.WriteLine ($"ConfigurationManager: {error}");
  210. jsonErrors.AppendLine (error);
  211. }
  212. /// <summary>
  213. /// Prints any Json deserialization errors that occurred during deserialization to the console.
  214. /// </summary>
  215. public static void PrintJsonErrors ()
  216. {
  217. if (jsonErrors.Length > 0) {
  218. Console.WriteLine ($"Terminal.Gui ConfigurationManager encountered the following errors while deserializing configuration files:");
  219. Console.WriteLine (jsonErrors.ToString ());
  220. }
  221. }
  222. private static void ClearJsonErrors ()
  223. {
  224. jsonErrors.Clear ();
  225. }
  226. /// <summary>
  227. /// Called when the configuration has been updated from a configuration file. Invokes the <see cref="Updated"/>
  228. /// event.
  229. /// </summary>
  230. public static void OnUpdated ()
  231. {
  232. Debug.WriteLine ($"ConfigurationManager.OnApplied()");
  233. Updated?.Invoke (null, new ConfigurationManagerEventArgs ());
  234. }
  235. /// <summary>
  236. /// Event fired when the configuration has been updated from a configuration source.
  237. /// application.
  238. /// </summary>
  239. public static event EventHandler<ConfigurationManagerEventArgs>? Updated;
  240. /// <summary>
  241. /// Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session
  242. /// (e.g. in <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/> starts. Called by <see cref="Load"/>
  243. /// if the <c>reset</c> parameter is <see langword="true"/>.
  244. /// </summary>
  245. /// <remarks>
  246. ///
  247. /// </remarks>
  248. public static void Reset ()
  249. {
  250. Debug.WriteLine ($"ConfigurationManager.Reset()");
  251. if (_allConfigProperties == null) {
  252. ConfigurationManager.Initialize ();
  253. }
  254. ClearJsonErrors ();
  255. Settings = new SettingsScope ();
  256. ThemeManager.Reset ();
  257. AppSettings = new AppScope ();
  258. // To enable some unit tests, we only load from resources if the flag is set
  259. if (Locations.HasFlag (ConfigLocations.DefaultOnly)) {
  260. Settings.UpdateFromResource (typeof (ConfigurationManager).Assembly, $"Terminal.Gui.Resources.{_configFilename}");
  261. }
  262. Apply ();
  263. ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply ();
  264. AppSettings?.Apply ();
  265. }
  266. /// <summary>
  267. /// Retrieves the hard coded default settings from the Terminal.Gui library implementation. Used in development of
  268. /// the library to generate the default configuration file. Before calling Application.Init, make sure
  269. /// <see cref="Locations"/> is set to <see cref="ConfigLocations.None"/>.
  270. /// </summary>
  271. /// <remarks>
  272. /// <para>
  273. /// This method is only really useful when using ConfigurationManagerTests
  274. /// to generate the JSON doc that is embedded into Terminal.Gui (during development).
  275. /// </para>
  276. /// <para>
  277. /// WARNING: The <c>Terminal.Gui.Resources.config.json</c> resource has setting definitions (Themes)
  278. /// that are NOT generated by this function. If you use this function to regenerate <c>Terminal.Gui.Resources.config.json</c>,
  279. /// make sure you copy the Theme definitions from the existing <c>Terminal.Gui.Resources.config.json</c> file.
  280. /// </para>
  281. /// </remarks>
  282. internal static void GetHardCodedDefaults ()
  283. {
  284. if (_allConfigProperties == null) {
  285. throw new InvalidOperationException ("Initialize must be called first.");
  286. }
  287. Settings = new SettingsScope ();
  288. ThemeManager.GetHardCodedDefaults ();
  289. AppSettings?.RetrieveValues ();
  290. foreach (var p in Settings!.Where (cp => cp.Value.PropertyInfo != null)) {
  291. Settings! [p.Key].PropertyValue = p.Value.PropertyInfo?.GetValue (null);
  292. }
  293. }
  294. /// <summary>
  295. /// Applies the configuration settings to the running <see cref="Application"/> instance.
  296. /// </summary>
  297. public static void Apply ()
  298. {
  299. bool settings = Settings?.Apply () ?? false;
  300. bool themes = !string.IsNullOrEmpty (ThemeManager.SelectedTheme) && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
  301. bool appsettings = AppSettings?.Apply () ?? false;
  302. if (settings || themes || appsettings) {
  303. OnApplied ();
  304. }
  305. }
  306. /// <summary>
  307. /// Called when an updated configuration has been applied to the
  308. /// application. Fires the <see cref="Applied"/> event.
  309. /// </summary>
  310. public static void OnApplied ()
  311. {
  312. Debug.WriteLine ($"ConfigurationManager.OnApplied()");
  313. Applied?.Invoke (null, new ConfigurationManagerEventArgs ());
  314. // TODO: Refactor ConfigurationManager to not use an event handler for this.
  315. // Instead, have it call a method on any class appropriately attributed
  316. // to update the cached values. See Issue #2871
  317. }
  318. /// <summary>
  319. /// Event fired when an updated configuration has been applied to the
  320. /// application.
  321. /// </summary>
  322. public static event EventHandler<ConfigurationManagerEventArgs>? Applied;
  323. /// <summary>
  324. /// Name of the running application. By default this property is set to the application's assembly name.
  325. /// </summary>
  326. public static string AppName { get; set; } = Assembly.GetEntryAssembly ()?.FullName?.Split (',') [0]?.Trim ()!;
  327. /// <summary>
  328. /// Describes the location of the configuration files. The constants can be
  329. /// combined (bitwise) to specify multiple locations.
  330. /// </summary>
  331. [Flags]
  332. public enum ConfigLocations {
  333. /// <summary>
  334. /// No configuration will be loaded.
  335. /// </summary>
  336. /// <remarks>
  337. /// Used for development and testing only. For Terminal,Gui to function properly, at least
  338. /// <see cref="DefaultOnly"/> should be set.
  339. /// </remarks>
  340. None = 0,
  341. /// <summary>
  342. /// Global configuration in <c>Terminal.Gui.dll</c>'s resources (<c>Terminal.Gui.Resources.config.json</c>) -- Lowest Precidence.
  343. /// </summary>
  344. DefaultOnly,
  345. /// <summary>
  346. /// This constant is a combination of all locations
  347. /// </summary>
  348. All = -1
  349. }
  350. /// <summary>
  351. /// Gets and sets the locations where <see cref="ConfigurationManager"/> will look for config files.
  352. /// The value is <see cref="ConfigLocations.All"/>.
  353. /// </summary>
  354. public static ConfigLocations Locations { get; set; } = ConfigLocations.All;
  355. /// <summary>
  356. /// Loads all settings found in the various configuration storage locations to
  357. /// the <see cref="ConfigurationManager"/>. Optionally,
  358. /// resets all settings attributed with <see cref="SerializableConfigurationProperty"/> to the defaults.
  359. /// </summary>
  360. /// <remarks>
  361. /// Use <see cref="Apply"/> to cause the loaded settings to be applied to the running application.
  362. /// </remarks>
  363. /// <param name="reset">If <see langword="true"/> the state of <see cref="ConfigurationManager"/> will
  364. /// be reset to the defaults.</param>
  365. public static void Load (bool reset = false)
  366. {
  367. Debug.WriteLine ($"ConfigurationManager.Load()");
  368. if (reset) Reset ();
  369. // LibraryResources is always loaded by Reset
  370. if (Locations == ConfigLocations.All) {
  371. var embeddedStylesResourceName = Assembly.GetEntryAssembly ()?
  372. .GetManifestResourceNames ().FirstOrDefault (x => x.EndsWith (_configFilename));
  373. if (string.IsNullOrEmpty (embeddedStylesResourceName)) {
  374. embeddedStylesResourceName = _configFilename;
  375. }
  376. Settings = Settings?
  377. // Global current directory
  378. .Update ($"./.tui/{_configFilename}")?
  379. // Global home directory
  380. .Update ($"~/.tui/{_configFilename}")?
  381. // App resources
  382. .UpdateFromResource (Assembly.GetEntryAssembly ()!, embeddedStylesResourceName!)?
  383. // App current directory
  384. .Update ($"./.tui/{AppName}.{_configFilename}")?
  385. // App home directory
  386. .Update ($"~/.tui/{AppName}.{_configFilename}");
  387. }
  388. }
  389. /// <summary>
  390. /// Returns an empty Json document with just the $schema tag.
  391. /// </summary>
  392. /// <returns></returns>
  393. public static string GetEmptyJson ()
  394. {
  395. var emptyScope = new SettingsScope ();
  396. emptyScope.Clear ();
  397. return JsonSerializer.Serialize<SettingsScope> (emptyScope, _serializerOptions);
  398. }
  399. /// <summary>
  400. /// System.Text.Json does not support copying a deserialized object to an existing instance.
  401. /// To work around this, we implement a 'deep, memberwise copy' method.
  402. /// </summary>
  403. /// <remarks>
  404. /// TOOD: When System.Text.Json implements `PopulateObject` revisit
  405. /// https://github.com/dotnet/corefx/issues/37627
  406. /// </remarks>
  407. /// <param name="source"></param>
  408. /// <param name="destination"></param>
  409. /// <returns><paramref name="destination"/> updated from <paramref name="source"/></returns>
  410. internal static object? DeepMemberwiseCopy (object? source, object? destination)
  411. {
  412. if (destination == null) {
  413. throw new ArgumentNullException (nameof (destination));
  414. }
  415. if (source == null) {
  416. return null!;
  417. }
  418. if (source.GetType () == typeof (SettingsScope)) {
  419. return ((SettingsScope)destination).Update ((SettingsScope)source);
  420. }
  421. if (source.GetType () == typeof (ThemeScope)) {
  422. return ((ThemeScope)destination).Update ((ThemeScope)source);
  423. }
  424. if (source.GetType () == typeof (AppScope)) {
  425. return ((AppScope)destination).Update ((AppScope)source);
  426. }
  427. // If value type, just use copy constructor.
  428. if (source.GetType ().IsValueType || source.GetType () == typeof (string)) {
  429. return source;
  430. }
  431. // Dictionary
  432. if (source.GetType ().IsGenericType && source.GetType ().GetGenericTypeDefinition ().IsAssignableFrom (typeof (Dictionary<,>))) {
  433. foreach (var srcKey in ((IDictionary)source).Keys) {
  434. if (((IDictionary)destination).Contains (srcKey))
  435. ((IDictionary)destination) [srcKey] = DeepMemberwiseCopy (((IDictionary)source) [srcKey], ((IDictionary)destination) [srcKey]);
  436. else {
  437. ((IDictionary)destination).Add (srcKey, ((IDictionary)source) [srcKey]);
  438. }
  439. }
  440. return destination;
  441. }
  442. // ALl other object types
  443. var sourceProps = source?.GetType ().GetProperties ().Where (x => x.CanRead).ToList ();
  444. var destProps = destination?.GetType ().GetProperties ().Where (x => x.CanWrite).ToList ()!;
  445. foreach (var (sourceProp, destProp) in
  446. from sourceProp in sourceProps
  447. where destProps.Any (x => x.Name == sourceProp.Name)
  448. let destProp = destProps.First (x => x.Name == sourceProp.Name)
  449. where destProp.CanWrite
  450. select (sourceProp, destProp)) {
  451. var sourceVal = sourceProp.GetValue (source);
  452. var destVal = destProp.GetValue (destination);
  453. if (sourceVal != null) {
  454. if (destVal != null) {
  455. // Recurse
  456. destProp.SetValue (destination, DeepMemberwiseCopy (sourceVal, destVal));
  457. } else {
  458. destProp.SetValue (destination, sourceVal);
  459. }
  460. }
  461. }
  462. return destination!;
  463. }
  464. //public class ConfiguraitonLocation
  465. //{
  466. // public string Name { get; set; } = string.Empty;
  467. // public string? Path { get; set; }
  468. // public async Task<SettingsScope> UpdateAsync (Stream stream)
  469. // {
  470. // var scope = await JsonSerializer.DeserializeAsync<SettingsScope> (stream, serializerOptions);
  471. // if (scope != null) {
  472. // ConfigurationManager.Settings?.UpdateFrom (scope);
  473. // return scope;
  474. // }
  475. // return new SettingsScope ();
  476. // }
  477. //}
  478. //public class StreamConfiguration {
  479. // private bool _reset;
  480. // public StreamConfiguration (bool reset)
  481. // {
  482. // _reset = reset;
  483. // }
  484. // public StreamConfiguration UpdateAppResources ()
  485. // {
  486. // if (Locations.HasFlag (ConfigLocations.AppResources)) LoadAppResources ();
  487. // return this;
  488. // }
  489. // public StreamConfiguration UpdateAppDirectory ()
  490. // {
  491. // if (Locations.HasFlag (ConfigLocations.AppDirectory)) LoadAppDirectory ();
  492. // return this;
  493. // }
  494. // // Additional update methods for each location here
  495. // private void LoadAppResources ()
  496. // {
  497. // // Load AppResources logic here
  498. // }
  499. // private void LoadAppDirectory ()
  500. // {
  501. // // Load AppDirectory logic here
  502. // }
  503. //}
  504. }