Improved configuration management
This commit is contained in:
parent
6c757773f7
commit
4994684690
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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))
|
return;
|
||||||
{
|
|
||||||
// will create any missing directories, incl subdirectories. if all already exist: no action
|
// will create any missing directories, incl subdirectories. if all already exist: no action
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
|
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
|
||||||
|
|
||||||
File.WriteAllText(Filepath, "{}");
|
File.WriteAllText(Filepath, "{}");
|
||||||
|
|
||||||
// give system time to create file before first use
|
|
||||||
System.Threading.Thread.Sleep(100);
|
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 =
|
if (!objectCache.ContainsKey(propertyName))
|
||||||
GetPropertiesToPersist(typeof(T))
|
{
|
||||||
.Select(p => p.Name)
|
var jObject = readFile();
|
||||||
.ToList();
|
objectCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<object>() : null;
|
||||||
var keys = settingsDic.Keys.Cast<string>().ToList();
|
}
|
||||||
var missingKeys = stringProperties.Except(keys).ToList();
|
|
||||||
|
|
||||||
if (!missingKeys.Any())
|
return objectCache[propertyName];
|
||||||
return;
|
|
||||||
|
|
||||||
foreach (var key in missingKeys)
|
|
||||||
settingsDic.Add(key, null);
|
|
||||||
save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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":
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user