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")]
public string DecryptKey
{
get => persistentDictionary[nameof(DecryptKey)];
set => persistentDictionary[nameof(DecryptKey)] = value;
get => persistentDictionary.GetString(nameof(DecryptKey));
set => persistentDictionary.Set(nameof(DecryptKey), value);
}
[Description("Location for book storage. Includes destination of newly liberated books")]
public string Books
{
get => persistentDictionary[nameof(Books)];
set => persistentDictionary[nameof(Books)] = value;
get => persistentDictionary.GetString(nameof(Books));
set => persistentDictionary.Set(nameof(Books), value);
}
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")]
public string DownloadsInProgressEnum
{
get => persistentDictionary[nameof(DownloadsInProgressEnum)];
set => persistentDictionary[nameof(DownloadsInProgressEnum)] = value;
get => persistentDictionary.GetString(nameof(DownloadsInProgressEnum));
set => persistentDictionary.Set(nameof(DownloadsInProgressEnum), value);
}
// 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")]
public string DecryptInProgressEnum
{
get => persistentDictionary[nameof(DecryptInProgressEnum)];
set => persistentDictionary[nameof(DecryptInProgressEnum)] = value;
get => persistentDictionary.GetString(nameof(DecryptInProgressEnum));
set => persistentDictionary.Set(nameof(DecryptInProgressEnum), value);
}
public string LocaleCountryCode
{
get => persistentDictionary[nameof(LocaleCountryCode)];
set => persistentDictionary[nameof(LocaleCountryCode)] = value;
get => persistentDictionary.GetString(nameof(LocaleCountryCode));
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
@ -123,7 +123,6 @@ namespace FileManager
// load json values into memory. create if not exists
persistentDictionary = new PersistentDictionary(SettingsJsonPath);
persistentDictionary.EnsureEntries<Configuration>();
return value;
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace FileManager
{
@ -10,78 +11,87 @@ namespace FileManager
{
public string Filepath { get; }
// forgiving -- doesn't drop settings. old entries will continue to be persisted even if not publicly visible
private Dictionary<string, string> settingsDic { get; }
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();
}
}
// optimize for strings. expectation is most settings will be strings and a rare exception will be something else
private Dictionary<string, string> stringCache { get; } = new Dictionary<string, string>();
private Dictionary<string, object> objectCache { get; } = new Dictionary<string, object>();
public PersistentDictionary(string filepath)
{
Filepath = filepath;
// not found. create blank file
if (!File.Exists(Filepath))
{
if (File.Exists(Filepath))
return;
// 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 string GetString(string propertyName)
{
if (!stringCache.ContainsKey(propertyName))
{
var jObject = readFile();
stringCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<string>() : null;
}
public void EnsureEntries<T>()
return stringCache[propertyName];
}
public T Get<T>(string propertyName) where T : class
=> GetObject(propertyName) is T obj ? obj : default;
public object GetObject(string propertyName)
{
var stringProperties =
GetPropertiesToPersist(typeof(T))
.Select(p => p.Name)
.ToList();
var keys = settingsDic.Keys.Cast<string>().ToList();
var missingKeys = stringProperties.Except(keys).ToList();
if (!objectCache.ContainsKey(propertyName))
{
var jObject = readFile();
objectCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<object>() : null;
}
if (!missingKeys.Any())
return;
foreach (var key in missingKeys)
settingsDic.Add(key, null);
save();
return objectCache[propertyName];
}
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)
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)
=> type
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(p =>
// string properties only
p.PropertyType == typeof(string)
// exclude indexer
&& p.GetIndexParameters().Length == 0
// exclude read-only, write-only
&& p.GetGetMethod(false) != null
&& p.GetSetMethod(false) != null
).ToList();
public void Set(string propertyName, object newValue)
{
// set cache
objectCache[propertyName] = newValue;
// set in file
lock (locker)
{
var jObject = readFile();
jObject[propertyName] = JToken.Parse(JsonConvert.SerializeObject(newValue));
File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
}
}
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.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
= !string.IsNullOrWhiteSpace(config.Books)
? config.Books
: Path.GetDirectoryName(Exe.FileLocationOnDisk);
this.booksLocationDescLbl.Text = desc(nameof(config.Books));
this.audibleLocaleCb.Text
= !string.IsNullOrWhiteSpace(config.LocaleCountryCode)
? config.LocaleCountryCode
: "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)
{
case "LibationFiles":
@ -46,9 +53,6 @@ namespace LibationWinForms.Dialogs
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)
{
case "LibationFiles":