Improved configuration management

This commit is contained in:
Robert McRackan 2019-12-17 10:09:33 -05:00
parent 6c757773f7
commit 4994684690
3 changed files with 90 additions and 77 deletions

View File

@ -47,15 +47,15 @@ namespace FileManager
[Description("Your user-specific key used to decrypt your audible files (*.aax) into audio files you can use anywhere (*.m4b). Leave alone in most cases")] [Description("Your user-specific key used to decrypt your audible files (*.aax) into audio files you can use anywhere (*.m4b). Leave alone in most cases")]
public string DecryptKey public string DecryptKey
{ {
get => persistentDictionary[nameof(DecryptKey)]; get => persistentDictionary.GetString(nameof(DecryptKey));
set => persistentDictionary[nameof(DecryptKey)] = value; set => persistentDictionary.Set(nameof(DecryptKey), value);
} }
[Description("Location for book storage. Includes destination of newly liberated books")] [Description("Location for book storage. Includes destination of newly liberated books")]
public string Books public string Books
{ {
get => persistentDictionary[nameof(Books)]; get => persistentDictionary.GetString(nameof(Books));
set => persistentDictionary[nameof(Books)] = value; set => persistentDictionary.Set(nameof(Books), value);
} }
private const string APP_DIR = "AppDir"; private const string APP_DIR = "AppDir";
@ -79,22 +79,22 @@ namespace FileManager
[Description("Temporary location of files while they're in process of being downloaded.\r\nWhen download is complete, the final file will be in [LibationFiles]\\DownloadsFinal")] [Description("Temporary location of files while they're in process of being downloaded.\r\nWhen download is complete, the final file will be in [LibationFiles]\\DownloadsFinal")]
public string DownloadsInProgressEnum public string DownloadsInProgressEnum
{ {
get => persistentDictionary[nameof(DownloadsInProgressEnum)]; get => persistentDictionary.GetString(nameof(DownloadsInProgressEnum));
set => persistentDictionary[nameof(DownloadsInProgressEnum)] = value; set => persistentDictionary.Set(nameof(DownloadsInProgressEnum), value);
} }
// temp/working dir(s) should be outside of dropbox // temp/working dir(s) should be outside of dropbox
[Description("Temporary location of files while they're in process of being decrypted.\r\nWhen decryption is complete, the final file will be in Books location")] [Description("Temporary location of files while they're in process of being decrypted.\r\nWhen decryption is complete, the final file will be in Books location")]
public string DecryptInProgressEnum public string DecryptInProgressEnum
{ {
get => persistentDictionary[nameof(DecryptInProgressEnum)]; get => persistentDictionary.GetString(nameof(DecryptInProgressEnum));
set => persistentDictionary[nameof(DecryptInProgressEnum)] = value; set => persistentDictionary.Set(nameof(DecryptInProgressEnum), value);
} }
public string LocaleCountryCode public string LocaleCountryCode
{ {
get => persistentDictionary[nameof(LocaleCountryCode)]; get => persistentDictionary.GetString(nameof(LocaleCountryCode));
set => persistentDictionary[nameof(LocaleCountryCode)] = value; set => persistentDictionary.Set(nameof(LocaleCountryCode), value);
} }
// note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app // note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app
@ -123,7 +123,6 @@ namespace FileManager
// load json values into memory. create if not exists // load json values into memory. create if not exists
persistentDictionary = new PersistentDictionary(SettingsJsonPath); persistentDictionary = new PersistentDictionary(SettingsJsonPath);
persistentDictionary.EnsureEntries<Configuration>();
return value; return value;
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace FileManager namespace FileManager
{ {
@ -10,78 +11,87 @@ namespace FileManager
{ {
public string Filepath { get; } public string Filepath { get; }
// forgiving -- doesn't drop settings. old entries will continue to be persisted even if not publicly visible // optimize for strings. expectation is most settings will be strings and a rare exception will be something else
private Dictionary<string, string> settingsDic { get; } private Dictionary<string, string> stringCache { get; } = new Dictionary<string, string>();
private Dictionary<string, object> objectCache { get; } = new Dictionary<string, object>();
public string this[string key]
{
get => settingsDic[key];
set
{
if (settingsDic.ContainsKey(key) && settingsDic[key] == value)
return;
settingsDic[key] = value;
// auto-save to file
save();
}
}
public PersistentDictionary(string filepath) public PersistentDictionary(string filepath)
{ {
Filepath = filepath; Filepath = filepath;
// not found. create blank file if (File.Exists(Filepath))
if (!File.Exists(Filepath))
{
// will create any missing directories, incl subdirectories. if all already exist: no action
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
File.WriteAllText(Filepath, "{}");
// give system time to create file before first use
System.Threading.Thread.Sleep(100);
}
settingsDic = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(Filepath));
}
public void EnsureEntries<T>()
{
var stringProperties =
GetPropertiesToPersist(typeof(T))
.Select(p => p.Name)
.ToList();
var keys = settingsDic.Keys.Cast<string>().ToList();
var missingKeys = stringProperties.Except(keys).ToList();
if (!missingKeys.Any())
return; return;
foreach (var key in missingKeys) // will create any missing directories, incl subdirectories. if all already exist: no action
settingsDic.Add(key, null); Directory.CreateDirectory(Path.GetDirectoryName(filepath));
save();
File.WriteAllText(Filepath, "{}");
System.Threading.Thread.Sleep(100);
}
public string GetString(string propertyName)
{
if (!stringCache.ContainsKey(propertyName))
{
var jObject = readFile();
stringCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<string>() : null;
}
return stringCache[propertyName];
}
public T Get<T>(string propertyName) where T : class
=> GetObject(propertyName) is T obj ? obj : default;
public object GetObject(string propertyName)
{
if (!objectCache.ContainsKey(propertyName))
{
var jObject = readFile();
objectCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<object>() : null;
}
return objectCache[propertyName];
} }
private object locker { get; } = new object(); private object locker { get; } = new object();
private void save() public void Set(string propertyName, string newValue)
{ {
// only do this check in string cache, NOT object cache
if (stringCache[propertyName] == newValue)
return;
// set cache
stringCache[propertyName] = newValue;
// set in file
lock (locker) lock (locker)
File.WriteAllText(Filepath, JsonConvert.SerializeObject(settingsDic, Formatting.Indented)); {
var jObject = readFile();
jObject[propertyName] = newValue;
File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
}
} }
public static IEnumerable<System.Reflection.PropertyInfo> GetPropertiesToPersist(Type type) public void Set(string propertyName, object newValue)
=> type {
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance) // set cache
.Where(p => objectCache[propertyName] = newValue;
// string properties only
p.PropertyType == typeof(string) // set in file
// exclude indexer lock (locker)
&& p.GetIndexParameters().Length == 0 {
// exclude read-only, write-only var jObject = readFile();
&& p.GetGetMethod(false) != null jObject[propertyName] = JToken.Parse(JsonConvert.SerializeObject(newValue));
&& p.GetSetMethod(false) != null File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
).ToList(); }
}
private JObject readFile()
{
var settingsJsonContents = File.ReadAllText(Filepath);
var jObject = JsonConvert.DeserializeObject<JObject>(settingsJsonContents);
return jObject;
}
} }
} }

View File

@ -20,21 +20,28 @@ namespace LibationWinForms.Dialogs
{ {
this.decryptKeyTb.Text = config.DecryptKey; this.decryptKeyTb.Text = config.DecryptKey;
this.decryptKeyDescLbl.Text = desc(nameof(config.DecryptKey)); this.decryptKeyDescLbl.Text = desc(nameof(config.DecryptKey));
this.booksLocationDescLbl.Text = desc(nameof(config.Books));
this.downloadsInProgressDescLbl.Text = desc(nameof(config.DownloadsInProgressEnum));
this.decryptInProgressDescLbl.Text = desc(nameof(config.DecryptInProgressEnum));
var winTempText = "In your Windows temporary folder\r\n";
this.downloadsInProgressWinTempRb.Text = $"{winTempText}{Path.Combine(Configuration.WinTemp, "DownloadsInProgress")}";
this.decryptInProgressWinTempRb.Text = $"{winTempText}{Path.Combine(Configuration.WinTemp, "DecryptInProgress")}";
var libFileText = "In your Libation Files (ie: program-created files)\r\n";
this.downloadsInProgressLibationFilesRb.Text = $"{libFileText}{Path.Combine(config.LibationFiles, "DownloadsInProgress")}";
this.decryptInProgressLibationFilesRb.Text = $"{libFileText}{Path.Combine(config.LibationFiles, "DecryptInProgress")}";
this.booksLocationTb.Text this.booksLocationTb.Text
= !string.IsNullOrWhiteSpace(config.Books) = !string.IsNullOrWhiteSpace(config.Books)
? config.Books ? config.Books
: Path.GetDirectoryName(Exe.FileLocationOnDisk); : Path.GetDirectoryName(Exe.FileLocationOnDisk);
this.booksLocationDescLbl.Text = desc(nameof(config.Books));
this.audibleLocaleCb.Text this.audibleLocaleCb.Text
= !string.IsNullOrWhiteSpace(config.LocaleCountryCode) = !string.IsNullOrWhiteSpace(config.LocaleCountryCode)
? config.LocaleCountryCode ? config.LocaleCountryCode
: "us"; : "us";
this.downloadsInProgressDescLbl.Text = desc(nameof(config.DownloadsInProgressEnum));
var winTempDownloadsInProgress = Path.Combine(Configuration.WinTemp, "DownloadsInProgress");
this.downloadsInProgressWinTempRb.Text = "In your Windows temporary folder\r\n" + winTempDownloadsInProgress;
switch (config.DownloadsInProgressEnum) switch (config.DownloadsInProgressEnum)
{ {
case "LibationFiles": case "LibationFiles":
@ -46,9 +53,6 @@ namespace LibationWinForms.Dialogs
break; break;
} }
this.decryptInProgressDescLbl.Text = desc(nameof(config.DecryptInProgressEnum));
var winTempDecryptInProgress = Path.Combine(Configuration.WinTemp, "DecryptInProgress");
this.decryptInProgressWinTempRb.Text = "In your Windows temporary folder\r\n" + winTempDecryptInProgress;
switch (config.DecryptInProgressEnum) switch (config.DecryptInProgressEnum)
{ {
case "LibationFiles": case "LibationFiles":