Overhaul of installation workflow per issue #36

This commit is contained in:
Robert McRackan 2021-07-19 14:52:34 -04:00
parent 730484c28c
commit c9b434daed
14 changed files with 543 additions and 516 deletions

View file

@ -37,7 +37,7 @@ namespace FileManager
get
{
if (string.IsNullOrWhiteSpace(Configuration.Instance.Books))
Configuration.Instance.Books = Path.Combine(Configuration.Instance.LibationFiles, "Books");
Configuration.Instance.Books = Path.Combine(Configuration.UserProfile, "Books");
return Directory.CreateDirectory(Configuration.Instance.Books).FullName;
}
}

View file

@ -11,11 +11,28 @@ namespace FileManager
{
public class Configuration
{
// settings will be persisted when all are true
// - property (not field)
// - string
// - public getter
// - public setter
public bool LibationSettingsAreValid
=> File.Exists(APPSETTINGS_JSON)
&& SettingsFileIsValid(SettingsFilePath);
public static bool SettingsFileIsValid(string settingsFile)
{
if (!Directory.Exists(Path.GetDirectoryName(settingsFile)) || !File.Exists(settingsFile))
return false;
var pDic = new PersistentDictionary(settingsFile, isReadOnly: true);
var booksDir = pDic.GetString(nameof(Books));
if (booksDir is null || !Directory.Exists(booksDir))
return false;
if (string.IsNullOrWhiteSpace(pDic.GetString(nameof(InProgress))))
return false;
return true;
}
#region persistent configuration settings/values
#region // properties to test reflection
/*
@ -33,16 +50,39 @@ namespace FileManager
*/
#endregion
// settings will be persisted when all are true
// - property (not field)
// - string
// - public getter
// - public setter
// 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
// default setting and directory creation occur in class responsible for files.
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
// exceptions: appsettings.json, LibationFiles dir, Settings.json
private PersistentDictionary persistentDictionary;
public bool FilesExist
=> File.Exists(APPSETTINGS_JSON)
&& File.Exists(SettingsFilePath)
&& Directory.Exists(LibationFiles)
&& Directory.Exists(Books);
public object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName);
public void SetObject(string propertyName, object newValue) => persistentDictionary.Set(propertyName, newValue);
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue) => persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue);
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
public static string GetDescription(string propertyName)
{
var attribute = typeof(Configuration)
.GetProperty(propertyName)
?.GetCustomAttributes(typeof(DescriptionAttribute), true)
.SingleOrDefault()
as DescriptionAttribute;
return attribute?.Description;
}
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
[Description("Location for book storage. Includes destination of newly liberated books")]
public string Books
{
@ -50,10 +90,34 @@ namespace FileManager
set => persistentDictionary.Set(nameof(Books), value);
}
#region known directories
public static string AppDir_Relative => @".\LibationFiles";
// temp/working dir(s) should be outside of dropbox
[Description("Temporary location of files while they're in process of being downloaded and decrypted.\r\nWhen decryption is complete, the final file will be in Books location\r\nRecommend not using a folder which is backed up real time. Eg: Dropbox, iCloud, Google Drive")]
public string InProgress
{
get => persistentDictionary.GetString(nameof(InProgress));
set => persistentDictionary.Set(nameof(InProgress), value);
}
[Description("Allow Libation for fix up audiobook metadata?")]
public bool AllowLibationFixup
{
get => persistentDictionary.Get<bool>(nameof(AllowLibationFixup));
set => persistentDictionary.Set(nameof(AllowLibationFixup), value);
}
[Description("Decrypt to lossy format?")]
public bool DecryptToLossy
{
get => persistentDictionary.Get<bool>(nameof(DecryptToLossy));
set => persistentDictionary.Set(nameof(DecryptToLossy), value);
}
#endregion
#region known directories
public static string AppDir_Relative => $@".\{LIBATION_FILES_KEY}";
public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), LIBATION_FILES_KEY));
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "LibationFiles"));
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
public static string WinTemp => Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation"));
public static string UserProfile => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Libation"));
@ -84,7 +148,8 @@ namespace FileManager
(KnownDirectories.AppDir, () => AppDir_Relative),
(KnownDirectories.WinTemp, () => WinTemp),
(KnownDirectories.MyDocs, () => MyDocs),
// this is important to not let very early calls try to accidentally load LibationFiles too early
// this is important to not let very early calls try to accidentally load LibationFiles too early.
// also, keep this at bottom of this list
(KnownDirectories.LibationFiles, () => libationFilesPathCache)
};
public static string GetKnownDirectoryPath(KnownDirectories directory)
@ -98,43 +163,21 @@ namespace FileManager
if (string.IsNullOrWhiteSpace(directory))
return KnownDirectories.None;
var dirFunc = directoryOptionsPaths.SingleOrDefault(dirFunc => dirFunc.getPathFunc() == directory);
// 'First' instead of 'Single' because LibationFiles could match other directories. eg: default value of LibationFiles == UserProfile.
// since it's a list, order matters and non-LibationFiles will be returned first
var dirFunc = directoryOptionsPaths.FirstOrDefault(dirFunc => dirFunc.getPathFunc() == directory);
return dirFunc == default ? KnownDirectories.None : dirFunc.directory;
}
#endregion
#endregion
// default setting and directory creation occur in class responsible for files.
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
// exceptions: appsettings.json, LibationFiles dir, Settings.json
// temp/working dir(s) should be outside of dropbox
[Description("Temporary location of files while they're in process of being downloaded and decrypted.\r\nWhen decryption is complete, the final file will be in Books location\r\nRecommend not using a folder which is backed up real time. Eg: Dropbox, iCloud, Google Drive")]
public string InProgress
{
get => persistentDictionary.GetString(nameof(InProgress));
set => persistentDictionary.Set(nameof(InProgress), value);
}
[Description("Allow Libation for fix up audiobook metadata?")]
public bool AllowLibationFixup
{
get => persistentDictionary.Get<bool>(nameof(AllowLibationFixup));
set => persistentDictionary.Set(nameof(AllowLibationFixup), value);
}
[Description("Decrypt to lossy format?")]
public bool DecryptToLossy
{
get => persistentDictionary.Get<bool>(nameof(DecryptToLossy));
set => persistentDictionary.Set(nameof(DecryptToLossy), 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
// singleton stuff
public static Configuration Instance { get; } = new Configuration();
#region singleton stuff
public static Configuration Instance { get; } = new Configuration();
private Configuration() { }
#endregion
private const string APPSETTINGS_JSON = "appsettings.json";
#region LibationFiles
private const string APPSETTINGS_JSON = "appsettings.json";
private const string LIBATION_FILES_KEY = "LibationFiles";
[Description("Location for storage of program-created files")]
@ -192,21 +235,6 @@ namespace FileManager
return valueFinal;
}
public object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName);
public void SetObject(string propertyName, object newValue) => persistentDictionary.Set(propertyName, newValue);
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue) => persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue);
public static string GetDescription(string propertyName)
{
var attribute = typeof(Configuration)
.GetProperty(propertyName)
?.GetCustomAttributes(typeof(DescriptionAttribute), true)
.SingleOrDefault()
as DescriptionAttribute;
return attribute?.Description;
}
public bool TrySetLibationFiles(string directory)
{
// this is WRONG. need to MOVE settings; not DELETE them
@ -236,5 +264,6 @@ namespace FileManager
return true;
}
#endregion
}
}

View file

@ -10,14 +10,16 @@ namespace FileManager
public class PersistentDictionary
{
public string Filepath { get; }
public bool IsReadOnly { get; }
// 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)
public PersistentDictionary(string filepath, bool isReadOnly = false)
{
Filepath = filepath;
IsReadOnly = isReadOnly;
if (File.Exists(Filepath))
return;
@ -25,6 +27,9 @@ namespace FileManager
// will create any missing directories, incl subdirectories. if all already exist: no action
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
if (IsReadOnly)
return;
File.WriteAllText(Filepath, "{}");
System.Threading.Thread.Sleep(100);
}
@ -34,7 +39,9 @@ namespace FileManager
if (!stringCache.ContainsKey(propertyName))
{
var jObject = readFile();
stringCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<string>() : null;
if (!jObject.ContainsKey(propertyName))
return null;
stringCache[propertyName] = jObject[propertyName].Value<string>();
}
return stringCache[propertyName];
@ -42,10 +49,10 @@ namespace FileManager
public T Get<T>(string propertyName)
{
var o = GetObject(propertyName);
if (o is null) return default;
if (o is JToken jt) return jt.Value<T>();
return (T)o;
var obj = GetObject(propertyName);
if (obj is null) return default;
if (obj is JToken jToken) return jToken.Value<T>();
return (T)obj;
}
public object GetObject(string propertyName)
@ -53,17 +60,21 @@ namespace FileManager
if (!objectCache.ContainsKey(propertyName))
{
var jObject = readFile();
objectCache[propertyName] = jObject.ContainsKey(propertyName) ? jObject[propertyName].Value<object>() : null;
if (!jObject.ContainsKey(propertyName))
return null;
objectCache[propertyName] = jObject[propertyName].Value<object>();
}
return objectCache[propertyName];
}
public bool Exists(string propertyName) => readFile().ContainsKey(propertyName);
private object locker { get; } = new object();
public void Set(string propertyName, string newValue)
{
// only do this check in string cache, NOT object cache
if (stringCache[propertyName] == newValue)
if (stringCache.ContainsKey(propertyName) && stringCache[propertyName] == newValue)
return;
// set cache
@ -83,6 +94,9 @@ namespace FileManager
private void writeFile(string propertyName, JToken newValue)
{
if (IsReadOnly)
return;
try
{
var str = newValue?.ToString();
@ -113,6 +127,9 @@ namespace FileManager
// special case: no caching. no logging
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue)
{
if (IsReadOnly)
return;
lock (locker)
{
var jObject = readFile();