Improved settings

This commit is contained in:
Robert McRackan 2019-12-13 16:11:55 -05:00
parent 2fa5170f28
commit 95ae8335a1
36 changed files with 1755 additions and 805 deletions

View file

@ -39,7 +39,7 @@ namespace FileManager
Configuration.Instance.DecryptInProgressEnum = "WinTemp";
var M4bRootDir
= Configuration.Instance.DecryptInProgressEnum == "WinTemp" // else "LibationFiles"
? Configuration.Instance.WinTemp
? Configuration.WinTemp
: Configuration.Instance.LibationFiles;
DecryptInProgress = Path.Combine(M4bRootDir, "DecryptInProgress");
Directory.CreateDirectory(DecryptInProgress);
@ -50,7 +50,7 @@ namespace FileManager
Configuration.Instance.DownloadsInProgressEnum = "WinTemp";
var AaxRootDir
= Configuration.Instance.DownloadsInProgressEnum == "WinTemp" // else "LibationFiles"
? Configuration.Instance.WinTemp
? Configuration.WinTemp
: Configuration.Instance.LibationFiles;
DownloadsInProgress = Path.Combine(AaxRootDir, "DownloadsInProgress");
Directory.CreateDirectory(DownloadsInProgress);

View file

@ -4,6 +4,8 @@ using System.ComponentModel;
using System.IO;
using System.Linq;
using Dinah.Core;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace FileManager
{
@ -31,12 +33,18 @@ namespace FileManager
*/
#endregion
private PersistentDictionary persistentDictionary { get; }
private PersistentDictionary persistentDictionary;
[Description("Location of the configuration file where these settings are saved. Please do not edit this file directly while Libation is running.")]
public string Filepath => Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), "Settings.json");
public bool IsComplete
=> File.Exists(APPSETTINGS_JSON)
&& Directory.Exists(LibationFiles)
&& Directory.Exists(Books)
&& File.Exists(SettingsJsonPath)
&& !string.IsNullOrWhiteSpace(LocaleCountryCode)
&& !string.IsNullOrWhiteSpace(DownloadsInProgressEnum)
&& !string.IsNullOrWhiteSpace(DecryptInProgressEnum);
[Description("[Advanced. Leave alone in most cases.] Your user-specific key used to decrypt your audible files (*.aax) into audio files you can use anywhere (*.m4b)")]
[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)];
@ -50,17 +58,21 @@ namespace FileManager
set => persistentDictionary[nameof(Books)] = value;
}
public string WinTemp { get; } = Path.Combine(Path.GetTempPath(), "Libation");
public static string AppDir { get; } = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), LIBATION_FILES));
public static string MyDocs { get; } = Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), LIBATION_FILES));
public static string WinTemp { get; } = Path.GetFullPath(Path.Combine(Path.GetTempPath(), "Libation"));
[Description("Location for storage of program-created files")]
public string LibationFiles
private Dictionary<string, string> wellKnownPaths { get; } = new Dictionary<string, string>
{
get => persistentDictionary[nameof(LibationFiles)];
set => persistentDictionary[nameof(LibationFiles)] = value;
}
["AppDir"] = AppDir,
["MyDocs"] = MyDocs,
["WinTemp"] = WinTemp
};
private Dictionary<string, string> cache { get; } = new Dictionary<string, string>();
// 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.\r\nWhen download is complete, the final file will be in [LibationFiles]\\DownloadsFinal")]
@ -78,49 +90,114 @@ namespace FileManager
set => persistentDictionary[nameof(DecryptInProgressEnum)] = value;
}
public string LocaleCountryCode
{
get => persistentDictionary[nameof(LocaleCountryCode)];
set => persistentDictionary[nameof(LocaleCountryCode)] = value;
}
public string LocaleCountryCode
{
get => persistentDictionary[nameof(LocaleCountryCode)];
set => persistentDictionary[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
// singleton stuff
public static Configuration Instance { get; } = new Configuration();
private Configuration()
{
// load json values into memory
persistentDictionary = new PersistentDictionary(Filepath);
ensureDictionaryEntries();
private Configuration() { }
// don't create dir. dir creation is the responsibility of places that use the dir
if (string.IsNullOrWhiteSpace(LibationFiles))
LibationFiles = Path.Combine(Path.GetDirectoryName(Exe.FileLocationOnDisk), "Libation");
private const string APPSETTINGS_JSON = "appsettings.json";
private const string LIBATION_FILES = "LibationFiles";
[Description("Location for storage of program-created files")]
public string LibationFiles
=> cache.ContainsKey(LIBATION_FILES)
? cache[LIBATION_FILES]
: getLibationFiles();
private string getLibationFiles()
{
var value = getLiberationFilesSettingFromJson();
if (wellKnownPaths.ContainsKey(value))
value = wellKnownPaths[value];
// must write here before SettingsJsonPath in next step tries to read from dictionary
cache[LIBATION_FILES] = value;
// load json values into memory. create if not exists
persistentDictionary = new PersistentDictionary(SettingsJsonPath);
persistentDictionary.EnsureEntries<Configuration>();
return value;
}
private string getLiberationFilesSettingFromJson()
{
static string createSettingsJson()
{
var dir = "AppDir";
File.WriteAllText(APPSETTINGS_JSON, new JObject { { LIBATION_FILES, dir } }.ToString(Formatting.Indented));
return dir;
}
if (!File.Exists(APPSETTINGS_JSON))
return createSettingsJson();
var appSettingsContents = File.ReadAllText(APPSETTINGS_JSON);
JObject jObj;
try
{
jObj = JObject.Parse(appSettingsContents);
}
catch
{
return createSettingsJson();
}
if (!jObj.ContainsKey(LIBATION_FILES))
return createSettingsJson();
var value = jObj[LIBATION_FILES].Value<string>();
return value;
}
private string SettingsJsonPath => Path.Combine(LibationFiles, "Settings.json");
public static string GetDescription(string propertyName)
{
var attribute = typeof(Configuration)
.GetProperty(propertyName)
?.GetCustomAttributes(typeof(DescriptionAttribute), true)
.SingleOrDefault()
as DescriptionAttribute;
var attribute = typeof(Configuration)
.GetProperty(propertyName)
?.GetCustomAttributes(typeof(DescriptionAttribute), true)
.SingleOrDefault()
as DescriptionAttribute;
return attribute?.Description;
}
private void ensureDictionaryEntries()
{
var stringProperties = getDictionaryProperties().Select(p => p.Name).ToList();
var missingKeys = stringProperties.Except(persistentDictionary.Keys).ToArray();
persistentDictionary.AddKeys(missingKeys);
return attribute?.Description;
}
private IEnumerable<System.Reflection.PropertyInfo> dicPropertiesCache;
private IEnumerable<System.Reflection.PropertyInfo> getDictionaryProperties()
public bool TrySetLibationFiles(string directory)
{
if (dicPropertiesCache == null)
dicPropertiesCache = PersistentDictionary.GetPropertiesToPersist(this.GetType());
return dicPropertiesCache;
if (!Directory.Exists(directory) && !wellKnownPaths.ContainsKey(directory))
return false;
// if moving from default, delete old settings file and dir (if empty)
if (LibationFiles.EqualsInsensitive(AppDir))
{
File.Delete(SettingsJsonPath);
System.Threading.Thread.Sleep(100);
if (!Directory.EnumerateDirectories(AppDir).Any() && !Directory.EnumerateFiles(AppDir).Any())
Directory.Delete(AppDir);
}
cache.Remove(LIBATION_FILES);
var contents = File.ReadAllText(APPSETTINGS_JSON);
var jObj = JObject.Parse(contents);
jObj[LIBATION_FILES] = directory;
var output = JsonConvert.SerializeObject(jObj, Formatting.Indented);
File.WriteAllText(APPSETTINGS_JSON, output);
return true;
}
}
}

View file

@ -23,7 +23,7 @@ namespace FileManager
static FilePathCache()
{
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
if (FileUtility.FileExists(JsonFile))
if (File.Exists(JsonFile))
{
var list = JsonConvert.DeserializeObject<List<CacheEntry>>(File.ReadAllText(JsonFile));
cache = new Cache<CacheEntry>(list);
@ -39,7 +39,7 @@ namespace FileManager
if (entry == null)
return null;
if (!FileUtility.FileExists(entry.Path))
if (!File.Exists(entry.Path))
{
remove(entry);
return null;
@ -56,7 +56,7 @@ namespace FileManager
public static void Upsert(string id, FileType type, string path)
{
if (!FileUtility.FileExists(path))
if (!File.Exists(path))
throw new FileNotFoundException("Cannot add path to cache. File not found");
var entry = cache.SingleOrDefault(i => i.Id == id && i.FileType == type);

View file

@ -1,32 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FileManager
{
public static class FileUtility
{
// a replacement for File.Exists() which allows long paths
// not needed in .net-core
public static bool FileExists(string path)
{
var basic = File.Exists(path);
if (basic)
return true;
// character cutoff is usually 269 but this isn't a hard number. there are edgecases which shorted the threshold
if (path.Length < 260)
return false;
// try long name prefix:
// \\?\
// https://blogs.msdn.microsoft.com/jeremykuhne/2016/06/21/more-on-new-net-path-handling/
path = @"\\?\" + path;
return File.Exists(path);
}
public static string GetValidFilename(string dirFullPath, string filename, string extension, params string[] metadataSuffixes)
{
if (string.IsNullOrWhiteSpace(dirFullPath))
@ -51,7 +29,7 @@ namespace FileManager
// ensure uniqueness
var fullfilename = Path.Combine(dirFullPath, filename + extension);
var i = 0;
while (FileExists(fullfilename))
while (File.Exists(fullfilename))
fullfilename = Path.Combine(dirFullPath, filename + $" ({++i})" + extension);
return fullfilename;

View file

@ -35,6 +35,9 @@ namespace FileManager
// not found. create blank file
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
@ -44,14 +47,19 @@ namespace FileManager
settingsDic = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(Filepath));
}
public IEnumerable<string> Keys => settingsDic.Keys.Cast<string>();
public void AddKeys(params string[] keys)
public void EnsureEntries<T>()
{
if (keys == null || keys.Length == 0)
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;
foreach (var key in keys)
foreach (var key in missingKeys)
settingsDic.Add(key, null);
save();
}

View file

@ -47,7 +47,7 @@ namespace FileManager
{
var path = getPath(def);
cache[def]
= FileUtility.FileExists(path)
= File.Exists(path)
? File.ReadAllBytes(path)
: null;
}

View file

@ -22,7 +22,7 @@ namespace FileManager
static QuickFilters()
{
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
if (FileUtility.FileExists(JsonFile))
if (File.Exists(JsonFile))
inMemoryState = JsonConvert.DeserializeObject<FilterState>(File.ReadAllText(JsonFile));
}

View file

@ -53,7 +53,7 @@ namespace FileManager
{
if (cache is null)
lock (locker)
cache = !FileUtility.FileExists(TagsFile)
cache = !File.Exists(TagsFile)
? new Dictionary<string, string>()
: JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(TagsFile));
}