Improved settings
This commit is contained in:
parent
2fa5170f28
commit
95ae8335a1
36 changed files with 1755 additions and 805 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ namespace FileManager
|
|||
{
|
||||
var path = getPath(def);
|
||||
cache[def]
|
||||
= FileUtility.FileExists(path)
|
||||
= File.Exists(path)
|
||||
? File.ReadAllBytes(path)
|
||||
: null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue