mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-24 06:28:02 -05:00
- Add `LIBATION_FILES_DIR` environment variable to specify LibationFiles directory instead of appsettings.json - OptionsBase supports overriding setting - Added `EphemeralSettings` which are loaded from Settings.json once and can be modified with the `--override` command parameter - Added `get-setting` command - Prints (editable) settings and their values. Prints specified settings, or all settings if none specified - `--listEnumValues` option will list all names for a speficied enum-type settings. If no setting names are specified, prints all enum values for all enum settings. - Prints in a text-based table or bare with `-b` switch - Added `get-license` command which requests a content license and prints it as a json to stdout - Improved `liberate` command - Added `-force` option to force liberation without validation. - Added support to download with a license file supplied to stdin - Improve startup performance when downloading explicit ASIN(s) - Fix long-standing bug where cover art was not being downloading
283 lines
9.2 KiB
C#
283 lines
9.2 KiB
C#
using Dinah.Core.Logging;
|
|
using FileManager;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using Serilog;
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
[assembly: InternalsVisibleTo("AppScaffolding")]
|
|
[assembly: InternalsVisibleTo("LibationUiBase.Tests")]
|
|
|
|
#nullable enable
|
|
namespace LibationFileManager;
|
|
|
|
/// <summary>
|
|
/// Provides access to Libation's configuration and settings file locations, including methods for validating and
|
|
/// updating the Libation files directory and Settings.json file. An instance is bount to a single appsettings.json file.
|
|
/// </summary>
|
|
public class LibationFiles
|
|
{
|
|
internal static string? s_DefaultLibationFilesDirectory;
|
|
public static string DefaultLibationFilesDirectory => s_DefaultLibationFilesDirectory ??= Configuration.IsWindows ? Configuration.UserProfile : Configuration.LocalAppData;
|
|
|
|
public const string LIBATION_FILES_KEY = "LibationFiles";
|
|
public const string SETTINGS_JSON = "Settings.json";
|
|
public const string LIBATION_FILES_DIR = "LIBATION_FILES_DIR";
|
|
|
|
/// <summary>
|
|
/// Directory pointed to by appsettings.json
|
|
/// </summary>
|
|
public LongPath Location { get; private set; }
|
|
/// <summary>
|
|
/// Returns true if <see cref="SettingsFilePath"/> exists and the <see cref="Configuration.Books"/> property has a non-null, non-empty value.
|
|
/// Does not verify the existence of the <see cref="Configuration.Books"/> directory.
|
|
/// </summary>
|
|
public bool SettingsAreValid => SettingsFileIsValid(SettingsFilePath);
|
|
/// <summary>
|
|
/// Found Location of appsettings.json. This file must exist or be able to be created for Libation to start.
|
|
/// </summary>
|
|
internal string? AppsettingsJsonFile { get; }
|
|
/// <summary>
|
|
/// File path to Settings.json inside <see cref="Location"/>
|
|
/// </summary>
|
|
public string SettingsFilePath => Path.Combine(Location, SETTINGS_JSON);
|
|
|
|
internal LibationFiles()
|
|
{
|
|
var libationFilesDir = Environment.GetEnvironmentVariable(LIBATION_FILES_DIR);
|
|
if (Directory.Exists(libationFilesDir))
|
|
{
|
|
Location = libationFilesDir;
|
|
}
|
|
else
|
|
{
|
|
AppsettingsJsonFile = GetOrCreateAppsettingsFile();
|
|
Location = GetLibationFilesFromAppsettings(AppsettingsJsonFile);
|
|
}
|
|
}
|
|
|
|
internal LibationFiles(string appSettingsFile)
|
|
{
|
|
AppsettingsJsonFile = appSettingsFile;
|
|
Location = GetLibationFilesFromAppsettings(AppsettingsJsonFile);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the location of the Libation Files directory, updating appsettings.json.
|
|
/// </summary>
|
|
public void SetLibationFiles(LongPath libationFilesDirectory)
|
|
{
|
|
if (AppsettingsJsonFile is null)
|
|
{
|
|
Environment.SetEnvironmentVariable(LIBATION_FILES_DIR, libationFilesDirectory);
|
|
return;
|
|
}
|
|
|
|
var startingContents = File.ReadAllText(AppsettingsJsonFile);
|
|
var jObj = JObject.Parse(startingContents);
|
|
|
|
jObj[LIBATION_FILES_KEY] = (string)(Location = libationFilesDirectory);
|
|
|
|
var endingContents = JsonConvert.SerializeObject(jObj, Formatting.Indented);
|
|
if (startingContents == endingContents)
|
|
return;
|
|
|
|
try
|
|
{
|
|
// now it's set in the file again but no settings have moved yet
|
|
File.WriteAllText(AppsettingsJsonFile, endingContents);
|
|
Log.Logger.TryLogInformation("Libation files changed {@DebugInfo}", new { AppsettingsJsonFile, LIBATION_FILES_KEY, libationFilesDirectory });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Logger.TryLogError(ex, "Failed to change Libation files location {@DebugInfo}", new { AppsettingsJsonFile, LIBATION_FILES_KEY, libationFilesDirectory });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if <paramref name="settingsFile"/> exists and the <see cref="Configuration.Books"/> property has a non-null, non-empty value.
|
|
/// Does not verify the existence of the <see cref="Configuration.Books"/> directory.
|
|
/// </summary>
|
|
/// <param name="settingsFile">File path to the Settings.json file</param>
|
|
public static bool SettingsFileIsValid(string settingsFile)
|
|
{
|
|
if (!Directory.Exists(Path.GetDirectoryName(settingsFile)) || !File.Exists(settingsFile))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
var settingsJson = JObject.Parse(File.ReadAllText(settingsFile));
|
|
return !string.IsNullOrWhiteSpace(settingsJson[nameof(Configuration.Books)]?.Value<string>());
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Logger.Error(ex, "Failed to load settings file: {@SettingsFile}", settingsFile);
|
|
try
|
|
{
|
|
Log.Logger.Information("Deleting invalid settings file: {@SettingsFile}", settingsFile);
|
|
FileUtility.SaferDelete(settingsFile);
|
|
Log.Logger.Information("Creating a new, empty setting file: {@SettingsFile}", settingsFile);
|
|
try
|
|
{
|
|
File.WriteAllText(settingsFile, "{}");
|
|
}
|
|
catch (Exception createEx)
|
|
{
|
|
Log.Logger.Error(createEx, "Failed to create new settings file: {@SettingsFile}", settingsFile);
|
|
}
|
|
}
|
|
catch (Exception deleteEx)
|
|
{
|
|
Log.Logger.Error(deleteEx, "Failed to delete the invalid settings file: {@SettingsFile}", settingsFile);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Try to find appsettings.json in the following locations:
|
|
/// <list type="number">
|
|
/// <item>
|
|
/// <description>[App Directory]</description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>%LocalAppData%\Libation</description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>%AppData%\Libation</description>
|
|
/// </item>
|
|
/// <item>
|
|
/// <description>%Temp%\Libation</description>
|
|
/// </item>
|
|
/// </list>
|
|
///
|
|
/// If not found, try to create it in each of the same locations in-order until successful.
|
|
///
|
|
/// <para>This method must complete successfully for Libation to continue.</para>
|
|
/// </summary>
|
|
/// <returns>appsettings.json file path</returns>
|
|
/// <exception cref="ApplicationException">appsettings.json could not be found or created.</exception>
|
|
private static string GetOrCreateAppsettingsFile()
|
|
{
|
|
const string appsettings_filename = "appsettings.json";
|
|
|
|
//Possible appsettings.json locations, in order of preference.
|
|
string[] possibleAppsettingsDirectories = new[]
|
|
{
|
|
Configuration.ProcessDirectory,
|
|
Configuration.LocalAppData,
|
|
Configuration.UserProfile,
|
|
Configuration.WinTemp,
|
|
};
|
|
|
|
//Try to find and validate appsettings.json in each folder
|
|
foreach (var dir in possibleAppsettingsDirectories)
|
|
{
|
|
var appsettingsFile = Path.Combine(dir, appsettings_filename);
|
|
|
|
if (File.Exists(appsettingsFile))
|
|
{
|
|
try
|
|
{
|
|
var appSettings = JObject.Parse(File.ReadAllText(appsettingsFile));
|
|
|
|
if (appSettings.ContainsKey(LIBATION_FILES_KEY)
|
|
&& appSettings[LIBATION_FILES_KEY] is JValue jval
|
|
&& jval.Value is string settingsPath
|
|
&& !string.IsNullOrWhiteSpace(settingsPath))
|
|
return appsettingsFile;
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
//Valid appsettings.json not found. Try to create it in each folder.
|
|
var endingContents = new JObject { { LIBATION_FILES_KEY, DefaultLibationFilesDirectory } }.ToString(Formatting.Indented);
|
|
|
|
foreach (var dir in possibleAppsettingsDirectories)
|
|
{
|
|
//Don't try to create appsettings.json in the program files directory on *.nix systems.
|
|
//However, still _look_ for one there for backwards compatibility with previous installations
|
|
if (!Configuration.IsWindows && dir == Configuration.ProcessDirectory)
|
|
continue;
|
|
|
|
var appsettingsFile = Path.Combine(dir, appsettings_filename);
|
|
|
|
try
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
File.WriteAllText(appsettingsFile, endingContents);
|
|
return appsettingsFile;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Logger.TryLogError(ex, $"Failed to create {appsettingsFile}");
|
|
}
|
|
}
|
|
|
|
throw new ApplicationException($"Could not locate or create {appsettings_filename}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the LibationFiles directory from appsettings.json, expanding environment variables as needed.
|
|
/// </summary>
|
|
/// <param name="appsettingsPath"></param>
|
|
/// <returns></returns>
|
|
/// <exception cref="InvalidDataException">The appsettings.json file does not contain a "LibationFiles" key</exception>
|
|
private static string GetLibationFilesFromAppsettings(LongPath appsettingsPath)
|
|
{
|
|
// do not check whether directory exists. special/meta directory (eg: AppDir) is valid
|
|
// verify from live file. no try/catch. want failures to be visible
|
|
var jObjFinal = JObject.Parse(File.ReadAllText(appsettingsPath));
|
|
|
|
if (jObjFinal[LIBATION_FILES_KEY]?.Value<string>() is not string libationFiles)
|
|
throw new InvalidDataException($"{LIBATION_FILES_KEY} not found in {appsettingsPath}");
|
|
|
|
if (Configuration.IsWindows)
|
|
{
|
|
libationFiles = Environment.ExpandEnvironmentVariables(libationFiles);
|
|
}
|
|
else
|
|
{
|
|
//If the shell command fails and returns null, proceed with the verbatim
|
|
//LIBATION_FILES_KEY path and hope for the best. If Libation can't find
|
|
//anything at this path it will set LIBATION_FILES_KEY to UserProfile
|
|
libationFiles = runShellCommand("echo " + libationFiles) ?? libationFiles;
|
|
}
|
|
|
|
return libationFiles;
|
|
|
|
static string? runShellCommand(string command)
|
|
{
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = "/bin/sh",
|
|
RedirectStandardOutput = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
ArgumentList =
|
|
{
|
|
"-c",
|
|
command
|
|
}
|
|
};
|
|
|
|
try
|
|
{
|
|
var proc = Process.Start(psi);
|
|
proc?.WaitForExit();
|
|
return proc?.StandardOutput?.ReadToEnd()?.Trim();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error(e, "Failed to run shell command. {@Arguments}", psi.ArgumentList);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|