Add support for multipart title naming templates
This commit is contained in:
parent
a24c929acf
commit
45c5efffbd
@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AAXClean;
|
using AAXClean;
|
||||||
using AAXClean.Codecs;
|
using AAXClean.Codecs;
|
||||||
|
using Dinah.Core;
|
||||||
using Dinah.Core.StepRunner;
|
using Dinah.Core.StepRunner;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
|
|
||||||
@ -14,14 +15,22 @@ namespace AaxDecrypter
|
|||||||
protected override StepSequence Steps { get; }
|
protected override StepSequence Steps { get; }
|
||||||
|
|
||||||
private Func<MultiConvertFileProperties, string> multipartFileNameCallback { get; }
|
private Func<MultiConvertFileProperties, string> multipartFileNameCallback { get; }
|
||||||
|
private Func<MultiConvertFileProperties, string> multipartTitleNameCallback { get; }
|
||||||
|
|
||||||
private static TimeSpan minChapterLength { get; } = TimeSpan.FromSeconds(3);
|
private static TimeSpan minChapterLength { get; } = TimeSpan.FromSeconds(3);
|
||||||
private List<string> multiPartFilePaths { get; } = new List<string>();
|
private List<string> multiPartFilePaths { get; } = new List<string>();
|
||||||
|
|
||||||
public AaxcDownloadMultiConverter(string outFileName, string cacheDirectory, DownloadOptions dlLic,
|
public AaxcDownloadMultiConverter(
|
||||||
Func<MultiConvertFileProperties, string> multipartFileNameCallback = null)
|
string outFileName,
|
||||||
|
string cacheDirectory,
|
||||||
|
DownloadOptions dlLic,
|
||||||
|
Func<MultiConvertFileProperties, string> multipartFileNameCallback,
|
||||||
|
Func<MultiConvertFileProperties, string> multipartTitleNameCallback
|
||||||
|
)
|
||||||
: base(outFileName, cacheDirectory, dlLic)
|
: base(outFileName, cacheDirectory, dlLic)
|
||||||
{
|
{
|
||||||
|
ArgumentValidator.EnsureNotNull(multipartFileNameCallback, nameof(multipartFileNameCallback));
|
||||||
|
ArgumentValidator.EnsureNotNull(multipartTitleNameCallback, nameof(multipartTitleNameCallback));
|
||||||
Steps = new StepSequence
|
Steps = new StepSequence
|
||||||
{
|
{
|
||||||
Name = "Download and Convert Aaxc To " + DownloadOptions.OutputFormat,
|
Name = "Download and Convert Aaxc To " + DownloadOptions.OutputFormat,
|
||||||
@ -30,7 +39,8 @@ namespace AaxDecrypter
|
|||||||
["Step 2: Download Decrypted Audiobook"] = Step_DownloadAudiobookAsMultipleFilesPerChapter,
|
["Step 2: Download Decrypted Audiobook"] = Step_DownloadAudiobookAsMultipleFilesPerChapter,
|
||||||
["Step 3: Cleanup"] = Step_Cleanup,
|
["Step 3: Cleanup"] = Step_Cleanup,
|
||||||
};
|
};
|
||||||
this.multipartFileNameCallback = multipartFileNameCallback ?? MultiConvertFileProperties.DefaultMultipartFilename;
|
this.multipartFileNameCallback = multipartFileNameCallback;
|
||||||
|
this.multipartTitleNameCallback = multipartTitleNameCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -103,48 +113,57 @@ That naming may not be desirable for everyone, but it's an easy change to instea
|
|||||||
private ConversionResult ConvertToMultiMp4a(ChapterInfo splitChapters)
|
private ConversionResult ConvertToMultiMp4a(ChapterInfo splitChapters)
|
||||||
{
|
{
|
||||||
var chapterCount = 0;
|
var chapterCount = 0;
|
||||||
return AaxFile.ConvertToMultiMp4a(splitChapters, newSplitCallback =>
|
return AaxFile.ConvertToMultiMp4a
|
||||||
{
|
(
|
||||||
createOutputFileStream(++chapterCount, splitChapters, newSplitCallback);
|
splitChapters,
|
||||||
|
newSplitCallback => Callback(++chapterCount, splitChapters, newSplitCallback),
|
||||||
newSplitCallback.TrackNumber = chapterCount;
|
DownloadOptions.TrimOutputToChapterLength
|
||||||
newSplitCallback.TrackCount = splitChapters.Count;
|
);
|
||||||
|
|
||||||
}, DownloadOptions.TrimOutputToChapterLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConversionResult ConvertToMultiMp3(ChapterInfo splitChapters)
|
private ConversionResult ConvertToMultiMp3(ChapterInfo splitChapters)
|
||||||
{
|
{
|
||||||
var chapterCount = 0;
|
var chapterCount = 0;
|
||||||
return AaxFile.ConvertToMultiMp3(splitChapters, newSplitCallback =>
|
return AaxFile.ConvertToMultiMp3
|
||||||
{
|
(
|
||||||
createOutputFileStream(++chapterCount, splitChapters, newSplitCallback);
|
splitChapters,
|
||||||
|
newSplitCallback => Callback(++chapterCount, splitChapters, newSplitCallback),
|
||||||
newSplitCallback.TrackNumber = chapterCount;
|
DownloadOptions.LameConfig,
|
||||||
newSplitCallback.TrackCount = splitChapters.Count;
|
DownloadOptions.TrimOutputToChapterLength
|
||||||
|
);
|
||||||
}, DownloadOptions.LameConfig, DownloadOptions.TrimOutputToChapterLength);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createOutputFileStream(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)
|
|
||||||
|
private void Callback(int currentChapter, ChapterInfo splitChapters, NewMP3SplitCallback newSplitCallback)
|
||||||
|
=> Callback(currentChapter, splitChapters, newSplitCallback);
|
||||||
|
|
||||||
|
private void Callback(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)
|
||||||
{
|
{
|
||||||
var fileName = multipartFileNameCallback(new()
|
MultiConvertFileProperties props = new()
|
||||||
{
|
{
|
||||||
OutputFileName = OutputFileName,
|
OutputFileName = OutputFileName,
|
||||||
PartsPosition = currentChapter,
|
PartsPosition = currentChapter,
|
||||||
PartsTotal = splitChapters.Count,
|
PartsTotal = splitChapters.Count,
|
||||||
Title = newSplitCallback?.Chapter?.Title,
|
Title = newSplitCallback?.Chapter?.Title,
|
||||||
|
};
|
||||||
|
newSplitCallback.OutputFile = createOutputFileStream(props);
|
||||||
|
newSplitCallback.TrackTitle = multipartTitleNameCallback(props);
|
||||||
|
newSplitCallback.TrackNumber = currentChapter;
|
||||||
|
newSplitCallback.TrackCount = splitChapters.Count;
|
||||||
|
}
|
||||||
|
|
||||||
});
|
private FileStream createOutputFileStream(MultiConvertFileProperties multiConvertFileProperties)
|
||||||
|
{
|
||||||
|
var fileName = multipartFileNameCallback(multiConvertFileProperties);
|
||||||
fileName = FileUtility.GetValidFilename(fileName);
|
fileName = FileUtility.GetValidFilename(fileName);
|
||||||
|
|
||||||
multiPartFilePaths.Add(fileName);
|
multiPartFilePaths.Add(fileName);
|
||||||
|
|
||||||
FileUtility.SaferDelete(fileName);
|
FileUtility.SaferDelete(fileName);
|
||||||
|
|
||||||
newSplitCallback.OutputFile = File.Open(fileName, FileMode.OpenOrCreate);
|
var file = File.Open(fileName, FileMode.OpenOrCreate);
|
||||||
|
|
||||||
OnFileCreated(fileName);
|
OnFileCreated(fileName);
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -126,6 +126,9 @@ namespace AppScaffolding
|
|||||||
if (!config.Exists(nameof(config.ChapterFileTemplate)))
|
if (!config.Exists(nameof(config.ChapterFileTemplate)))
|
||||||
config.ChapterFileTemplate = Templates.ChapterFile.DefaultTemplate;
|
config.ChapterFileTemplate = Templates.ChapterFile.DefaultTemplate;
|
||||||
|
|
||||||
|
if (!config.Exists(nameof(config.ChapterTitleTemplate)))
|
||||||
|
config.ChapterTitleTemplate = Templates.ChapterTitle.DefaultTemplate;
|
||||||
|
|
||||||
if (!config.Exists(nameof(config.AutoScan)))
|
if (!config.Exists(nameof(config.AutoScan)))
|
||||||
config.AutoScan = true;
|
config.AutoScan = true;
|
||||||
|
|
||||||
|
|||||||
@ -18,9 +18,22 @@ namespace FileLiberator
|
|||||||
=> Templates.ChapterFile.GetFilename(libraryBook.ToDto(), props);
|
=> Templates.ChapterFile.GetFilename(libraryBook.ToDto(), props);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class MultipartTitleNamer
|
||||||
|
{
|
||||||
|
private LibraryBook libraryBook { get; }
|
||||||
|
|
||||||
|
internal MultipartTitleNamer(LibraryBook libraryBook) => this.libraryBook = libraryBook;
|
||||||
|
|
||||||
|
internal string MultipartTitle(AaxDecrypter.MultiConvertFileProperties props)
|
||||||
|
=> Templates.ChapterTitle.GetTitle(libraryBook.ToDto(), props);
|
||||||
|
}
|
||||||
|
|
||||||
public static Func<AaxDecrypter.MultiConvertFileProperties, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBook libraryBook)
|
public static Func<AaxDecrypter.MultiConvertFileProperties, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBook libraryBook)
|
||||||
=> new MultipartRenamer(libraryBook).MultipartFilename;
|
=> new MultipartRenamer(libraryBook).MultipartFilename;
|
||||||
|
|
||||||
|
public static Func<AaxDecrypter.MultiConvertFileProperties, string> CreateMultipartTitleNamer(this AudioFileStorage _, LibraryBook libraryBook)
|
||||||
|
=> new MultipartTitleNamer(libraryBook).MultipartTitle;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DownloadDecryptBook:
|
/// DownloadDecryptBook:
|
||||||
/// File path for where to move files into.
|
/// File path for where to move files into.
|
||||||
|
|||||||
@ -112,9 +112,12 @@ namespace FileLiberator
|
|||||||
{
|
{
|
||||||
AaxcDownloadConvertBase converter
|
AaxcDownloadConvertBase converter
|
||||||
= config.SplitFilesByChapter ?
|
= config.SplitFilesByChapter ?
|
||||||
new AaxcDownloadMultiConverter(
|
new AaxcDownloadMultiConverter
|
||||||
|
(
|
||||||
outFileName, cacheDir, audiobookDlLic,
|
outFileName, cacheDir, audiobookDlLic,
|
||||||
AudibleFileStorage.Audio.CreateMultipartRenamerFunc(libraryBook)) :
|
AudibleFileStorage.Audio.CreateMultipartRenamerFunc(libraryBook),
|
||||||
|
AudibleFileStorage.Audio.CreateMultipartTitleNamer(libraryBook)
|
||||||
|
) :
|
||||||
new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic);
|
new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic);
|
||||||
|
|
||||||
if (config.AllowLibationFixup)
|
if (config.AllowLibationFixup)
|
||||||
|
|||||||
@ -2,104 +2,87 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Dinah.Core;
|
|
||||||
|
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
/// <summary>Get valid filename. Advanced features incl. parameterized template</summary>
|
/// <summary>Get valid filename. Advanced features incl. parameterized template</summary>
|
||||||
public class FileNamingTemplate
|
public class FileNamingTemplate : NamingTemplate
|
||||||
{
|
{
|
||||||
/// <summary>Proposed full file path. May contain optional html-styled template tags. Eg: <name></summary>
|
/// <param name="template">Proposed file name with optional html-styled template tags.</param>
|
||||||
public string Template { get; }
|
public FileNamingTemplate(string template) : base(template) { }
|
||||||
|
|
||||||
/// <param name="template">Proposed file name with optional html-styled template tags.</param>
|
/// <summary>Optional step 2: Replace all illegal characters with this. Default=<see cref="string.Empty"/></summary>
|
||||||
public FileNamingTemplate(string template) => Template = ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
public string IllegalCharacterReplacements { get; set; }
|
||||||
|
|
||||||
/// <summary>Optional step 1: Replace html-styled template tags with parameters. Eg {"name", "Bill Gates"} => /<name>/ => /Bill Gates/</summary>
|
/// <summary>Generate a valid path for this file or directory</summary>
|
||||||
public Dictionary<string, object> ParameterReplacements { get; } = new Dictionary<string, object>();
|
public LongPath GetFilePath(bool returnFirstExisting = false)
|
||||||
|
{
|
||||||
|
int lastSlash = Template.LastIndexOf('\\');
|
||||||
|
|
||||||
/// <summary>Convenience method</summary>
|
var directoryName = lastSlash >= 0 ? Template[..(lastSlash + 1)] : string.Empty;
|
||||||
public void AddParameterReplacement(string key, object value)
|
var filename = lastSlash >= 0 ? Template[(lastSlash + 1)..] : Template;
|
||||||
// using .Add() instead of "[key] = value" will make unintended overwriting throw exception
|
|
||||||
=> ParameterReplacements.Add(key, value);
|
|
||||||
|
|
||||||
/// <summary>Optional step 2: Replace all illegal characters with this. Default=<see cref="string.Empty"/></summary>
|
List<StringBuilder> filenameParts = new();
|
||||||
public string IllegalCharacterReplacements { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Generate a valid path for this file or directory</summary>
|
var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value));
|
||||||
public LongPath GetFilePath(bool returnFirstExisting = false)
|
|
||||||
{
|
|
||||||
int lastSlash = Template.LastIndexOf('\\');
|
|
||||||
|
|
||||||
var directoryName = lastSlash >= 0 ? Template[..(lastSlash + 1)] : string.Empty;
|
//Build the filename in parts, replacing replacement parameters with
|
||||||
var filename = lastSlash >= 0 ? Template[(lastSlash + 1)..] : Template;
|
//their values, and storing the parts in a list.
|
||||||
|
while(!string.IsNullOrEmpty(filename))
|
||||||
|
{
|
||||||
|
int openIndex = filename.IndexOf('<');
|
||||||
|
int closeIndex = filename.IndexOf('>');
|
||||||
|
|
||||||
List<StringBuilder> filenameParts = new();
|
if (openIndex == 0 && closeIndex > 0)
|
||||||
|
{
|
||||||
|
var key = filename[..(closeIndex + 1)];
|
||||||
|
|
||||||
var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value));
|
if (paramReplacements.ContainsKey(key))
|
||||||
|
filenameParts.Add(new StringBuilder(paramReplacements[key]));
|
||||||
|
else
|
||||||
|
filenameParts.Add(new StringBuilder(key));
|
||||||
|
|
||||||
//Build the filename in parts, replacing replacement parameters with
|
filename = filename[(closeIndex + 1)..];
|
||||||
//their values, and storing the parts in a list.
|
}
|
||||||
while(!string.IsNullOrEmpty(filename))
|
else if (openIndex > 0 && closeIndex > openIndex)
|
||||||
{
|
{
|
||||||
int openIndex = filename.IndexOf('<');
|
var other = filename[..openIndex];
|
||||||
int closeIndex = filename.IndexOf('>');
|
filenameParts.Add(new StringBuilder(other));
|
||||||
|
filename = filename[openIndex..];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
filenameParts.Add(new StringBuilder(filename));
|
||||||
|
filename = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (openIndex == 0 && closeIndex > 0)
|
//Remove 1 character from the end of the longest filename part until
|
||||||
{
|
//the total filename is less than max filename length
|
||||||
var key = filename[..(closeIndex + 1)];
|
while(filenameParts.Sum(p => p.Length) > LongPath.MaxFilenameLength)
|
||||||
|
{
|
||||||
|
int maxLength = filenameParts.Max(p => p.Length);
|
||||||
|
var maxEntry = filenameParts.First(p => p.Length == maxLength);
|
||||||
|
|
||||||
if (paramReplacements.ContainsKey(key))
|
maxEntry.Remove(maxLength - 1, 1);
|
||||||
filenameParts.Add(new StringBuilder(paramReplacements[key]));
|
}
|
||||||
else
|
|
||||||
filenameParts.Add(new StringBuilder(key));
|
|
||||||
|
|
||||||
filename = filename[(closeIndex + 1)..];
|
filename = string.Join("", filenameParts);
|
||||||
}
|
|
||||||
else if (openIndex > 0 && closeIndex > openIndex)
|
|
||||||
{
|
|
||||||
var other = filename[..openIndex];
|
|
||||||
filenameParts.Add(new StringBuilder(other));
|
|
||||||
filename = filename[openIndex..];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
filenameParts.Add(new StringBuilder(filename));
|
|
||||||
filename = string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Remove 1 character from the end of the longest filename part until
|
return FileUtility.GetValidFilename(directoryName + filename, IllegalCharacterReplacements, returnFirstExisting);
|
||||||
//the total filename is less than max filename length
|
}
|
||||||
while(filenameParts.Sum(p => p.Length) > LongPath.MaxFilenameLength)
|
|
||||||
{
|
|
||||||
int maxLength = filenameParts.Max(p => p.Length);
|
|
||||||
var maxEntry = filenameParts.First(p => p.Length == maxLength);
|
|
||||||
|
|
||||||
maxEntry.Remove(maxLength - 1, 1);
|
private string formatValue(object value)
|
||||||
}
|
{
|
||||||
|
if (value is null)
|
||||||
|
return "";
|
||||||
|
|
||||||
filename = string.Join("", filenameParts);
|
// Other illegal characters will be taken care of later. Must take care of slashes now so params can't introduce new folders.
|
||||||
|
// Esp important for file templates.
|
||||||
return FileUtility.GetValidFilename(directoryName + filename, IllegalCharacterReplacements, returnFirstExisting);
|
return value
|
||||||
}
|
.ToString()
|
||||||
|
.Replace($"{System.IO.Path.DirectorySeparatorChar}", IllegalCharacterReplacements)
|
||||||
private static string formatKey(string key)
|
.Replace($"{System.IO.Path.AltDirectorySeparatorChar}", IllegalCharacterReplacements);
|
||||||
=> key
|
}
|
||||||
.Replace("<", "")
|
}
|
||||||
.Replace(">", "");
|
|
||||||
|
|
||||||
private string formatValue(object value)
|
|
||||||
{
|
|
||||||
if (value is null)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
// Other illegal characters will be taken care of later. Must take care of slashes now so params can't introduce new folders.
|
|
||||||
// Esp important for file templates.
|
|
||||||
return value
|
|
||||||
.ToString()
|
|
||||||
.Replace($"{System.IO.Path.DirectorySeparatorChar}", IllegalCharacterReplacements)
|
|
||||||
.Replace($"{System.IO.Path.AltDirectorySeparatorChar}", IllegalCharacterReplacements);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,270 +9,265 @@ using Polly.Retry;
|
|||||||
|
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
public static class FileUtility
|
public static class FileUtility
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "txt" => ".txt"
|
/// "txt" => ".txt"
|
||||||
/// <br />".txt" => ".txt"
|
/// <br />".txt" => ".txt"
|
||||||
/// <br />null or whitespace => ""
|
/// <br />null or whitespace => ""
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string GetStandardizedExtension(string extension)
|
public static string GetStandardizedExtension(string extension)
|
||||||
=> string.IsNullOrWhiteSpace(extension)
|
=> string.IsNullOrWhiteSpace(extension)
|
||||||
? (extension ?? "")?.Trim()
|
? (extension ?? "")?.Trim()
|
||||||
: '.' + extension.Trim().Trim('.');
|
: '.' + extension.Trim().Trim('.');
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return position with correct number of leading zeros.
|
/// Return position with correct number of leading zeros.
|
||||||
/// <br />- 2 of 9 => "2"
|
/// <br />- 2 of 9 => "2"
|
||||||
/// <br />- 2 of 90 => "02"
|
/// <br />- 2 of 90 => "02"
|
||||||
/// <br />- 2 of 900 => "002"
|
/// <br />- 2 of 900 => "002"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="position">position in sequence. The 'x' in 'x of y'</param>
|
/// <param name="position">position in sequence. The 'x' in 'x of y'</param>
|
||||||
/// <param name="total">total qty in sequence. The 'y' in 'x of y'</param>
|
/// <param name="total">total qty in sequence. The 'y' in 'x of y'</param>
|
||||||
public static string GetSequenceFormatted(int position, int total)
|
public static string GetSequenceFormatted(int position, int total)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureGreaterThan(position, nameof(position), 0);
|
ArgumentValidator.EnsureGreaterThan(position, nameof(position), 0);
|
||||||
ArgumentValidator.EnsureGreaterThan(total, nameof(total), 0);
|
ArgumentValidator.EnsureGreaterThan(total, nameof(total), 0);
|
||||||
if (position > total)
|
if (position > total)
|
||||||
throw new ArgumentException($"{position} may not be greater than {total}");
|
throw new ArgumentException($"{position} may not be greater than {total}");
|
||||||
|
|
||||||
return position.ToString().PadLeft(total.ToString().Length, '0');
|
return position.ToString().PadLeft(total.ToString().Length, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensure valid file name path:
|
/// Ensure valid file name path:
|
||||||
/// <br/>- remove invalid chars
|
/// <br/>- remove invalid chars
|
||||||
/// <br/>- ensure uniqueness
|
/// <br/>- ensure uniqueness
|
||||||
/// <br/>- enforce max file length
|
/// <br/>- enforce max file length
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LongPath GetValidFilename(LongPath path, string illegalCharacterReplacements = "", bool returnFirstExisting = false)
|
public static LongPath GetValidFilename(LongPath path, string illegalCharacterReplacements = "", bool returnFirstExisting = false)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
||||||
|
|
||||||
// remove invalid chars
|
// remove invalid chars
|
||||||
path = GetSafePath(path, illegalCharacterReplacements);
|
path = GetSafePath(path, illegalCharacterReplacements);
|
||||||
|
|
||||||
// ensure uniqueness and check lengths
|
// ensure uniqueness and check lengths
|
||||||
var dir = Path.GetDirectoryName(path);
|
var dir = Path.GetDirectoryName(path);
|
||||||
dir = dir?.Truncate(LongPath.MaxDirectoryLength) ?? string.Empty;
|
dir = dir?.Truncate(LongPath.MaxDirectoryLength) ?? string.Empty;
|
||||||
|
|
||||||
var extension = Path.GetExtension(path);
|
var extension = Path.GetExtension(path);
|
||||||
|
|
||||||
var filename = Path.GetFileNameWithoutExtension(path).Truncate(LongPath.MaxFilenameLength - extension.Length);
|
var filename = Path.GetFileNameWithoutExtension(path).Truncate(LongPath.MaxFilenameLength - extension.Length);
|
||||||
var fileStem = Path.Combine(dir, filename);
|
var fileStem = Path.Combine(dir, filename);
|
||||||
|
|
||||||
|
|
||||||
var fullfilename = fileStem.Truncate(LongPath.MaxPathLength - extension.Length) + extension;
|
var fullfilename = fileStem.Truncate(LongPath.MaxPathLength - extension.Length) + extension;
|
||||||
|
|
||||||
fullfilename = removeInvalidWhitespace(fullfilename);
|
fullfilename = removeInvalidWhitespace(fullfilename);
|
||||||
|
|
||||||
var i = 0;
|
var i = 0;
|
||||||
while (File.Exists(fullfilename) && !returnFirstExisting)
|
while (File.Exists(fullfilename) && !returnFirstExisting)
|
||||||
{
|
{
|
||||||
var increm = $" ({++i})";
|
var increm = $" ({++i})";
|
||||||
fullfilename = fileStem.Truncate(LongPath.MaxPathLength - increm.Length - extension.Length) + increm + extension;
|
fullfilename = fileStem.Truncate(LongPath.MaxPathLength - increm.Length - extension.Length) + increm + extension;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fullfilename;
|
return fullfilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInvalidFileNameChars contains everything in GetInvalidPathChars plus ':', '*', '?', '\\', '/'
|
// GetInvalidFileNameChars contains everything in GetInvalidPathChars plus ':', '*', '?', '\\', '/'
|
||||||
|
|
||||||
/// <summary>Use with file name, not full path. Valid path charaters which are invalid file name characters will be replaced: ':', '\\', '/'</summary>
|
/// <summary>Use with file name, not full path. Valid path charaters which are invalid file name characters will be replaced: ':', '\\', '/'</summary>
|
||||||
public static string GetSafeFileName(string str, string illegalCharacterReplacements = "")
|
public static string GetSafeFileName(string str, string illegalCharacterReplacements = "")
|
||||||
=> string.Join(illegalCharacterReplacements ?? "", str.Split(Path.GetInvalidFileNameChars()));
|
=> string.Join(illegalCharacterReplacements ?? "", str.Split(Path.GetInvalidFileNameChars()));
|
||||||
|
|
||||||
/// <summary>Use with full path, not file name. Valid path charaters which are invalid file name characters will be retained: '\\', '/'</summary>
|
/// <summary>Use with full path, not file name. Valid path charaters which are invalid file name characters will be retained: '\\', '/'</summary>
|
||||||
public static LongPath GetSafePath(LongPath path, string illegalCharacterReplacements = "")
|
public static LongPath GetSafePath(LongPath path, string illegalCharacterReplacements = "")
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
||||||
|
|
||||||
var pathNoPrefix = path.PathWithoutPrefix;
|
var pathNoPrefix = path.PathWithoutPrefix;
|
||||||
|
|
||||||
pathNoPrefix = replaceInvalidChars(pathNoPrefix, illegalCharacterReplacements);
|
pathNoPrefix = replaceInvalidChars(pathNoPrefix, illegalCharacterReplacements);
|
||||||
pathNoPrefix = standardizeSlashes(pathNoPrefix);
|
pathNoPrefix = standardizeSlashes(pathNoPrefix);
|
||||||
pathNoPrefix = replaceColons(pathNoPrefix, illegalCharacterReplacements);
|
pathNoPrefix = replaceColons(pathNoPrefix, illegalCharacterReplacements);
|
||||||
pathNoPrefix = removeDoubleSlashes(pathNoPrefix);
|
pathNoPrefix = removeDoubleSlashes(pathNoPrefix);
|
||||||
|
|
||||||
return pathNoPrefix;
|
return pathNoPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char[] invalidChars { get; } = Path.GetInvalidPathChars().Union(new[] {
|
private static char[] invalidChars { get; } = Path.GetInvalidPathChars().Union(new[] {
|
||||||
'*', '?',
|
'*', '?',
|
||||||
// these are weird. If you run Path.GetInvalidPathChars() in Visual Studio's "C# Interactive", then these characters are included.
|
// these are weird. If you run Path.GetInvalidPathChars() in Visual Studio's "C# Interactive", then these characters are included.
|
||||||
// In live code, Path.GetInvalidPathChars() does not include them
|
// In live code, Path.GetInvalidPathChars() does not include them
|
||||||
'"', '<', '>'
|
'"', '<', '>'
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
private static string replaceInvalidChars(string path, string illegalCharacterReplacements)
|
private static string replaceInvalidChars(string path, string illegalCharacterReplacements)
|
||||||
=> string.Join(illegalCharacterReplacements ?? "", path.Split(invalidChars));
|
=> string.Join(illegalCharacterReplacements ?? "", path.Split(invalidChars));
|
||||||
|
|
||||||
private static string standardizeSlashes(string path)
|
private static string standardizeSlashes(string path)
|
||||||
=> path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
=> path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
private static string replaceColons(string path, string illegalCharacterReplacements)
|
private static string replaceColons(string path, string illegalCharacterReplacements)
|
||||||
{
|
{
|
||||||
// replace all colons except within the first 2 chars
|
// replace all colons except within the first 2 chars
|
||||||
var builder = new System.Text.StringBuilder();
|
var builder = new System.Text.StringBuilder();
|
||||||
for (var i = 0; i < path.Length; i++)
|
for (var i = 0; i < path.Length; i++)
|
||||||
{
|
{
|
||||||
var c = path[i];
|
var c = path[i];
|
||||||
if (i >= 2 && c == ':')
|
if (i >= 2 && c == ':')
|
||||||
builder.Append(illegalCharacterReplacements);
|
builder.Append(illegalCharacterReplacements);
|
||||||
else
|
else
|
||||||
builder.Append(c);
|
builder.Append(c);
|
||||||
}
|
}
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string removeDoubleSlashes(string path)
|
private static string removeDoubleSlashes(string path)
|
||||||
{
|
{
|
||||||
if (path.Length < 2)
|
if (path.Length < 2)
|
||||||
return path;
|
return path;
|
||||||
|
|
||||||
// exception: don't try to condense the initial dbl bk slashes in a path. eg: \\192.168.0.1
|
// exception: don't try to condense the initial dbl bk slashes in a path. eg: \\192.168.0.1
|
||||||
|
|
||||||
var remainder = path[1..];
|
var remainder = path[1..];
|
||||||
var dblSeparator = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}";
|
var dblSeparator = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}";
|
||||||
while (remainder.Contains(dblSeparator))
|
while (remainder.Contains(dblSeparator))
|
||||||
remainder = remainder.Replace(dblSeparator, $"{Path.DirectorySeparatorChar}");
|
remainder = remainder.Replace(dblSeparator, $"{Path.DirectorySeparatorChar}");
|
||||||
|
|
||||||
return path[0] + remainder;
|
return path[0] + remainder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string removeInvalidWhitespace_pattern { get; } = $@"[\s\.]*\{Path.DirectorySeparatorChar}\s*";
|
private static string removeInvalidWhitespace_pattern { get; } = $@"[\s\.]*\{Path.DirectorySeparatorChar}\s*";
|
||||||
private static Regex removeInvalidWhitespace_regex { get; } = new(removeInvalidWhitespace_pattern, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
|
private static Regex removeInvalidWhitespace_regex { get; } = new(removeInvalidWhitespace_pattern, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
|
||||||
|
|
||||||
/// <summary>no part of the path may begin or end in whitespace</summary>
|
/// <summary>no part of the path may begin or end in whitespace</summary>
|
||||||
private static string removeInvalidWhitespace(string fullfilename)
|
private static string removeInvalidWhitespace(string fullfilename)
|
||||||
{
|
{
|
||||||
// no whitespace at beginning or end
|
// no whitespace at beginning or end
|
||||||
// replace whitespace around path slashes
|
// replace whitespace around path slashes
|
||||||
// regex (with space added for clarity)
|
// regex (with space added for clarity)
|
||||||
// \s* \\ \s* => \
|
// \s* \\ \s* => \
|
||||||
// no ending dots. beginning dots are valid
|
// no ending dots. beginning dots are valid
|
||||||
|
|
||||||
// regex is easier by ending with separator
|
// regex is easier by ending with separator
|
||||||
fullfilename += Path.DirectorySeparatorChar;
|
fullfilename += Path.DirectorySeparatorChar;
|
||||||
fullfilename = removeInvalidWhitespace_regex.Replace(fullfilename, Path.DirectorySeparatorChar.ToString());
|
fullfilename = removeInvalidWhitespace_regex.Replace(fullfilename, Path.DirectorySeparatorChar.ToString());
|
||||||
// take seperator back off
|
// take seperator back off
|
||||||
fullfilename = RemoveLastCharacter(fullfilename);
|
fullfilename = RemoveLastCharacter(fullfilename);
|
||||||
|
|
||||||
fullfilename = removeDoubleSlashes(fullfilename);
|
fullfilename = removeDoubleSlashes(fullfilename);
|
||||||
return fullfilename;
|
return fullfilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string RemoveLastCharacter(this string str) => string.IsNullOrEmpty(str) ? str : str[..^1];
|
public static string RemoveLastCharacter(this string str) => string.IsNullOrEmpty(str) ? str : str[..^1];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Move file.
|
/// Move file.
|
||||||
/// <br/>- Ensure valid file name path: remove invalid chars, ensure uniqueness, enforce max file length
|
/// <br/>- Ensure valid file name path: remove invalid chars, ensure uniqueness, enforce max file length
|
||||||
/// <br/>- Perform <see cref="SaferMove"/>
|
/// <br/>- Perform <see cref="SaferMove"/>
|
||||||
/// <br/>- Return valid path
|
/// <br/>- Return valid path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string SaferMoveToValidPath(LongPath source, LongPath destination)
|
public static string SaferMoveToValidPath(LongPath source, LongPath destination)
|
||||||
{
|
{
|
||||||
destination = GetValidFilename(destination);
|
destination = GetValidFilename(destination);
|
||||||
SaferMove(source, destination);
|
SaferMove(source, destination);
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int maxRetryAttempts { get; } = 3;
|
private static int maxRetryAttempts { get; } = 3;
|
||||||
private static TimeSpan pauseBetweenFailures { get; } = TimeSpan.FromMilliseconds(100);
|
private static TimeSpan pauseBetweenFailures { get; } = TimeSpan.FromMilliseconds(100);
|
||||||
private static RetryPolicy retryPolicy { get; } =
|
private static RetryPolicy retryPolicy { get; } =
|
||||||
Policy
|
Policy
|
||||||
.Handle<Exception>()
|
.Handle<Exception>()
|
||||||
.WaitAndRetry(maxRetryAttempts, i => pauseBetweenFailures);
|
.WaitAndRetry(maxRetryAttempts, i => pauseBetweenFailures);
|
||||||
|
|
||||||
/// <summary>Delete file. No error when source does not exist. Retry up to 3 times before throwing exception.</summary>
|
/// <summary>Delete file. No error when source does not exist. Retry up to 3 times before throwing exception.</summary>
|
||||||
public static void SaferDelete(LongPath source)
|
public static void SaferDelete(LongPath source)
|
||||||
=> retryPolicy.Execute(() =>
|
=> retryPolicy.Execute(() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!File.Exists(source))
|
if (!File.Exists(source))
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Debug("No file to delete: {@DebugText}", new { source });
|
Serilog.Log.Logger.Debug("No file to delete: {@DebugText}", new { source });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Serilog.Log.Logger.Debug("Attempt to delete file: {@DebugText}", new { source });
|
Serilog.Log.Logger.Debug("Attempt to delete file: {@DebugText}", new { source });
|
||||||
File.Delete(source);
|
File.Delete(source);
|
||||||
Serilog.Log.Logger.Information("File successfully deleted: {@DebugText}", new { source });
|
Serilog.Log.Logger.Information("File successfully deleted: {@DebugText}", new { source });
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Error(e, "Failed to delete file: {@DebugText}", new { source });
|
Serilog.Log.Logger.Error(e, "Failed to delete file: {@DebugText}", new { source });
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/// <summary>Move file. No error when source does not exist. Retry up to 3 times before throwing exception.</summary>
|
/// <summary>Move file. No error when source does not exist. Retry up to 3 times before throwing exception.</summary>
|
||||||
public static void SaferMove(LongPath source, LongPath destination)
|
public static void SaferMove(LongPath source, LongPath destination)
|
||||||
=> retryPolicy.Execute(() =>
|
=> retryPolicy.Execute(() =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!File.Exists(source))
|
if (!File.Exists(source))
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Debug("No file to move: {@DebugText}", new { source });
|
Serilog.Log.Logger.Debug("No file to move: {@DebugText}", new { source });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SaferDelete(destination);
|
SaferDelete(destination);
|
||||||
|
|
||||||
var dir = Path.GetDirectoryName(destination);
|
var dir = Path.GetDirectoryName(destination);
|
||||||
Serilog.Log.Logger.Debug("Attempt to create directory: {@DebugText}", new { dir });
|
Serilog.Log.Logger.Debug("Attempt to create directory: {@DebugText}", new { dir });
|
||||||
Directory.CreateDirectory(dir);
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
Serilog.Log.Logger.Debug("Attempt to move file: {@DebugText}", new { source, destination });
|
Serilog.Log.Logger.Debug("Attempt to move file: {@DebugText}", new { source, destination });
|
||||||
File.Move(source, destination);
|
File.Move(source, destination);
|
||||||
Serilog.Log.Logger.Information("File successfully moved: {@DebugText}", new { source, destination });
|
Serilog.Log.Logger.Information("File successfully moved: {@DebugText}", new { source, destination });
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Error(e, "Failed to move file: {@DebugText}", new { source, destination });
|
Serilog.Log.Logger.Error(e, "Failed to move file: {@DebugText}", new { source, destination });
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A safer way to get all the files in a directory and sub directory without crashing on UnauthorizedException or PathTooLongException
|
/// A safer way to get all the files in a directory and sub directory without crashing on UnauthorizedException or PathTooLongException
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rootPath">Starting directory</param>
|
/// <param name="rootPath">Starting directory</param>
|
||||||
/// <param name="patternMatch">Filename pattern match</param>
|
/// <param name="patternMatch">Filename pattern match</param>
|
||||||
/// <param name="searchOption">Search subdirectories or only top level directory for files</param>
|
/// <param name="searchOption">Search subdirectories or only top level directory for files</param>
|
||||||
/// <returns>List of files</returns>
|
/// <returns>List of files</returns>
|
||||||
public static IEnumerable<LongPath> SaferEnumerateFiles(LongPath path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
public static IEnumerable<LongPath> SaferEnumerateFiles(LongPath path, string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly)
|
||||||
{
|
{
|
||||||
var foundFiles = Enumerable.Empty<LongPath>();
|
var foundFiles = Enumerable.Empty<LongPath>();
|
||||||
|
|
||||||
if (searchOption == SearchOption.AllDirectories)
|
if (searchOption == SearchOption.AllDirectories)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var list = Directory.EnumerateDirectories(path).ToList();
|
IEnumerable <LongPath> subDirs = Directory.EnumerateDirectories(path).Select(p => (LongPath)p);
|
||||||
IEnumerable <LongPath> subDirs = Directory.EnumerateDirectories(path).Select(p => (LongPath)p);
|
// Add files in subdirectories recursively to the list
|
||||||
// Add files in subdirectories recursively to the list
|
foreach (string dir in subDirs)
|
||||||
foreach (string dir in subDirs)
|
foundFiles = foundFiles.Concat(SaferEnumerateFiles(dir, searchPattern, searchOption));
|
||||||
foundFiles = foundFiles.Concat(SaferEnumerateFiles(dir, searchPattern, searchOption));
|
}
|
||||||
}
|
catch (UnauthorizedAccessException) { }
|
||||||
catch (UnauthorizedAccessException) { }
|
catch (PathTooLongException) { }
|
||||||
catch (PathTooLongException) { }
|
}
|
||||||
catch(Exception ex)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
try
|
||||||
}
|
{
|
||||||
|
// Add files from the current directory
|
||||||
|
foundFiles = foundFiles.Concat(Directory.EnumerateFiles(path, searchPattern).Select(f => (LongPath)f));
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException) { }
|
||||||
|
|
||||||
try
|
return foundFiles;
|
||||||
{
|
}
|
||||||
// Add files from the current directory
|
}
|
||||||
foundFiles = foundFiles.Concat(Directory.EnumerateFiles(path, searchPattern).Select(f => (LongPath)f));
|
|
||||||
}
|
|
||||||
catch (UnauthorizedAccessException) { }
|
|
||||||
|
|
||||||
return foundFiles;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
20
Source/FileManager/MetadataNamingTemplate.cs
Normal file
20
Source/FileManager/MetadataNamingTemplate.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FileManager
|
||||||
|
{
|
||||||
|
public class MetadataNamingTemplate : NamingTemplate
|
||||||
|
{
|
||||||
|
public MetadataNamingTemplate(string template) : base(template) { }
|
||||||
|
|
||||||
|
public string GetTagContents()
|
||||||
|
{
|
||||||
|
var tagValue = Template;
|
||||||
|
|
||||||
|
foreach (var r in ParameterReplacements)
|
||||||
|
tagValue = tagValue.Replace($"<{formatKey(r.Key)}>", r.Value.ToString());
|
||||||
|
|
||||||
|
return tagValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Source/FileManager/NamingTemplate.cs
Normal file
28
Source/FileManager/NamingTemplate.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
using Dinah.Core;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace FileManager
|
||||||
|
{
|
||||||
|
public class NamingTemplate
|
||||||
|
{
|
||||||
|
/// <summary>Proposed full name. May contain optional html-styled template tags. Eg: <name></summary>
|
||||||
|
public string Template { get; }
|
||||||
|
|
||||||
|
/// <param name="template">Proposed file name with optional html-styled template tags.</param>
|
||||||
|
public NamingTemplate(string template) => Template = ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
||||||
|
|
||||||
|
/// <summary>Optional step 1: Replace html-styled template tags with parameters. Eg {"name", "Bill Gates"} => /<name>/ => /Bill Gates/</summary>
|
||||||
|
public Dictionary<string, object> ParameterReplacements { get; } = new Dictionary<string, object>();
|
||||||
|
|
||||||
|
/// <summary>Convenience method</summary>
|
||||||
|
public void AddParameterReplacement(string key, object value)
|
||||||
|
// using .Add() instead of "[key] = value" will make unintended overwriting throw exception
|
||||||
|
=> ParameterReplacements.Add(key, value);
|
||||||
|
|
||||||
|
protected static string formatKey(string key)
|
||||||
|
=> key
|
||||||
|
.Replace("<", "")
|
||||||
|
.Replace(">", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,514 +14,521 @@ using Serilog.Events;
|
|||||||
|
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public class Configuration
|
public class Configuration
|
||||||
{
|
{
|
||||||
public bool LibationSettingsAreValid
|
public bool LibationSettingsAreValid
|
||||||
=> File.Exists(APPSETTINGS_JSON)
|
=> File.Exists(APPSETTINGS_JSON)
|
||||||
&& SettingsFileIsValid(SettingsFilePath);
|
&& SettingsFileIsValid(SettingsFilePath);
|
||||||
|
|
||||||
public static bool SettingsFileIsValid(string settingsFile)
|
public static bool SettingsFileIsValid(string settingsFile)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(Path.GetDirectoryName(settingsFile)) || !File.Exists(settingsFile))
|
if (!Directory.Exists(Path.GetDirectoryName(settingsFile)) || !File.Exists(settingsFile))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var pDic = new PersistentDictionary(settingsFile, isReadOnly: true);
|
var pDic = new PersistentDictionary(settingsFile, isReadOnly: true);
|
||||||
|
|
||||||
var booksDir = pDic.GetString(nameof(Books));
|
var booksDir = pDic.GetString(nameof(Books));
|
||||||
if (booksDir is null || !Directory.Exists(booksDir))
|
if (booksDir is null || !Directory.Exists(booksDir))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(pDic.GetString(nameof(InProgress))))
|
if (string.IsNullOrWhiteSpace(pDic.GetString(nameof(InProgress))))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
#region persistent configuration settings/values
|
|
||||||
|
|
||||||
// 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 T GetNonString<T>(string propertyName) => persistentDictionary.GetNonString<T>(propertyName);
|
|
||||||
public object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName);
|
|
||||||
public void SetObject(string propertyName, object newValue) => persistentDictionary.SetNonString(propertyName, newValue);
|
|
||||||
|
|
||||||
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
|
||||||
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
|
||||||
{
|
|
||||||
var settingWasChanged = persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue, suppressLogging);
|
|
||||||
if (settingWasChanged)
|
|
||||||
configuration?.Reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetString(nameof(Books));
|
|
||||||
set => persistentDictionary.SetString(nameof(Books), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.SetString(nameof(InProgress), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Allow Libation to fix up audiobook metadata")]
|
|
||||||
public bool AllowLibationFixup
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(AllowLibationFixup));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(AllowLibationFixup), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Create a cue sheet (.cue)")]
|
|
||||||
public bool CreateCueSheet
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(CreateCueSheet));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(CreateCueSheet), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Retain the Aax file after successfully decrypting")]
|
|
||||||
public bool RetainAaxFile
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(RetainAaxFile));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(RetainAaxFile), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Split my books into multiple files by chapter")]
|
|
||||||
public bool SplitFilesByChapter
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(SplitFilesByChapter));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
|
|
||||||
public bool StripUnabridged
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(StripUnabridged));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(StripUnabridged), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Allow Libation to remove audible branding from the start\r\nand end of audiobooks. (e.g. \"This is Audible\")")]
|
|
||||||
public bool StripAudibleBrandAudio
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(StripAudibleBrandAudio));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(StripAudibleBrandAudio), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Decrypt to lossy format?")]
|
|
||||||
public bool DecryptToLossy
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(DecryptToLossy));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(DecryptToLossy), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Lame encoder target. true = Bitrate, false = Quality")]
|
|
||||||
public bool LameTargetBitrate
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(LameTargetBitrate));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameTargetBitrate), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Lame encoder downsamples to mono")]
|
|
||||||
public bool LameDownsampleMono
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(LameDownsampleMono));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameDownsampleMono), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Lame target bitrate [16,320]")]
|
|
||||||
public int LameBitrate
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<int>(nameof(LameBitrate));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameBitrate), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Restrict encoder to constant bitrate?")]
|
|
||||||
public bool LameConstantBitrate
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(LameConstantBitrate));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameConstantBitrate), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Match the source bitrate?")]
|
|
||||||
public bool LameMatchSourceBR
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(LameMatchSourceBR));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameMatchSourceBR), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Lame target VBR quality [10,100]")]
|
|
||||||
public int LameVBRQuality
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<int>(nameof(LameVBRQuality));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
|
|
||||||
public Dictionary<string, bool> GridColumnsVisibilities
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<Dictionary<string, bool>>(nameof(GridColumnsVisibilities));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(GridColumnsVisibilities), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
|
|
||||||
public Dictionary<string, int> GridColumnsDisplayIndices
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<Dictionary<string,int>>(nameof(GridColumnsDisplayIndices));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(GridColumnsDisplayIndices), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
|
|
||||||
public Dictionary<string, int> GridColumnsWidths
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<Dictionary<string,int>>(nameof(GridColumnsWidths));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(GridColumnsWidths), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Save cover image alongside audiobook?")]
|
|
||||||
public bool DownloadCoverArt
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(DownloadCoverArt));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(DownloadCoverArt), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum BadBookAction
|
|
||||||
{
|
|
||||||
[Description("Ask each time what action to take.")]
|
|
||||||
Ask = 0,
|
|
||||||
[Description("Stop processing books.")]
|
|
||||||
Abort = 1,
|
|
||||||
[Description("Retry book later. Skip for now. Continue processing books.")]
|
|
||||||
Retry = 2,
|
|
||||||
[Description("Permanently ignore book. Continue processing books. Do not try book again.")]
|
|
||||||
Ignore = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("When liberating books and there is an error, Libation should:")]
|
|
||||||
public BadBookAction BadBook
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var badBookStr = persistentDictionary.GetString(nameof(BadBook));
|
|
||||||
return Enum.TryParse<BadBookAction>(badBookStr, out var badBookEnum) ? badBookEnum : BadBookAction.Ask;
|
|
||||||
}
|
|
||||||
set => persistentDictionary.SetString(nameof(BadBook), value.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")]
|
|
||||||
public bool ShowImportedStats
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(ShowImportedStats));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(ShowImportedStats), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
|
|
||||||
public bool ImportEpisodes
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(ImportEpisodes));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(ImportEpisodes), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
|
|
||||||
public bool DownloadEpisodes
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public event EventHandler AutoScanChanged;
|
#region persistent configuration settings/values
|
||||||
|
|
||||||
[Description("Automatically run periodic scans in the background?")]
|
// 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
|
||||||
public bool AutoScan
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(AutoScan));
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (AutoScan != value)
|
|
||||||
{
|
|
||||||
persistentDictionary.SetNonString(nameof(AutoScan), value);
|
|
||||||
AutoScanChanged?.Invoke(null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Auto download episodes? After scan, download new books in 'checked' accounts.")]
|
// default setting and directory creation occur in class responsible for files.
|
||||||
public bool AutoDownloadEpisodes
|
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
|
||||||
{
|
// exceptions: appsettings.json, LibationFiles dir, Settings.json
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(AutoDownloadEpisodes));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(AutoDownloadEpisodes), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("Save all podcast episodes in a series to the series parent folder?")]
|
private PersistentDictionary persistentDictionary;
|
||||||
public bool SavePodcastsToParentFolder
|
|
||||||
{
|
|
||||||
get => persistentDictionary.GetNonString<bool>(nameof(SavePodcastsToParentFolder));
|
|
||||||
set => persistentDictionary.SetNonString(nameof(SavePodcastsToParentFolder), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region templates: custom file naming
|
public T GetNonString<T>(string propertyName) => persistentDictionary.GetNonString<T>(propertyName);
|
||||||
|
public object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName);
|
||||||
|
public void SetObject(string propertyName, object newValue) => persistentDictionary.SetNonString(propertyName, newValue);
|
||||||
|
|
||||||
[Description("How to format the folders in which files will be saved")]
|
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
||||||
|
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
||||||
|
{
|
||||||
|
var settingWasChanged = persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue, suppressLogging);
|
||||||
|
if (settingWasChanged)
|
||||||
|
configuration?.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetString(nameof(Books));
|
||||||
|
set => persistentDictionary.SetString(nameof(Books), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.SetString(nameof(InProgress), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Allow Libation to fix up audiobook metadata")]
|
||||||
|
public bool AllowLibationFixup
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(AllowLibationFixup));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(AllowLibationFixup), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Create a cue sheet (.cue)")]
|
||||||
|
public bool CreateCueSheet
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(CreateCueSheet));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(CreateCueSheet), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Retain the Aax file after successfully decrypting")]
|
||||||
|
public bool RetainAaxFile
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(RetainAaxFile));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(RetainAaxFile), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Split my books into multiple files by chapter")]
|
||||||
|
public bool SplitFilesByChapter
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(SplitFilesByChapter));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
|
||||||
|
public bool StripUnabridged
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(StripUnabridged));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(StripUnabridged), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Strip audible branding from the start and end of audiobooks.\r\n(e.g. \"This is Audible\")")]
|
||||||
|
public bool StripAudibleBrandAudio
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(StripAudibleBrandAudio));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(StripAudibleBrandAudio), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Decrypt to lossy format?")]
|
||||||
|
public bool DecryptToLossy
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(DecryptToLossy));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(DecryptToLossy), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Lame encoder target. true = Bitrate, false = Quality")]
|
||||||
|
public bool LameTargetBitrate
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(LameTargetBitrate));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(LameTargetBitrate), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Lame encoder downsamples to mono")]
|
||||||
|
public bool LameDownsampleMono
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(LameDownsampleMono));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(LameDownsampleMono), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Lame target bitrate [16,320]")]
|
||||||
|
public int LameBitrate
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<int>(nameof(LameBitrate));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(LameBitrate), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Restrict encoder to constant bitrate?")]
|
||||||
|
public bool LameConstantBitrate
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(LameConstantBitrate));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(LameConstantBitrate), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Match the source bitrate?")]
|
||||||
|
public bool LameMatchSourceBR
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(LameMatchSourceBR));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(LameMatchSourceBR), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Lame target VBR quality [10,100]")]
|
||||||
|
public int LameVBRQuality
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<int>(nameof(LameVBRQuality));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(LameVBRQuality), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("A Dictionary of GridView data property names and bool indicating its column's visibility in ProductsGrid")]
|
||||||
|
public Dictionary<string, bool> GridColumnsVisibilities
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<Dictionary<string, bool>>(nameof(GridColumnsVisibilities));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(GridColumnsVisibilities), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("A Dictionary of GridView data property names and int indicating its column's display index in ProductsGrid")]
|
||||||
|
public Dictionary<string, int> GridColumnsDisplayIndices
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<Dictionary<string,int>>(nameof(GridColumnsDisplayIndices));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(GridColumnsDisplayIndices), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("A Dictionary of GridView data property names and int indicating its column's width in ProductsGrid")]
|
||||||
|
public Dictionary<string, int> GridColumnsWidths
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<Dictionary<string,int>>(nameof(GridColumnsWidths));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(GridColumnsWidths), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Save cover image alongside audiobook?")]
|
||||||
|
public bool DownloadCoverArt
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(DownloadCoverArt));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(DownloadCoverArt), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum BadBookAction
|
||||||
|
{
|
||||||
|
[Description("Ask each time what action to take.")]
|
||||||
|
Ask = 0,
|
||||||
|
[Description("Stop processing books.")]
|
||||||
|
Abort = 1,
|
||||||
|
[Description("Retry book later. Skip for now. Continue processing books.")]
|
||||||
|
Retry = 2,
|
||||||
|
[Description("Permanently ignore book. Continue processing books. Do not try book again.")]
|
||||||
|
Ignore = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("When liberating books and there is an error, Libation should:")]
|
||||||
|
public BadBookAction BadBook
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var badBookStr = persistentDictionary.GetString(nameof(BadBook));
|
||||||
|
return Enum.TryParse<BadBookAction>(badBookStr, out var badBookEnum) ? badBookEnum : BadBookAction.Ask;
|
||||||
|
}
|
||||||
|
set => persistentDictionary.SetString(nameof(BadBook), value.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Show number of newly imported titles? When unchecked, no pop-up will appear after library scan.")]
|
||||||
|
public bool ShowImportedStats
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(ShowImportedStats));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(ShowImportedStats), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Import episodes? (eg: podcasts) When unchecked, episodes will not be imported into Libation.")]
|
||||||
|
public bool ImportEpisodes
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(ImportEpisodes));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(ImportEpisodes), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Download episodes? (eg: podcasts). When unchecked, episodes already in Libation will not be downloaded.")]
|
||||||
|
public bool DownloadEpisodes
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event EventHandler AutoScanChanged;
|
||||||
|
|
||||||
|
[Description("Automatically run periodic scans in the background?")]
|
||||||
|
public bool AutoScan
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(AutoScan));
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (AutoScan != value)
|
||||||
|
{
|
||||||
|
persistentDictionary.SetNonString(nameof(AutoScan), value);
|
||||||
|
AutoScanChanged?.Invoke(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Auto download episodes? After scan, download new books in 'checked' accounts.")]
|
||||||
|
public bool AutoDownloadEpisodes
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(AutoDownloadEpisodes));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(AutoDownloadEpisodes), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Save all podcast episodes in a series to the series parent folder?")]
|
||||||
|
public bool SavePodcastsToParentFolder
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<bool>(nameof(SavePodcastsToParentFolder));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(SavePodcastsToParentFolder), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region templates: custom file naming
|
||||||
|
|
||||||
|
[Description("How to format the folders in which files will be saved")]
|
||||||
public string FolderTemplate
|
public string FolderTemplate
|
||||||
{
|
{
|
||||||
get => getTemplate(nameof(FolderTemplate), Templates.Folder);
|
get => getTemplate(nameof(FolderTemplate), Templates.Folder);
|
||||||
set => setTemplate(nameof(FolderTemplate), Templates.Folder, value);
|
set => setTemplate(nameof(FolderTemplate), Templates.Folder, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Description("How to format the saved pdf and audio files")]
|
[Description("How to format the saved pdf and audio files")]
|
||||||
public string FileTemplate
|
public string FileTemplate
|
||||||
{
|
{
|
||||||
get => getTemplate(nameof(FileTemplate), Templates.File);
|
get => getTemplate(nameof(FileTemplate), Templates.File);
|
||||||
set => setTemplate(nameof(FileTemplate), Templates.File, value);
|
set => setTemplate(nameof(FileTemplate), Templates.File, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Description("How to format the saved audio files when split by chapters")]
|
[Description("How to format the saved audio files when split by chapters")]
|
||||||
public string ChapterFileTemplate
|
public string ChapterFileTemplate
|
||||||
{
|
{
|
||||||
get => getTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile);
|
get => getTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile);
|
||||||
set => setTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile, value);
|
set => setTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string getTemplate(string settingName, Templates templ) => templ.GetValid(persistentDictionary.GetString(settingName));
|
[Description("How to format the file's Tile stored in metadata")]
|
||||||
private void setTemplate(string settingName, Templates templ, string newValue)
|
public string ChapterTitleTemplate
|
||||||
{
|
{
|
||||||
var template = newValue?.Trim();
|
get => getTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle);
|
||||||
if (templ.IsValid(template))
|
set => setTemplate(nameof(ChapterTitleTemplate), Templates.ChapterTitle, value);
|
||||||
persistentDictionary.SetString(settingName, template);
|
}
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#endregion
|
private string getTemplate(string settingName, Templates templ) => templ.GetValid(persistentDictionary.GetString(settingName));
|
||||||
|
private void setTemplate(string settingName, Templates templ, string newValue)
|
||||||
|
{
|
||||||
|
var template = newValue?.Trim();
|
||||||
|
if (templ.IsValid(template))
|
||||||
|
persistentDictionary.SetString(settingName, template);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region known directories
|
#endregion
|
||||||
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), "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"));
|
|
||||||
|
|
||||||
public enum KnownDirectories
|
#region known directories
|
||||||
{
|
public static string AppDir_Relative => $@".\{LIBATION_FILES_KEY}";
|
||||||
None = 0,
|
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), "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"));
|
||||||
|
|
||||||
[Description("My Users folder")]
|
public enum KnownDirectories
|
||||||
UserProfile = 1,
|
{
|
||||||
|
None = 0,
|
||||||
|
|
||||||
[Description("The same folder that Libation is running from")]
|
[Description("My Users folder")]
|
||||||
AppDir = 2,
|
UserProfile = 1,
|
||||||
|
|
||||||
[Description("Windows temporary folder")]
|
[Description("The same folder that Libation is running from")]
|
||||||
WinTemp = 3,
|
AppDir = 2,
|
||||||
|
|
||||||
[Description("My Documents")]
|
[Description("Windows temporary folder")]
|
||||||
MyDocs = 4,
|
WinTemp = 3,
|
||||||
|
|
||||||
[Description("Your settings folder (aka: Libation Files)")]
|
[Description("My Documents")]
|
||||||
LibationFiles = 5
|
MyDocs = 4,
|
||||||
}
|
|
||||||
// use func calls so we always get the latest value of LibationFiles
|
|
||||||
private static List<(KnownDirectories directory, Func<string> getPathFunc)> directoryOptionsPaths { get; } = new()
|
|
||||||
{
|
|
||||||
(KnownDirectories.None, () => null),
|
|
||||||
(KnownDirectories.UserProfile, () => UserProfile),
|
|
||||||
(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.
|
|
||||||
// also, keep this at bottom of this list
|
|
||||||
(KnownDirectories.LibationFiles, () => libationFilesPathCache)
|
|
||||||
};
|
|
||||||
public static string GetKnownDirectoryPath(KnownDirectories directory)
|
|
||||||
{
|
|
||||||
var dirFunc = directoryOptionsPaths.SingleOrDefault(dirFunc => dirFunc.directory == directory);
|
|
||||||
return dirFunc == default ? null : dirFunc.getPathFunc();
|
|
||||||
}
|
|
||||||
public static KnownDirectories GetKnownDirectory(string directory)
|
|
||||||
{
|
|
||||||
// especially important so a very early call doesn't match null => LibationFiles
|
|
||||||
if (string.IsNullOrWhiteSpace(directory))
|
|
||||||
return KnownDirectories.None;
|
|
||||||
|
|
||||||
// 'First' instead of 'Single' because LibationFiles could match other directories. eg: default value of LibationFiles == UserProfile.
|
[Description("Your settings folder (aka: Libation Files)")]
|
||||||
// since it's a list, order matters and non-LibationFiles will be returned first
|
LibationFiles = 5
|
||||||
var dirFunc = directoryOptionsPaths.FirstOrDefault(dirFunc => dirFunc.getPathFunc() == directory);
|
}
|
||||||
return dirFunc == default ? KnownDirectories.None : dirFunc.directory;
|
// use func calls so we always get the latest value of LibationFiles
|
||||||
}
|
private static List<(KnownDirectories directory, Func<string> getPathFunc)> directoryOptionsPaths { get; } = new()
|
||||||
#endregion
|
{
|
||||||
|
(KnownDirectories.None, () => null),
|
||||||
|
(KnownDirectories.UserProfile, () => UserProfile),
|
||||||
|
(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.
|
||||||
|
// also, keep this at bottom of this list
|
||||||
|
(KnownDirectories.LibationFiles, () => libationFilesPathCache)
|
||||||
|
};
|
||||||
|
public static string GetKnownDirectoryPath(KnownDirectories directory)
|
||||||
|
{
|
||||||
|
var dirFunc = directoryOptionsPaths.SingleOrDefault(dirFunc => dirFunc.directory == directory);
|
||||||
|
return dirFunc == default ? null : dirFunc.getPathFunc();
|
||||||
|
}
|
||||||
|
public static KnownDirectories GetKnownDirectory(string directory)
|
||||||
|
{
|
||||||
|
// especially important so a very early call doesn't match null => LibationFiles
|
||||||
|
if (string.IsNullOrWhiteSpace(directory))
|
||||||
|
return KnownDirectories.None;
|
||||||
|
|
||||||
#region logging
|
// 'First' instead of 'Single' because LibationFiles could match other directories. eg: default value of LibationFiles == UserProfile.
|
||||||
private IConfigurationRoot configuration;
|
// 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
|
||||||
|
|
||||||
public void ConfigureLogging()
|
#region logging
|
||||||
{
|
private IConfigurationRoot configuration;
|
||||||
configuration = new ConfigurationBuilder()
|
|
||||||
.AddJsonFile(SettingsFilePath, optional: false, reloadOnChange: true)
|
|
||||||
.Build();
|
|
||||||
Log.Logger = new LoggerConfiguration()
|
|
||||||
.ReadFrom.Configuration(configuration)
|
|
||||||
.CreateLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
[Description("The importance of a log event")]
|
public void ConfigureLogging()
|
||||||
public LogEventLevel LogLevel
|
{
|
||||||
{
|
configuration = new ConfigurationBuilder()
|
||||||
get
|
.AddJsonFile(SettingsFilePath, optional: false, reloadOnChange: true)
|
||||||
{
|
.Build();
|
||||||
var logLevelStr = persistentDictionary.GetStringFromJsonPath("Serilog", "MinimumLevel");
|
Log.Logger = new LoggerConfiguration()
|
||||||
return Enum.TryParse<LogEventLevel>(logLevelStr, out var logLevelEnum) ? logLevelEnum : LogEventLevel.Information;
|
.ReadFrom.Configuration(configuration)
|
||||||
}
|
.CreateLogger();
|
||||||
set
|
}
|
||||||
{
|
|
||||||
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
|
||||||
if (!valueWasChanged)
|
|
||||||
{
|
|
||||||
Log.Logger.Debug("LogLevel.set attempt. No change");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration.Reload();
|
[Description("The importance of a log event")]
|
||||||
|
public LogEventLevel LogLevel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var logLevelStr = persistentDictionary.GetStringFromJsonPath("Serilog", "MinimumLevel");
|
||||||
|
return Enum.TryParse<LogEventLevel>(logLevelStr, out var logLevelEnum) ? logLevelEnum : LogEventLevel.Information;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
||||||
|
if (!valueWasChanged)
|
||||||
|
{
|
||||||
|
Log.Logger.Debug("LogLevel.set attempt. No change");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
|
configuration.Reload();
|
||||||
{
|
|
||||||
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
|
Log.Logger.Information("Updated LogLevel MinimumLevel. {@DebugInfo}", new
|
||||||
LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(),
|
{
|
||||||
LogLevel_Information_Enabled = Log.Logger.IsInformationEnabled(),
|
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
|
||||||
LogLevel_Warning_Enabled = Log.Logger.IsWarningEnabled(),
|
LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(),
|
||||||
LogLevel_Error_Enabled = Log.Logger.IsErrorEnabled(),
|
LogLevel_Information_Enabled = Log.Logger.IsInformationEnabled(),
|
||||||
LogLevel_Fatal_Enabled = Log.Logger.IsFatalEnabled()
|
LogLevel_Warning_Enabled = Log.Logger.IsWarningEnabled(),
|
||||||
});
|
LogLevel_Error_Enabled = Log.Logger.IsErrorEnabled(),
|
||||||
}
|
LogLevel_Fatal_Enabled = Log.Logger.IsFatalEnabled()
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region singleton stuff
|
#region singleton stuff
|
||||||
public static Configuration Instance { get; } = new Configuration();
|
public static Configuration Instance { get; } = new Configuration();
|
||||||
private Configuration() { }
|
private Configuration() { }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region LibationFiles
|
#region LibationFiles
|
||||||
private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "appsettings.json");
|
private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "appsettings.json");
|
||||||
private const string LIBATION_FILES_KEY = "LibationFiles";
|
private const string LIBATION_FILES_KEY = "LibationFiles";
|
||||||
|
|
||||||
[Description("Location for storage of program-created files")]
|
[Description("Location for storage of program-created files")]
|
||||||
public string LibationFiles
|
public string LibationFiles
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (libationFilesPathCache is not null)
|
if (libationFilesPathCache is not null)
|
||||||
return libationFilesPathCache;
|
return libationFilesPathCache;
|
||||||
|
|
||||||
// FIRST: must write here before SettingsFilePath in next step reads cache
|
// FIRST: must write here before SettingsFilePath in next step reads cache
|
||||||
libationFilesPathCache = getLibationFilesSettingFromJson();
|
libationFilesPathCache = getLibationFilesSettingFromJson();
|
||||||
|
|
||||||
// SECOND. before setting to json file with SetWithJsonPath, PersistentDictionary must exist
|
// SECOND. before setting to json file with SetWithJsonPath, PersistentDictionary must exist
|
||||||
persistentDictionary = new PersistentDictionary(SettingsFilePath);
|
persistentDictionary = new PersistentDictionary(SettingsFilePath);
|
||||||
|
|
||||||
// Config init in ensureSerilogConfig() only happens when serilog setting is first created (prob on 1st run).
|
// Config init in ensureSerilogConfig() only happens when serilog setting is first created (prob on 1st run).
|
||||||
// This Set() enforces current LibationFiles every time we restart Libation or redirect LibationFiles
|
// This Set() enforces current LibationFiles every time we restart Libation or redirect LibationFiles
|
||||||
var logPath = Path.Combine(LibationFiles, "Log.log");
|
var logPath = Path.Combine(LibationFiles, "Log.log");
|
||||||
|
|
||||||
// BAD: Serilog.WriteTo[1].Args
|
// BAD: Serilog.WriteTo[1].Args
|
||||||
// "[1]" assumes ordinal position
|
// "[1]" assumes ordinal position
|
||||||
// GOOD: Serilog.WriteTo[?(@.Name=='File')].Args
|
// GOOD: Serilog.WriteTo[?(@.Name=='File')].Args
|
||||||
var jsonpath = "Serilog.WriteTo[?(@.Name=='File')].Args";
|
var jsonpath = "Serilog.WriteTo[?(@.Name=='File')].Args";
|
||||||
|
|
||||||
SetWithJsonPath(jsonpath, "path", logPath, true);
|
SetWithJsonPath(jsonpath, "path", logPath, true);
|
||||||
|
|
||||||
return libationFilesPathCache;
|
return libationFilesPathCache;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string libationFilesPathCache { get; set; }
|
private static string libationFilesPathCache { get; set; }
|
||||||
|
|
||||||
private string getLibationFilesSettingFromJson()
|
private string getLibationFilesSettingFromJson()
|
||||||
{
|
{
|
||||||
string startingContents = null;
|
string startingContents = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (File.Exists(APPSETTINGS_JSON))
|
if (File.Exists(APPSETTINGS_JSON))
|
||||||
{
|
{
|
||||||
startingContents = File.ReadAllText(APPSETTINGS_JSON);
|
startingContents = File.ReadAllText(APPSETTINGS_JSON);
|
||||||
var startingJObj = JObject.Parse(startingContents);
|
var startingJObj = JObject.Parse(startingContents);
|
||||||
|
|
||||||
if (startingJObj.ContainsKey(LIBATION_FILES_KEY))
|
if (startingJObj.ContainsKey(LIBATION_FILES_KEY))
|
||||||
{
|
{
|
||||||
var startingValue = startingJObj[LIBATION_FILES_KEY].Value<string>();
|
var startingValue = startingJObj[LIBATION_FILES_KEY].Value<string>();
|
||||||
if (!string.IsNullOrWhiteSpace(startingValue))
|
if (!string.IsNullOrWhiteSpace(startingValue))
|
||||||
return startingValue;
|
return startingValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
|
||||||
// not found. write to file. read from file
|
// not found. write to file. read from file
|
||||||
var endingContents = new JObject { { LIBATION_FILES_KEY, UserProfile.ToString() } }.ToString(Formatting.Indented);
|
var endingContents = new JObject { { LIBATION_FILES_KEY, UserProfile.ToString() } }.ToString(Formatting.Indented);
|
||||||
if (startingContents != endingContents)
|
if (startingContents != endingContents)
|
||||||
{
|
{
|
||||||
File.WriteAllText(APPSETTINGS_JSON, endingContents);
|
File.WriteAllText(APPSETTINGS_JSON, endingContents);
|
||||||
System.Threading.Thread.Sleep(100);
|
System.Threading.Thread.Sleep(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do not check whether directory exists. special/meta directory (eg: AppDir) is valid
|
// 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
|
// verify from live file. no try/catch. want failures to be visible
|
||||||
var jObjFinal = JObject.Parse(File.ReadAllText(APPSETTINGS_JSON));
|
var jObjFinal = JObject.Parse(File.ReadAllText(APPSETTINGS_JSON));
|
||||||
var valueFinal = jObjFinal[LIBATION_FILES_KEY].Value<string>();
|
var valueFinal = jObjFinal[LIBATION_FILES_KEY].Value<string>();
|
||||||
return valueFinal;
|
return valueFinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLibationFiles(string directory)
|
public void SetLibationFiles(string directory)
|
||||||
{
|
{
|
||||||
libationFilesPathCache = null;
|
libationFilesPathCache = null;
|
||||||
|
|
||||||
// ensure exists
|
// ensure exists
|
||||||
if (!File.Exists(APPSETTINGS_JSON))
|
if (!File.Exists(APPSETTINGS_JSON))
|
||||||
{
|
{
|
||||||
// getter creates new file, loads PersistentDictionary
|
// getter creates new file, loads PersistentDictionary
|
||||||
var _ = LibationFiles;
|
var _ = LibationFiles;
|
||||||
System.Threading.Thread.Sleep(100);
|
System.Threading.Thread.Sleep(100);
|
||||||
}
|
}
|
||||||
|
|
||||||
var startingContents = File.ReadAllText(APPSETTINGS_JSON);
|
var startingContents = File.ReadAllText(APPSETTINGS_JSON);
|
||||||
var jObj = JObject.Parse(startingContents);
|
var jObj = JObject.Parse(startingContents);
|
||||||
|
|
||||||
jObj[LIBATION_FILES_KEY] = directory;
|
jObj[LIBATION_FILES_KEY] = directory;
|
||||||
|
|
||||||
var endingContents = JsonConvert.SerializeObject(jObj, Formatting.Indented);
|
var endingContents = JsonConvert.SerializeObject(jObj, Formatting.Indented);
|
||||||
if (startingContents == endingContents)
|
if (startingContents == endingContents)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// now it's set in the file again but no settings have moved yet
|
// now it's set in the file again but no settings have moved yet
|
||||||
File.WriteAllText(APPSETTINGS_JSON, endingContents);
|
File.WriteAllText(APPSETTINGS_JSON, endingContents);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Log.Logger.Information("Libation files changed {@DebugInfo}", new { APPSETTINGS_JSON, LIBATION_FILES_KEY, directory });
|
Log.Logger.Information("Libation files changed {@DebugInfo}", new { APPSETTINGS_JSON, LIBATION_FILES_KEY, directory });
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,277 +8,330 @@ using FileManager;
|
|||||||
|
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public abstract class Templates
|
public abstract class Templates
|
||||||
{
|
{
|
||||||
protected static string[] Valid => Array.Empty<string>();
|
protected static string[] Valid => Array.Empty<string>();
|
||||||
public const string ERROR_NULL_IS_INVALID = "Null template is invalid.";
|
public const string ERROR_NULL_IS_INVALID = "Null template is invalid.";
|
||||||
public const string ERROR_FULL_PATH_IS_INVALID = @"No colons or full paths allowed. Eg: should not start with C:\";
|
public const string ERROR_FULL_PATH_IS_INVALID = @"No colons or full paths allowed. Eg: should not start with C:\";
|
||||||
public const string ERROR_INVALID_FILE_NAME_CHAR = @"Only file name friendly characters allowed. Eg: no colons or slashes";
|
public const string ERROR_INVALID_FILE_NAME_CHAR = @"Only file name friendly characters allowed. Eg: no colons or slashes";
|
||||||
|
|
||||||
public const string WARNING_EMPTY = "Template is empty.";
|
public const string WARNING_EMPTY = "Template is empty.";
|
||||||
public const string WARNING_WHITE_SPACE = "Template is white space.";
|
public const string WARNING_WHITE_SPACE = "Template is white space.";
|
||||||
public const string WARNING_NO_TAGS = "Should use tags. Eg: <title>";
|
public const string WARNING_NO_TAGS = "Should use tags. Eg: <title>";
|
||||||
public const string WARNING_HAS_CHAPTER_TAGS = "Chapter tags should only be used in the template used for naming files which are split by chapter. Eg: <ch title>";
|
public const string WARNING_HAS_CHAPTER_TAGS = "Chapter tags should only be used in the template used for naming files which are split by chapter. Eg: <ch title>";
|
||||||
public const string WARNING_NO_CHAPTER_NUMBER_TAG = "Should include chapter number tag in template used for naming files which are split by chapter. Ie: <ch#> or <ch# 0>";
|
public const string WARNING_NO_CHAPTER_NUMBER_TAG = "Should include chapter number tag in template used for naming files which are split by chapter. Ie: <ch#> or <ch# 0>";
|
||||||
|
|
||||||
public static FolderTemplate Folder { get; } = new FolderTemplate();
|
public static FolderTemplate Folder { get; } = new FolderTemplate();
|
||||||
public static FileTemplate File { get; } = new FileTemplate();
|
public static FileTemplate File { get; } = new FileTemplate();
|
||||||
public static ChapterFileTemplate ChapterFile { get; } = new ChapterFileTemplate();
|
public static ChapterFileTemplate ChapterFile { get; } = new ChapterFileTemplate();
|
||||||
|
public static ChapterTitleTemplate ChapterTitle { get; } = new ChapterTitleTemplate();
|
||||||
|
|
||||||
public abstract string Name { get; }
|
public abstract string Name { get; }
|
||||||
public abstract string Description { get; }
|
public abstract string Description { get; }
|
||||||
public abstract string DefaultTemplate { get; }
|
public abstract string DefaultTemplate { get; }
|
||||||
protected abstract bool IsChapterized { get; }
|
protected abstract bool IsChapterized { get; }
|
||||||
|
|
||||||
protected Templates() { }
|
protected Templates() { }
|
||||||
|
|
||||||
#region validation
|
#region validation
|
||||||
internal string GetValid(string configValue)
|
internal string GetValid(string configValue)
|
||||||
{
|
{
|
||||||
var value = configValue?.Trim();
|
var value = configValue?.Trim();
|
||||||
return IsValid(value) ? value : DefaultTemplate;
|
return IsValid(value) ? value : DefaultTemplate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract IEnumerable<string> GetErrors(string template);
|
public abstract IEnumerable<string> GetErrors(string template);
|
||||||
public bool IsValid(string template) => !GetErrors(template).Any();
|
public bool IsValid(string template) => !GetErrors(template).Any();
|
||||||
|
|
||||||
public abstract IEnumerable<string> GetWarnings(string template);
|
public abstract IEnumerable<string> GetWarnings(string template);
|
||||||
public bool HasWarnings(string template) => GetWarnings(template).Any();
|
public bool HasWarnings(string template) => GetWarnings(template).Any();
|
||||||
|
|
||||||
protected static string[] GetFileErrors(string template)
|
protected static string[] GetFileErrors(string template)
|
||||||
{
|
{
|
||||||
// File name only; not path. all other path chars are valid enough to pass this check and will be handled on final save.
|
// File name only; not path. all other path chars are valid enough to pass this check and will be handled on final save.
|
||||||
|
|
||||||
// null is invalid. whitespace is valid but not recommended
|
// null is invalid. whitespace is valid but not recommended
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return new[] { ERROR_NULL_IS_INVALID };
|
return new[] { ERROR_NULL_IS_INVALID };
|
||||||
|
|
||||||
if (template.Contains(':')
|
if (template.Contains(':')
|
||||||
|| template.Contains(Path.DirectorySeparatorChar)
|
|| template.Contains(Path.DirectorySeparatorChar)
|
||||||
|| template.Contains(Path.AltDirectorySeparatorChar)
|
|| template.Contains(Path.AltDirectorySeparatorChar)
|
||||||
)
|
)
|
||||||
return new[] { ERROR_INVALID_FILE_NAME_CHAR };
|
return new[] { ERROR_INVALID_FILE_NAME_CHAR };
|
||||||
|
|
||||||
return Valid;
|
return Valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IEnumerable<string> GetStandardWarnings(string template)
|
protected IEnumerable<string> GetStandardWarnings(string template)
|
||||||
{
|
{
|
||||||
var warnings = GetErrors(template).ToList();
|
var warnings = GetErrors(template).ToList();
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return warnings;
|
return warnings;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(template))
|
if (string.IsNullOrEmpty(template))
|
||||||
warnings.Add(WARNING_EMPTY);
|
warnings.Add(WARNING_EMPTY);
|
||||||
else if (string.IsNullOrWhiteSpace(template))
|
else if (string.IsNullOrWhiteSpace(template))
|
||||||
warnings.Add(WARNING_WHITE_SPACE);
|
warnings.Add(WARNING_WHITE_SPACE);
|
||||||
|
|
||||||
if (TagCount(template) == 0)
|
if (TagCount(template) == 0)
|
||||||
warnings.Add(WARNING_NO_TAGS);
|
warnings.Add(WARNING_NO_TAGS);
|
||||||
|
|
||||||
if (!IsChapterized && ContainsChapterOnlyTags(template))
|
if (!IsChapterized && ContainsChapterOnlyTags(template))
|
||||||
warnings.Add(WARNING_HAS_CHAPTER_TAGS);
|
warnings.Add(WARNING_HAS_CHAPTER_TAGS);
|
||||||
|
|
||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal int TagCount(string template)
|
internal int TagCount(string template)
|
||||||
=> GetTemplateTags()
|
=> GetTemplateTags()
|
||||||
// for <id><id> == 1, use:
|
// for <id><id> == 1, use:
|
||||||
// .Count(t => template.Contains($"<{t.TagName}>"))
|
// .Count(t => template.Contains($"<{t.TagName}>"))
|
||||||
// .Sum() impl: <id><id> == 2
|
// .Sum() impl: <id><id> == 2
|
||||||
.Sum(t => template.Split($"<{t.TagName}>").Length - 1);
|
.Sum(t => template.Split($"<{t.TagName}>").Length - 1);
|
||||||
|
|
||||||
internal static bool ContainsChapterOnlyTags(string template)
|
internal static bool ContainsChapterOnlyTags(string template)
|
||||||
=> TemplateTags.GetAll()
|
=> TemplateTags.GetAll()
|
||||||
.Where(t => t.IsChapterOnly)
|
.Where(t => t.IsChapterOnly)
|
||||||
.Any(t => ContainsTag(template, t.TagName));
|
.Any(t => ContainsTag(template, t.TagName));
|
||||||
|
|
||||||
internal static bool ContainsTag(string template, string tag) => template.Contains($"<{tag}>");
|
internal static bool ContainsTag(string template, string tag) => template.Contains($"<{tag}>");
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region to file name
|
#region to file name
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// EditTemplateDialog: Get template generated filename for portion of path
|
/// EditTemplateDialog: Get template generated filename for portion of path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template)
|
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template)
|
||||||
=> string.IsNullOrWhiteSpace(template)
|
=> string.IsNullOrWhiteSpace(template)
|
||||||
? ""
|
? ""
|
||||||
: getFileNamingTemplate(libraryBookDto, template, null, null)
|
: getFileNamingTemplate(libraryBookDto, template, null, null)
|
||||||
.GetFilePath().PathWithoutPrefix;
|
.GetFilePath().PathWithoutPrefix;
|
||||||
|
|
||||||
private static Regex ifSeriesRegex { get; } = new Regex("<if series->(.*?)<-if series>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
private static Regex ifSeriesRegex { get; } = new Regex("<if series->(.*?)<-if series>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
internal static FileNamingTemplate getFileNamingTemplate(LibraryBookDto libraryBookDto, string template, string dirFullPath, string extension)
|
internal static FileNamingTemplate getFileNamingTemplate(LibraryBookDto libraryBookDto, string template, string dirFullPath, string extension)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
||||||
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
||||||
|
|
||||||
dirFullPath = dirFullPath?.Trim() ?? "";
|
dirFullPath = dirFullPath?.Trim() ?? "";
|
||||||
|
|
||||||
// for non-series, remove <if series-> and <-if series> tags and everything in between
|
// for non-series, remove <if series-> and <-if series> tags and everything in between
|
||||||
// for series, remove <if series-> and <-if series> tags, what's in between will remain
|
// for series, remove <if series-> and <-if series> tags, what's in between will remain
|
||||||
template = ifSeriesRegex.Replace(
|
template = ifSeriesRegex.Replace(
|
||||||
template,
|
template,
|
||||||
string.IsNullOrWhiteSpace(libraryBookDto.SeriesName) ? "" : "$1");
|
string.IsNullOrWhiteSpace(libraryBookDto.SeriesName) ? "" : "$1");
|
||||||
|
|
||||||
var t = template + FileUtility.GetStandardizedExtension(extension);
|
var t = template + FileUtility.GetStandardizedExtension(extension);
|
||||||
var fullfilename = dirFullPath == "" ? t : Path.Combine(dirFullPath, t);
|
var fullfilename = dirFullPath == "" ? t : Path.Combine(dirFullPath, t);
|
||||||
|
|
||||||
var fileNamingTemplate = new FileNamingTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
|
var fileNamingTemplate = new FileNamingTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
|
||||||
|
|
||||||
var title = libraryBookDto.Title ?? "";
|
var title = libraryBookDto.Title ?? "";
|
||||||
var titleShort = title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':'));
|
var titleShort = title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':'));
|
||||||
|
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Id, libraryBookDto.AudibleProductId);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Id, libraryBookDto.AudibleProductId);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Title, title);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Title, title);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.TitleShort, titleShort);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.TitleShort, titleShort);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Author, libraryBookDto.AuthorNames);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Author, libraryBookDto.AuthorNames);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.FirstAuthor, libraryBookDto.FirstAuthor);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.FirstAuthor, libraryBookDto.FirstAuthor);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Narrator, libraryBookDto.NarratorNames);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Narrator, libraryBookDto.NarratorNames);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.FirstNarrator, libraryBookDto.FirstNarrator);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.FirstNarrator, libraryBookDto.FirstNarrator);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Series, libraryBookDto.SeriesName);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Series, libraryBookDto.SeriesName);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.SeriesNumber, libraryBookDto.SeriesNumber);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.SeriesNumber, libraryBookDto.SeriesNumber);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Account, libraryBookDto.Account);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Account, libraryBookDto.Account);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Locale, libraryBookDto.Locale);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Locale, libraryBookDto.Locale);
|
||||||
|
|
||||||
return fileNamingTemplate;
|
return fileNamingTemplate;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public IEnumerable<TemplateTags> GetTemplateTags()
|
public virtual IEnumerable<TemplateTags> GetTemplateTags()
|
||||||
=> TemplateTags.GetAll()
|
=> TemplateTags.GetAll()
|
||||||
// yeah, this line is a little funky but it works when you think through it. also: trust the unit tests
|
// yeah, this line is a little funky but it works when you think through it. also: trust the unit tests
|
||||||
.Where(t => IsChapterized || !t.IsChapterOnly);
|
.Where(t => IsChapterized || !t.IsChapterOnly);
|
||||||
|
|
||||||
public string Sanitize(string template)
|
public string Sanitize(string template)
|
||||||
{
|
{
|
||||||
var value = template ?? "";
|
var value = template ?? "";
|
||||||
|
|
||||||
// don't use alt slash
|
// don't use alt slash
|
||||||
value = value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
value = value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
// don't allow double slashes
|
// don't allow double slashes
|
||||||
var sing = $"{Path.DirectorySeparatorChar}";
|
var sing = $"{Path.DirectorySeparatorChar}";
|
||||||
var dbl = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}";
|
var dbl = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}";
|
||||||
while (value.Contains(dbl))
|
while (value.Contains(dbl))
|
||||||
value = value.Replace(dbl, sing);
|
value = value.Replace(dbl, sing);
|
||||||
|
|
||||||
// trim. don't start or end with slash
|
// trim. don't start or end with slash
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var start = value.Length;
|
var start = value.Length;
|
||||||
value = value
|
value = value
|
||||||
.Trim()
|
.Trim()
|
||||||
.Trim(Path.DirectorySeparatorChar);
|
.Trim(Path.DirectorySeparatorChar);
|
||||||
var end = value.Length;
|
var end = value.Length;
|
||||||
if (start == end)
|
if (start == end)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FolderTemplate : Templates
|
public class FolderTemplate : Templates
|
||||||
{
|
{
|
||||||
public override string Name => "Folder Template";
|
public override string Name => "Folder Template";
|
||||||
public override string Description => Configuration.GetDescription(nameof(Configuration.FolderTemplate));
|
public override string Description => Configuration.GetDescription(nameof(Configuration.FolderTemplate));
|
||||||
public override string DefaultTemplate { get; } = "<title short> [<id>]";
|
public override string DefaultTemplate { get; } = "<title short> [<id>]";
|
||||||
protected override bool IsChapterized { get; } = false;
|
protected override bool IsChapterized { get; } = false;
|
||||||
|
|
||||||
internal FolderTemplate() : base() { }
|
internal FolderTemplate() : base() { }
|
||||||
|
|
||||||
#region validation
|
#region validation
|
||||||
public override IEnumerable<string> GetErrors(string template)
|
public override IEnumerable<string> GetErrors(string template)
|
||||||
{
|
{
|
||||||
// null is invalid. whitespace is valid but not recommended
|
// null is invalid. whitespace is valid but not recommended
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return new[] { ERROR_NULL_IS_INVALID };
|
return new[] { ERROR_NULL_IS_INVALID };
|
||||||
|
|
||||||
// must be relative. no colons. all other path chars are valid enough to pass this check and will be handled on final save.
|
// must be relative. no colons. all other path chars are valid enough to pass this check and will be handled on final save.
|
||||||
if (template.Contains(':'))
|
if (template.Contains(':'))
|
||||||
return new[] { ERROR_FULL_PATH_IS_INVALID };
|
return new[] { ERROR_FULL_PATH_IS_INVALID };
|
||||||
|
|
||||||
return Valid;
|
return Valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<string> GetWarnings(string template) => GetStandardWarnings(template);
|
public override IEnumerable<string> GetWarnings(string template) => GetStandardWarnings(template);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region to file name
|
#region to file name
|
||||||
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
||||||
public string GetFilename(LibraryBookDto libraryBookDto, string baseDir = null)
|
public string GetFilename(LibraryBookDto libraryBookDto, string baseDir = null)
|
||||||
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FolderTemplate, baseDir ?? AudibleFileStorage.BooksDirectory, null)
|
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FolderTemplate, baseDir ?? AudibleFileStorage.BooksDirectory, null)
|
||||||
.GetFilePath();
|
.GetFilePath();
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileTemplate : Templates
|
public class FileTemplate : Templates
|
||||||
{
|
{
|
||||||
public override string Name => "File Template";
|
public override string Name => "File Template";
|
||||||
public override string Description => Configuration.GetDescription(nameof(Configuration.FileTemplate));
|
public override string Description => Configuration.GetDescription(nameof(Configuration.FileTemplate));
|
||||||
public override string DefaultTemplate { get; } = "<title> [<id>]";
|
public override string DefaultTemplate { get; } = "<title> [<id>]";
|
||||||
protected override bool IsChapterized { get; } = false;
|
protected override bool IsChapterized { get; } = false;
|
||||||
|
|
||||||
internal FileTemplate() : base() { }
|
internal FileTemplate() : base() { }
|
||||||
|
|
||||||
#region validation
|
#region validation
|
||||||
public override IEnumerable<string> GetErrors(string template) => GetFileErrors(template);
|
public override IEnumerable<string> GetErrors(string template) => GetFileErrors(template);
|
||||||
|
|
||||||
public override IEnumerable<string> GetWarnings(string template) => GetStandardWarnings(template);
|
public override IEnumerable<string> GetWarnings(string template) => GetStandardWarnings(template);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region to file name
|
#region to file name
|
||||||
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
||||||
public string GetFilename(LibraryBookDto libraryBookDto, string dirFullPath, string extension, bool returnFirstExisting = false)
|
public string GetFilename(LibraryBookDto libraryBookDto, string dirFullPath, string extension, bool returnFirstExisting = false)
|
||||||
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FileTemplate, dirFullPath, extension)
|
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FileTemplate, dirFullPath, extension)
|
||||||
.GetFilePath(returnFirstExisting);
|
.GetFilePath(returnFirstExisting);
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChapterFileTemplate : Templates
|
public class ChapterFileTemplate : Templates
|
||||||
{
|
{
|
||||||
public override string Name => "Chapter File Template";
|
public override string Name => "Chapter File Template";
|
||||||
public override string Description => Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate));
|
public override string Description => Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate));
|
||||||
public override string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
|
public override string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
|
||||||
protected override bool IsChapterized { get; } = true;
|
protected override bool IsChapterized { get; } = true;
|
||||||
|
|
||||||
internal ChapterFileTemplate() : base() { }
|
internal ChapterFileTemplate() : base() { }
|
||||||
|
|
||||||
#region validation
|
#region validation
|
||||||
public override IEnumerable<string> GetErrors(string template) => GetFileErrors(template);
|
public override IEnumerable<string> GetErrors(string template) => GetFileErrors(template);
|
||||||
|
|
||||||
public override IEnumerable<string> GetWarnings(string template)
|
public override IEnumerable<string> GetWarnings(string template)
|
||||||
{
|
{
|
||||||
var warnings = GetStandardWarnings(template).ToList();
|
var warnings = GetStandardWarnings(template).ToList();
|
||||||
if (template is null)
|
if (template is null)
|
||||||
return warnings;
|
return warnings;
|
||||||
|
|
||||||
// recommended to incl. <ch#> or <ch# 0>
|
// recommended to incl. <ch#> or <ch# 0>
|
||||||
if (!ContainsTag(template, TemplateTags.ChNumber.TagName) && !ContainsTag(template, TemplateTags.ChNumber0.TagName))
|
if (!ContainsTag(template, TemplateTags.ChNumber.TagName) && !ContainsTag(template, TemplateTags.ChNumber0.TagName))
|
||||||
warnings.Add(WARNING_NO_CHAPTER_NUMBER_TAG);
|
warnings.Add(WARNING_NO_CHAPTER_NUMBER_TAG);
|
||||||
|
|
||||||
return warnings;
|
return warnings;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region to file name
|
#region to file name
|
||||||
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
||||||
public string GetFilename(LibraryBookDto libraryBookDto, AaxDecrypter.MultiConvertFileProperties props)
|
public string GetFilename(LibraryBookDto libraryBookDto, AaxDecrypter.MultiConvertFileProperties props)
|
||||||
=> GetPortionFilename(libraryBookDto, Configuration.Instance.ChapterFileTemplate, props, AudibleFileStorage.DecryptInProgressDirectory);
|
=> GetPortionFilename(libraryBookDto, Configuration.Instance.ChapterFileTemplate, props, AudibleFileStorage.DecryptInProgressDirectory);
|
||||||
|
|
||||||
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath)
|
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath)
|
||||||
{
|
{
|
||||||
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName));
|
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName));
|
||||||
|
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChCount, props.PartsTotal);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChCount, props.PartsTotal);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber, props.PartsPosition);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber, props.PartsPosition);
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(props.PartsPosition, props.PartsTotal));
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(props.PartsPosition, props.PartsTotal));
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChTitle, props.Title ?? "");
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChTitle, props.Title ?? "");
|
||||||
|
|
||||||
return fileNamingTemplate.GetFilePath().PathWithoutPrefix;
|
return fileNamingTemplate.GetFilePath().PathWithoutPrefix;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
public class ChapterTitleTemplate : Templates
|
||||||
|
{
|
||||||
|
private List<TemplateTags> _templateTags { get; } = new()
|
||||||
|
{
|
||||||
|
TemplateTags.Title,
|
||||||
|
TemplateTags.TitleShort,
|
||||||
|
TemplateTags.Series,
|
||||||
|
TemplateTags.ChCount,
|
||||||
|
TemplateTags.ChNumber,
|
||||||
|
TemplateTags.ChNumber0,
|
||||||
|
TemplateTags.ChTitle,
|
||||||
|
};
|
||||||
|
public override string Name => "Chapter Title Template";
|
||||||
|
|
||||||
|
public override string Description => Configuration.GetDescription(nameof(Configuration.ChapterTitleTemplate));
|
||||||
|
|
||||||
|
public override string DefaultTemplate => "<ch#> - <title short>: <ch title>";
|
||||||
|
|
||||||
|
protected override bool IsChapterized => true;
|
||||||
|
|
||||||
|
public override IEnumerable<string> GetErrors(string template)
|
||||||
|
=> new List<string>();
|
||||||
|
|
||||||
|
public override IEnumerable<string> GetWarnings(string template)
|
||||||
|
=> GetStandardWarnings(template).ToList();
|
||||||
|
|
||||||
|
public string GetTitle(LibraryBookDto libraryBookDto, AaxDecrypter.MultiConvertFileProperties props)
|
||||||
|
=> GetPortionTitle(libraryBookDto, Configuration.Instance.ChapterTitleTemplate, props);
|
||||||
|
|
||||||
|
public string GetPortionTitle(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props)
|
||||||
|
{
|
||||||
|
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
||||||
|
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
||||||
|
|
||||||
|
var fileNamingTemplate = new MetadataNamingTemplate(template);
|
||||||
|
|
||||||
|
var title = libraryBookDto.Title ?? "";
|
||||||
|
var titleShort = title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':'));
|
||||||
|
|
||||||
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Title, title);
|
||||||
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.TitleShort, titleShort);
|
||||||
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.Series, libraryBookDto.SeriesName);
|
||||||
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChCount, props.PartsTotal);
|
||||||
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber, props.PartsPosition);
|
||||||
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(props.PartsPosition, props.PartsTotal));
|
||||||
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChTitle, props.Title ?? "");
|
||||||
|
|
||||||
|
return fileNamingTemplate.GetTagContents();
|
||||||
|
}
|
||||||
|
public override IEnumerable<TemplateTags> GetTemplateTags() => _templateTags;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
public static class UtilityExtensions
|
public static class UtilityExtensions
|
||||||
{
|
{
|
||||||
public static void AddParameterReplacement(this FileNamingTemplate fileNamingTemplate, TemplateTags templateTags, object value)
|
public static void AddParameterReplacement(this NamingTemplate fileNamingTemplate, TemplateTags templateTags, object value)
|
||||||
=> fileNamingTemplate.AddParameterReplacement(templateTags.TagName, value);
|
=> fileNamingTemplate.AddParameterReplacement(templateTags.TagName, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -67,7 +67,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
private void templateTb_TextChanged(object sender, EventArgs e)
|
private void templateTb_TextChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
workingTemplateText = templateTb.Text;
|
workingTemplateText = templateTb.Text;
|
||||||
|
var isChapterTitle = template == Templates.ChapterTitle;
|
||||||
var isFolder = template == Templates.Folder;
|
var isFolder = template == Templates.Folder;
|
||||||
|
|
||||||
var libraryBookDto = new LibraryBookDto
|
var libraryBookDto = new LibraryBookDto
|
||||||
@ -85,22 +85,35 @@ namespace LibationWinForms.Dialogs
|
|||||||
var chapterNumber = 4;
|
var chapterNumber = 4;
|
||||||
var chaptersTotal = 10;
|
var chaptersTotal = 10;
|
||||||
|
|
||||||
|
var partFileProperties = new AaxDecrypter.MultiConvertFileProperties()
|
||||||
|
{
|
||||||
|
OutputFileName = "",
|
||||||
|
PartsPosition = chapterNumber,
|
||||||
|
PartsTotal = chaptersTotal,
|
||||||
|
Title = chapterName
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
var books = config.Books;
|
var books = config.Books;
|
||||||
var folder = Templates.Folder.GetPortionFilename(
|
var folder = Templates.Folder.GetPortionFilename(
|
||||||
libraryBookDto,
|
libraryBookDto,
|
||||||
isFolder ? workingTemplateText : config.FolderTemplate);
|
isFolder ? workingTemplateText : config.FolderTemplate);
|
||||||
|
|
||||||
|
|
||||||
var file
|
var file
|
||||||
= template == Templates.ChapterFile
|
= template == Templates.ChapterFile
|
||||||
? Templates.ChapterFile.GetPortionFilename(
|
? Templates.ChapterFile.GetPortionFilename(
|
||||||
libraryBookDto,
|
libraryBookDto,
|
||||||
workingTemplateText,
|
workingTemplateText,
|
||||||
new() { OutputFileName = "", PartsPosition = chapterNumber, PartsTotal = chaptersTotal, Title = chapterName },
|
partFileProperties,
|
||||||
"")
|
"")
|
||||||
: Templates.File.GetPortionFilename(
|
: Templates.File.GetPortionFilename(
|
||||||
libraryBookDto,
|
libraryBookDto,
|
||||||
isFolder ? config.FileTemplate : workingTemplateText);
|
isFolder ? config.FileTemplate : workingTemplateText);
|
||||||
var ext = config.DecryptToLossy ? "mp3" : "m4b";
|
var ext = config.DecryptToLossy ? "mp3" : "m4b";
|
||||||
|
|
||||||
|
var chapterTitle = Templates.ChapterTitle.GetPortionTitle(libraryBookDto, workingTemplateText, partFileProperties);
|
||||||
|
|
||||||
const char ZERO_WIDTH_SPACE = '\u200B';
|
const char ZERO_WIDTH_SPACE = '\u200B';
|
||||||
var sing = $"{Path.DirectorySeparatorChar}";
|
var sing = $"{Path.DirectorySeparatorChar}";
|
||||||
|
|
||||||
@ -126,6 +139,14 @@ namespace LibationWinForms.Dialogs
|
|||||||
richTextBox1.Clear();
|
richTextBox1.Clear();
|
||||||
richTextBox1.SelectionFont = reg;
|
richTextBox1.SelectionFont = reg;
|
||||||
|
|
||||||
|
if (isChapterTitle)
|
||||||
|
richTextBox1.SelectionFont = bold;
|
||||||
|
|
||||||
|
richTextBox1.AppendText(chapterTitle);
|
||||||
|
|
||||||
|
if (isChapterTitle)
|
||||||
|
return;
|
||||||
|
|
||||||
richTextBox1.AppendText(slashWrap(books));
|
richTextBox1.AppendText(slashWrap(books));
|
||||||
richTextBox1.AppendText(sing);
|
richTextBox1.AppendText(sing);
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,69 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using LibationFileManager;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace LibationWinForms.Dialogs
|
namespace LibationWinForms.Dialogs
|
||||||
{
|
{
|
||||||
partial class SettingsDialog
|
partial class SettingsDialog
|
||||||
{
|
{
|
||||||
|
private void Load_AudioSettings(Configuration config)
|
||||||
|
{
|
||||||
|
this.allowLibationFixupCbox.Text = desc(nameof(config.AllowLibationFixup));
|
||||||
|
this.createCueSheetCbox.Text = desc(nameof(config.CreateCueSheet));
|
||||||
|
this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt));
|
||||||
|
this.retainAaxFileCbox.Text = desc(nameof(config.RetainAaxFile));
|
||||||
|
this.splitFilesByChapterCbox.Text = desc(nameof(config.SplitFilesByChapter));
|
||||||
|
this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio));
|
||||||
|
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
|
||||||
|
|
||||||
|
allowLibationFixupCbox.Checked = config.AllowLibationFixup;
|
||||||
|
createCueSheetCbox.Checked = config.CreateCueSheet;
|
||||||
|
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
||||||
|
retainAaxFileCbox.Checked = config.RetainAaxFile;
|
||||||
|
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
|
||||||
|
stripUnabridgedCbox.Checked = config.StripUnabridged;
|
||||||
|
stripAudibleBrandingCbox.Checked = config.StripAudibleBrandAudio;
|
||||||
|
convertLosslessRb.Checked = !config.DecryptToLossy;
|
||||||
|
convertLossyRb.Checked = config.DecryptToLossy;
|
||||||
|
|
||||||
|
lameTargetBitrateRb.Checked = config.LameTargetBitrate;
|
||||||
|
lameTargetQualityRb.Checked = !config.LameTargetBitrate;
|
||||||
|
lameDownsampleMonoCbox.Checked = config.LameDownsampleMono;
|
||||||
|
lameBitrateTb.Value = config.LameBitrate;
|
||||||
|
lameConstantBitrateCbox.Checked = config.LameConstantBitrate;
|
||||||
|
LameMatchSourceBRCbox.Checked = config.LameMatchSourceBR;
|
||||||
|
lameVBRQualityTb.Value = config.LameVBRQuality;
|
||||||
|
|
||||||
|
chapterTitleTemplateGb.Text = desc(nameof(config.ChapterTitleTemplate));
|
||||||
|
chapterTitleTemplateTb.Text = config.ChapterTitleTemplate;
|
||||||
|
|
||||||
|
lameTargetRb_CheckedChanged(this, EventArgs.Empty);
|
||||||
|
LameMatchSourceBRCbox_CheckedChanged(this, EventArgs.Empty);
|
||||||
|
convertFormatRb_CheckedChanged(this, EventArgs.Empty);
|
||||||
|
allowLibationFixupCbox_CheckedChanged(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Save_AudioSettings(Configuration config)
|
||||||
|
{
|
||||||
|
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
||||||
|
config.CreateCueSheet = createCueSheetCbox.Checked;
|
||||||
|
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
|
||||||
|
config.RetainAaxFile = retainAaxFileCbox.Checked;
|
||||||
|
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
|
||||||
|
config.StripUnabridged = stripUnabridgedCbox.Checked;
|
||||||
|
config.StripAudibleBrandAudio = stripAudibleBrandingCbox.Checked;
|
||||||
|
config.DecryptToLossy = convertLossyRb.Checked;
|
||||||
|
|
||||||
|
config.LameTargetBitrate = lameTargetBitrateRb.Checked;
|
||||||
|
config.LameDownsampleMono = lameDownsampleMonoCbox.Checked;
|
||||||
|
config.LameBitrate = lameBitrateTb.Value;
|
||||||
|
config.LameConstantBitrate = lameConstantBitrateCbox.Checked;
|
||||||
|
config.LameMatchSourceBR = LameMatchSourceBRCbox.Checked;
|
||||||
|
config.LameVBRQuality = lameVBRQualityTb.Value;
|
||||||
|
|
||||||
|
config.ChapterTitleTemplate = chapterTitleTemplateTb.Text;
|
||||||
|
}
|
||||||
|
|
||||||
private void lameTargetRb_CheckedChanged(object sender, EventArgs e)
|
private void lameTargetRb_CheckedChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
lameBitrateGb.Enabled = lameTargetBitrateRb.Checked;
|
lameBitrateGb.Enabled = lameTargetBitrateRb.Checked;
|
||||||
@ -19,6 +75,13 @@ namespace LibationWinForms.Dialogs
|
|||||||
lameBitrateTb.Enabled = !LameMatchSourceBRCbox.Checked;
|
lameBitrateTb.Enabled = !LameMatchSourceBRCbox.Checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void splitFilesByChapterCbox_CheckedChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
chapterTitleTemplateGb.Enabled = splitFilesByChapterCbox.Checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chapterTitleTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.ChapterTitle, chapterTitleTemplateTb);
|
||||||
|
|
||||||
private void convertFormatRb_CheckedChanged(object sender, EventArgs e)
|
private void convertFormatRb_CheckedChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
lameTargetRb_CheckedChanged(sender, e);
|
lameTargetRb_CheckedChanged(sender, e);
|
||||||
|
|||||||
@ -52,6 +52,7 @@
|
|||||||
this.tabControl = new System.Windows.Forms.TabControl();
|
this.tabControl = new System.Windows.Forms.TabControl();
|
||||||
this.tab1ImportantSettings = new System.Windows.Forms.TabPage();
|
this.tab1ImportantSettings = new System.Windows.Forms.TabPage();
|
||||||
this.booksGb = new System.Windows.Forms.GroupBox();
|
this.booksGb = new System.Windows.Forms.GroupBox();
|
||||||
|
this.saveEpisodesToSeriesFolderCbox = new System.Windows.Forms.CheckBox();
|
||||||
this.tab2ImportLibrary = new System.Windows.Forms.TabPage();
|
this.tab2ImportLibrary = new System.Windows.Forms.TabPage();
|
||||||
this.autoDownloadEpisodesCb = new System.Windows.Forms.CheckBox();
|
this.autoDownloadEpisodesCb = new System.Windows.Forms.CheckBox();
|
||||||
this.autoScanCb = new System.Windows.Forms.CheckBox();
|
this.autoScanCb = new System.Windows.Forms.CheckBox();
|
||||||
@ -69,6 +70,9 @@
|
|||||||
this.folderTemplateTb = new System.Windows.Forms.TextBox();
|
this.folderTemplateTb = new System.Windows.Forms.TextBox();
|
||||||
this.folderTemplateLbl = new System.Windows.Forms.Label();
|
this.folderTemplateLbl = new System.Windows.Forms.Label();
|
||||||
this.tab4AudioFileOptions = new System.Windows.Forms.TabPage();
|
this.tab4AudioFileOptions = new System.Windows.Forms.TabPage();
|
||||||
|
this.chapterTitleTemplateGb = new System.Windows.Forms.GroupBox();
|
||||||
|
this.chapterTitleTemplateBtn = new System.Windows.Forms.Button();
|
||||||
|
this.chapterTitleTemplateTb = new System.Windows.Forms.TextBox();
|
||||||
this.lameOptionsGb = new System.Windows.Forms.GroupBox();
|
this.lameOptionsGb = new System.Windows.Forms.GroupBox();
|
||||||
this.lameDownsampleMonoCbox = new System.Windows.Forms.CheckBox();
|
this.lameDownsampleMonoCbox = new System.Windows.Forms.CheckBox();
|
||||||
this.lameBitrateGb = new System.Windows.Forms.GroupBox();
|
this.lameBitrateGb = new System.Windows.Forms.GroupBox();
|
||||||
@ -103,7 +107,6 @@
|
|||||||
this.retainAaxFileCbox = new System.Windows.Forms.CheckBox();
|
this.retainAaxFileCbox = new System.Windows.Forms.CheckBox();
|
||||||
this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox();
|
this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox();
|
||||||
this.createCueSheetCbox = new System.Windows.Forms.CheckBox();
|
this.createCueSheetCbox = new System.Windows.Forms.CheckBox();
|
||||||
this.saveEpisodesToSeriesFolderCbox = new System.Windows.Forms.CheckBox();
|
|
||||||
this.badBookGb.SuspendLayout();
|
this.badBookGb.SuspendLayout();
|
||||||
this.tabControl.SuspendLayout();
|
this.tabControl.SuspendLayout();
|
||||||
this.tab1ImportantSettings.SuspendLayout();
|
this.tab1ImportantSettings.SuspendLayout();
|
||||||
@ -113,6 +116,7 @@
|
|||||||
this.inProgressFilesGb.SuspendLayout();
|
this.inProgressFilesGb.SuspendLayout();
|
||||||
this.customFileNamingGb.SuspendLayout();
|
this.customFileNamingGb.SuspendLayout();
|
||||||
this.tab4AudioFileOptions.SuspendLayout();
|
this.tab4AudioFileOptions.SuspendLayout();
|
||||||
|
this.chapterTitleTemplateGb.SuspendLayout();
|
||||||
this.lameOptionsGb.SuspendLayout();
|
this.lameOptionsGb.SuspendLayout();
|
||||||
this.lameBitrateGb.SuspendLayout();
|
this.lameBitrateGb.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)(this.lameBitrateTb)).BeginInit();
|
((System.ComponentModel.ISupportInitialize)(this.lameBitrateTb)).BeginInit();
|
||||||
@ -262,6 +266,7 @@
|
|||||||
this.splitFilesByChapterCbox.TabIndex = 13;
|
this.splitFilesByChapterCbox.TabIndex = 13;
|
||||||
this.splitFilesByChapterCbox.Text = "[SplitFilesByChapter desc]";
|
this.splitFilesByChapterCbox.Text = "[SplitFilesByChapter desc]";
|
||||||
this.splitFilesByChapterCbox.UseVisualStyleBackColor = true;
|
this.splitFilesByChapterCbox.UseVisualStyleBackColor = true;
|
||||||
|
this.splitFilesByChapterCbox.CheckedChanged += new System.EventHandler(this.splitFilesByChapterCbox_CheckedChanged);
|
||||||
//
|
//
|
||||||
// allowLibationFixupCbox
|
// allowLibationFixupCbox
|
||||||
//
|
//
|
||||||
@ -303,7 +308,7 @@
|
|||||||
// inProgressSelectControl
|
// inProgressSelectControl
|
||||||
//
|
//
|
||||||
this.inProgressSelectControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.inProgressSelectControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.inProgressSelectControl.Location = new System.Drawing.Point(7, 68);
|
this.inProgressSelectControl.Location = new System.Drawing.Point(7, 68);
|
||||||
this.inProgressSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
this.inProgressSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||||
this.inProgressSelectControl.Name = "inProgressSelectControl";
|
this.inProgressSelectControl.Name = "inProgressSelectControl";
|
||||||
@ -323,7 +328,7 @@
|
|||||||
// booksSelectControl
|
// booksSelectControl
|
||||||
//
|
//
|
||||||
this.booksSelectControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.booksSelectControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.booksSelectControl.Location = new System.Drawing.Point(7, 37);
|
this.booksSelectControl.Location = new System.Drawing.Point(7, 37);
|
||||||
this.booksSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
this.booksSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||||
this.booksSelectControl.Name = "booksSelectControl";
|
this.booksSelectControl.Name = "booksSelectControl";
|
||||||
@ -351,8 +356,8 @@
|
|||||||
// tabControl
|
// tabControl
|
||||||
//
|
//
|
||||||
this.tabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
this.tabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
| System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.tabControl.Controls.Add(this.tab1ImportantSettings);
|
this.tabControl.Controls.Add(this.tab1ImportantSettings);
|
||||||
this.tabControl.Controls.Add(this.tab2ImportLibrary);
|
this.tabControl.Controls.Add(this.tab2ImportLibrary);
|
||||||
this.tabControl.Controls.Add(this.tab3DownloadDecrypt);
|
this.tabControl.Controls.Add(this.tab3DownloadDecrypt);
|
||||||
@ -380,7 +385,7 @@
|
|||||||
// booksGb
|
// booksGb
|
||||||
//
|
//
|
||||||
this.booksGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.booksGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.booksGb.Controls.Add(this.saveEpisodesToSeriesFolderCbox);
|
this.booksGb.Controls.Add(this.saveEpisodesToSeriesFolderCbox);
|
||||||
this.booksGb.Controls.Add(this.booksSelectControl);
|
this.booksGb.Controls.Add(this.booksSelectControl);
|
||||||
this.booksGb.Controls.Add(this.booksLocationDescLbl);
|
this.booksGb.Controls.Add(this.booksLocationDescLbl);
|
||||||
@ -391,6 +396,16 @@
|
|||||||
this.booksGb.TabStop = false;
|
this.booksGb.TabStop = false;
|
||||||
this.booksGb.Text = "Books location";
|
this.booksGb.Text = "Books location";
|
||||||
//
|
//
|
||||||
|
// saveEpisodesToSeriesFolderCbox
|
||||||
|
//
|
||||||
|
this.saveEpisodesToSeriesFolderCbox.AutoSize = true;
|
||||||
|
this.saveEpisodesToSeriesFolderCbox.Location = new System.Drawing.Point(7, 131);
|
||||||
|
this.saveEpisodesToSeriesFolderCbox.Name = "saveEpisodesToSeriesFolderCbox";
|
||||||
|
this.saveEpisodesToSeriesFolderCbox.Size = new System.Drawing.Size(191, 19);
|
||||||
|
this.saveEpisodesToSeriesFolderCbox.TabIndex = 3;
|
||||||
|
this.saveEpisodesToSeriesFolderCbox.Text = "[Save Episodes To Series Folder]";
|
||||||
|
this.saveEpisodesToSeriesFolderCbox.UseVisualStyleBackColor = true;
|
||||||
|
//
|
||||||
// tab2ImportLibrary
|
// tab2ImportLibrary
|
||||||
//
|
//
|
||||||
this.tab2ImportLibrary.Controls.Add(this.autoDownloadEpisodesCb);
|
this.tab2ImportLibrary.Controls.Add(this.autoDownloadEpisodesCb);
|
||||||
@ -452,7 +467,7 @@
|
|||||||
// inProgressFilesGb
|
// inProgressFilesGb
|
||||||
//
|
//
|
||||||
this.inProgressFilesGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.inProgressFilesGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.inProgressFilesGb.Controls.Add(this.inProgressDescLbl);
|
this.inProgressFilesGb.Controls.Add(this.inProgressDescLbl);
|
||||||
this.inProgressFilesGb.Controls.Add(this.inProgressSelectControl);
|
this.inProgressFilesGb.Controls.Add(this.inProgressSelectControl);
|
||||||
this.inProgressFilesGb.Location = new System.Drawing.Point(7, 251);
|
this.inProgressFilesGb.Location = new System.Drawing.Point(7, 251);
|
||||||
@ -465,7 +480,7 @@
|
|||||||
// customFileNamingGb
|
// customFileNamingGb
|
||||||
//
|
//
|
||||||
this.customFileNamingGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.customFileNamingGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateBtn);
|
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateBtn);
|
||||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateTb);
|
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateTb);
|
||||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateLbl);
|
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateLbl);
|
||||||
@ -496,7 +511,7 @@
|
|||||||
// chapterFileTemplateTb
|
// chapterFileTemplateTb
|
||||||
//
|
//
|
||||||
this.chapterFileTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.chapterFileTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.chapterFileTemplateTb.Location = new System.Drawing.Point(6, 125);
|
this.chapterFileTemplateTb.Location = new System.Drawing.Point(6, 125);
|
||||||
this.chapterFileTemplateTb.Name = "chapterFileTemplateTb";
|
this.chapterFileTemplateTb.Name = "chapterFileTemplateTb";
|
||||||
this.chapterFileTemplateTb.ReadOnly = true;
|
this.chapterFileTemplateTb.ReadOnly = true;
|
||||||
@ -508,9 +523,9 @@
|
|||||||
this.chapterFileTemplateLbl.AutoSize = true;
|
this.chapterFileTemplateLbl.AutoSize = true;
|
||||||
this.chapterFileTemplateLbl.Location = new System.Drawing.Point(6, 107);
|
this.chapterFileTemplateLbl.Location = new System.Drawing.Point(6, 107);
|
||||||
this.chapterFileTemplateLbl.Name = "chapterFileTemplateLbl";
|
this.chapterFileTemplateLbl.Name = "chapterFileTemplateLbl";
|
||||||
this.chapterFileTemplateLbl.Size = new System.Drawing.Size(123, 15);
|
this.chapterFileTemplateLbl.Size = new System.Drawing.Size(132, 15);
|
||||||
this.chapterFileTemplateLbl.TabIndex = 6;
|
this.chapterFileTemplateLbl.TabIndex = 6;
|
||||||
this.chapterFileTemplateLbl.Text = "[folder template desc]";
|
this.chapterFileTemplateLbl.Text = "[chapter template desc]";
|
||||||
//
|
//
|
||||||
// fileTemplateBtn
|
// fileTemplateBtn
|
||||||
//
|
//
|
||||||
@ -526,7 +541,7 @@
|
|||||||
// fileTemplateTb
|
// fileTemplateTb
|
||||||
//
|
//
|
||||||
this.fileTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.fileTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.fileTemplateTb.Location = new System.Drawing.Point(6, 81);
|
this.fileTemplateTb.Location = new System.Drawing.Point(6, 81);
|
||||||
this.fileTemplateTb.Name = "fileTemplateTb";
|
this.fileTemplateTb.Name = "fileTemplateTb";
|
||||||
this.fileTemplateTb.ReadOnly = true;
|
this.fileTemplateTb.ReadOnly = true;
|
||||||
@ -538,9 +553,9 @@
|
|||||||
this.fileTemplateLbl.AutoSize = true;
|
this.fileTemplateLbl.AutoSize = true;
|
||||||
this.fileTemplateLbl.Location = new System.Drawing.Point(6, 63);
|
this.fileTemplateLbl.Location = new System.Drawing.Point(6, 63);
|
||||||
this.fileTemplateLbl.Name = "fileTemplateLbl";
|
this.fileTemplateLbl.Name = "fileTemplateLbl";
|
||||||
this.fileTemplateLbl.Size = new System.Drawing.Size(123, 15);
|
this.fileTemplateLbl.Size = new System.Drawing.Size(108, 15);
|
||||||
this.fileTemplateLbl.TabIndex = 3;
|
this.fileTemplateLbl.TabIndex = 3;
|
||||||
this.fileTemplateLbl.Text = "[folder template desc]";
|
this.fileTemplateLbl.Text = "[file template desc]";
|
||||||
//
|
//
|
||||||
// folderTemplateBtn
|
// folderTemplateBtn
|
||||||
//
|
//
|
||||||
@ -556,7 +571,7 @@
|
|||||||
// folderTemplateTb
|
// folderTemplateTb
|
||||||
//
|
//
|
||||||
this.folderTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.folderTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.folderTemplateTb.Location = new System.Drawing.Point(5, 37);
|
this.folderTemplateTb.Location = new System.Drawing.Point(5, 37);
|
||||||
this.folderTemplateTb.Name = "folderTemplateTb";
|
this.folderTemplateTb.Name = "folderTemplateTb";
|
||||||
this.folderTemplateTb.ReadOnly = true;
|
this.folderTemplateTb.ReadOnly = true;
|
||||||
@ -574,6 +589,7 @@
|
|||||||
//
|
//
|
||||||
// tab4AudioFileOptions
|
// tab4AudioFileOptions
|
||||||
//
|
//
|
||||||
|
this.tab4AudioFileOptions.Controls.Add(this.chapterTitleTemplateGb);
|
||||||
this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb);
|
this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb);
|
||||||
this.tab4AudioFileOptions.Controls.Add(this.convertLossyRb);
|
this.tab4AudioFileOptions.Controls.Add(this.convertLossyRb);
|
||||||
this.tab4AudioFileOptions.Controls.Add(this.stripAudibleBrandingCbox);
|
this.tab4AudioFileOptions.Controls.Add(this.stripAudibleBrandingCbox);
|
||||||
@ -592,6 +608,38 @@
|
|||||||
this.tab4AudioFileOptions.Text = "Audio File Options";
|
this.tab4AudioFileOptions.Text = "Audio File Options";
|
||||||
this.tab4AudioFileOptions.UseVisualStyleBackColor = true;
|
this.tab4AudioFileOptions.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
|
// chapterTitleTemplateGb
|
||||||
|
//
|
||||||
|
this.chapterTitleTemplateGb.Controls.Add(this.chapterTitleTemplateBtn);
|
||||||
|
this.chapterTitleTemplateGb.Controls.Add(this.chapterTitleTemplateTb);
|
||||||
|
this.chapterTitleTemplateGb.Location = new System.Drawing.Point(6, 335);
|
||||||
|
this.chapterTitleTemplateGb.Name = "chapterTitleTemplateGb";
|
||||||
|
this.chapterTitleTemplateGb.Size = new System.Drawing.Size(842, 54);
|
||||||
|
this.chapterTitleTemplateGb.TabIndex = 18;
|
||||||
|
this.chapterTitleTemplateGb.TabStop = false;
|
||||||
|
this.chapterTitleTemplateGb.Text = "[chapter title template desc]";
|
||||||
|
//
|
||||||
|
// chapterTitleTemplateBtn
|
||||||
|
//
|
||||||
|
this.chapterTitleTemplateBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.chapterTitleTemplateBtn.Location = new System.Drawing.Point(764, 22);
|
||||||
|
this.chapterTitleTemplateBtn.Name = "chapterTitleTemplateBtn";
|
||||||
|
this.chapterTitleTemplateBtn.Size = new System.Drawing.Size(75, 23);
|
||||||
|
this.chapterTitleTemplateBtn.TabIndex = 17;
|
||||||
|
this.chapterTitleTemplateBtn.Text = "Edit...";
|
||||||
|
this.chapterTitleTemplateBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.chapterTitleTemplateBtn.Click += new System.EventHandler(this.chapterTitleTemplateBtn_Click);
|
||||||
|
//
|
||||||
|
// chapterTitleTemplateTb
|
||||||
|
//
|
||||||
|
this.chapterTitleTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.chapterTitleTemplateTb.Location = new System.Drawing.Point(6, 22);
|
||||||
|
this.chapterTitleTemplateTb.Name = "chapterTitleTemplateTb";
|
||||||
|
this.chapterTitleTemplateTb.ReadOnly = true;
|
||||||
|
this.chapterTitleTemplateTb.Size = new System.Drawing.Size(752, 23);
|
||||||
|
this.chapterTitleTemplateTb.TabIndex = 16;
|
||||||
|
//
|
||||||
// lameOptionsGb
|
// lameOptionsGb
|
||||||
//
|
//
|
||||||
this.lameOptionsGb.Controls.Add(this.lameDownsampleMonoCbox);
|
this.lameOptionsGb.Controls.Add(this.lameDownsampleMonoCbox);
|
||||||
@ -599,9 +647,9 @@
|
|||||||
this.lameOptionsGb.Controls.Add(this.label1);
|
this.lameOptionsGb.Controls.Add(this.label1);
|
||||||
this.lameOptionsGb.Controls.Add(this.lameQualityGb);
|
this.lameOptionsGb.Controls.Add(this.lameQualityGb);
|
||||||
this.lameOptionsGb.Controls.Add(this.groupBox2);
|
this.lameOptionsGb.Controls.Add(this.groupBox2);
|
||||||
this.lameOptionsGb.Location = new System.Drawing.Point(415, 18);
|
this.lameOptionsGb.Location = new System.Drawing.Point(415, 6);
|
||||||
this.lameOptionsGb.Name = "lameOptionsGb";
|
this.lameOptionsGb.Name = "lameOptionsGb";
|
||||||
this.lameOptionsGb.Size = new System.Drawing.Size(433, 371);
|
this.lameOptionsGb.Size = new System.Drawing.Size(433, 323);
|
||||||
this.lameOptionsGb.TabIndex = 14;
|
this.lameOptionsGb.TabIndex = 14;
|
||||||
this.lameOptionsGb.TabStop = false;
|
this.lameOptionsGb.TabStop = false;
|
||||||
this.lameOptionsGb.Text = "Mp3 Encoding Options";
|
this.lameOptionsGb.Text = "Mp3 Encoding Options";
|
||||||
@ -629,7 +677,7 @@
|
|||||||
this.lameBitrateGb.Controls.Add(this.lameBitrateTb);
|
this.lameBitrateGb.Controls.Add(this.lameBitrateTb);
|
||||||
this.lameBitrateGb.Location = new System.Drawing.Point(6, 84);
|
this.lameBitrateGb.Location = new System.Drawing.Point(6, 84);
|
||||||
this.lameBitrateGb.Name = "lameBitrateGb";
|
this.lameBitrateGb.Name = "lameBitrateGb";
|
||||||
this.lameBitrateGb.Size = new System.Drawing.Size(421, 112);
|
this.lameBitrateGb.Size = new System.Drawing.Size(421, 101);
|
||||||
this.lameBitrateGb.TabIndex = 0;
|
this.lameBitrateGb.TabIndex = 0;
|
||||||
this.lameBitrateGb.TabStop = false;
|
this.lameBitrateGb.TabStop = false;
|
||||||
this.lameBitrateGb.Text = "Bitrate";
|
this.lameBitrateGb.Text = "Bitrate";
|
||||||
@ -637,7 +685,7 @@
|
|||||||
// LameMatchSourceBRCbox
|
// LameMatchSourceBRCbox
|
||||||
//
|
//
|
||||||
this.LameMatchSourceBRCbox.AutoSize = true;
|
this.LameMatchSourceBRCbox.AutoSize = true;
|
||||||
this.LameMatchSourceBRCbox.Location = new System.Drawing.Point(260, 87);
|
this.LameMatchSourceBRCbox.Location = new System.Drawing.Point(260, 77);
|
||||||
this.LameMatchSourceBRCbox.Name = "LameMatchSourceBRCbox";
|
this.LameMatchSourceBRCbox.Name = "LameMatchSourceBRCbox";
|
||||||
this.LameMatchSourceBRCbox.Size = new System.Drawing.Size(140, 19);
|
this.LameMatchSourceBRCbox.Size = new System.Drawing.Size(140, 19);
|
||||||
this.LameMatchSourceBRCbox.TabIndex = 3;
|
this.LameMatchSourceBRCbox.TabIndex = 3;
|
||||||
@ -648,7 +696,7 @@
|
|||||||
// lameConstantBitrateCbox
|
// lameConstantBitrateCbox
|
||||||
//
|
//
|
||||||
this.lameConstantBitrateCbox.AutoSize = true;
|
this.lameConstantBitrateCbox.AutoSize = true;
|
||||||
this.lameConstantBitrateCbox.Location = new System.Drawing.Point(6, 87);
|
this.lameConstantBitrateCbox.Location = new System.Drawing.Point(6, 77);
|
||||||
this.lameConstantBitrateCbox.Name = "lameConstantBitrateCbox";
|
this.lameConstantBitrateCbox.Name = "lameConstantBitrateCbox";
|
||||||
this.lameConstantBitrateCbox.Size = new System.Drawing.Size(216, 19);
|
this.lameConstantBitrateCbox.Size = new System.Drawing.Size(216, 19);
|
||||||
this.lameConstantBitrateCbox.TabIndex = 2;
|
this.lameConstantBitrateCbox.TabIndex = 2;
|
||||||
@ -734,7 +782,7 @@
|
|||||||
this.label1.AutoSize = true;
|
this.label1.AutoSize = true;
|
||||||
this.label1.Enabled = false;
|
this.label1.Enabled = false;
|
||||||
this.label1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point);
|
this.label1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Italic, System.Drawing.GraphicsUnit.Point);
|
||||||
this.label1.Location = new System.Drawing.Point(6, 353);
|
this.label1.Location = new System.Drawing.Point(6, 298);
|
||||||
this.label1.Name = "label1";
|
this.label1.Name = "label1";
|
||||||
this.label1.Size = new System.Drawing.Size(172, 15);
|
this.label1.Size = new System.Drawing.Size(172, 15);
|
||||||
this.label1.TabIndex = 1;
|
this.label1.TabIndex = 1;
|
||||||
@ -755,7 +803,7 @@
|
|||||||
this.lameQualityGb.Controls.Add(this.label14);
|
this.lameQualityGb.Controls.Add(this.label14);
|
||||||
this.lameQualityGb.Controls.Add(this.label2);
|
this.lameQualityGb.Controls.Add(this.label2);
|
||||||
this.lameQualityGb.Controls.Add(this.lameVBRQualityTb);
|
this.lameQualityGb.Controls.Add(this.lameVBRQualityTb);
|
||||||
this.lameQualityGb.Location = new System.Drawing.Point(6, 202);
|
this.lameQualityGb.Location = new System.Drawing.Point(6, 186);
|
||||||
this.lameQualityGb.Name = "lameQualityGb";
|
this.lameQualityGb.Name = "lameQualityGb";
|
||||||
this.lameQualityGb.Size = new System.Drawing.Size(421, 109);
|
this.lameQualityGb.Size = new System.Drawing.Size(421, 109);
|
||||||
this.lameQualityGb.TabIndex = 0;
|
this.lameQualityGb.TabIndex = 0;
|
||||||
@ -963,16 +1011,6 @@
|
|||||||
this.createCueSheetCbox.UseVisualStyleBackColor = true;
|
this.createCueSheetCbox.UseVisualStyleBackColor = true;
|
||||||
this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
|
this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
|
||||||
//
|
//
|
||||||
// saveEpisodesToSeriesFolderCbox
|
|
||||||
//
|
|
||||||
this.saveEpisodesToSeriesFolderCbox.AutoSize = true;
|
|
||||||
this.saveEpisodesToSeriesFolderCbox.Location = new System.Drawing.Point(7, 131);
|
|
||||||
this.saveEpisodesToSeriesFolderCbox.Name = "saveEpisodesToSeriesFolderCbox";
|
|
||||||
this.saveEpisodesToSeriesFolderCbox.Size = new System.Drawing.Size(191, 19);
|
|
||||||
this.saveEpisodesToSeriesFolderCbox.TabIndex = 3;
|
|
||||||
this.saveEpisodesToSeriesFolderCbox.Text = "[Save Episodes To Series Folder]";
|
|
||||||
this.saveEpisodesToSeriesFolderCbox.UseVisualStyleBackColor = true;
|
|
||||||
//
|
|
||||||
// SettingsDialog
|
// SettingsDialog
|
||||||
//
|
//
|
||||||
this.AcceptButton = this.saveBtn;
|
this.AcceptButton = this.saveBtn;
|
||||||
@ -1007,6 +1045,8 @@
|
|||||||
this.customFileNamingGb.PerformLayout();
|
this.customFileNamingGb.PerformLayout();
|
||||||
this.tab4AudioFileOptions.ResumeLayout(false);
|
this.tab4AudioFileOptions.ResumeLayout(false);
|
||||||
this.tab4AudioFileOptions.PerformLayout();
|
this.tab4AudioFileOptions.PerformLayout();
|
||||||
|
this.chapterTitleTemplateGb.ResumeLayout(false);
|
||||||
|
this.chapterTitleTemplateGb.PerformLayout();
|
||||||
this.lameOptionsGb.ResumeLayout(false);
|
this.lameOptionsGb.ResumeLayout(false);
|
||||||
this.lameOptionsGb.PerformLayout();
|
this.lameOptionsGb.PerformLayout();
|
||||||
this.lameBitrateGb.ResumeLayout(false);
|
this.lameBitrateGb.ResumeLayout(false);
|
||||||
@ -1026,11 +1066,11 @@
|
|||||||
private System.Windows.Forms.Label inProgressDescLbl;
|
private System.Windows.Forms.Label inProgressDescLbl;
|
||||||
private System.Windows.Forms.Button saveBtn;
|
private System.Windows.Forms.Button saveBtn;
|
||||||
private System.Windows.Forms.Button cancelBtn;
|
private System.Windows.Forms.Button cancelBtn;
|
||||||
private System.Windows.Forms.CheckBox allowLibationFixupCbox;
|
private System.Windows.Forms.CheckBox allowLibationFixupCbox;
|
||||||
private DirectoryOrCustomSelectControl booksSelectControl;
|
private DirectoryOrCustomSelectControl booksSelectControl;
|
||||||
private DirectorySelectControl inProgressSelectControl;
|
private DirectorySelectControl inProgressSelectControl;
|
||||||
private System.Windows.Forms.RadioButton convertLossyRb;
|
private System.Windows.Forms.RadioButton convertLossyRb;
|
||||||
private System.Windows.Forms.RadioButton convertLosslessRb;
|
private System.Windows.Forms.RadioButton convertLosslessRb;
|
||||||
private System.Windows.Forms.Button logsBtn;
|
private System.Windows.Forms.Button logsBtn;
|
||||||
private System.Windows.Forms.Label loggingLevelLbl;
|
private System.Windows.Forms.Label loggingLevelLbl;
|
||||||
private System.Windows.Forms.ComboBox loggingLevelCb;
|
private System.Windows.Forms.ComboBox loggingLevelCb;
|
||||||
@ -1041,7 +1081,7 @@
|
|||||||
private System.Windows.Forms.RadioButton badBookIgnoreRb;
|
private System.Windows.Forms.RadioButton badBookIgnoreRb;
|
||||||
private System.Windows.Forms.CheckBox downloadEpisodesCb;
|
private System.Windows.Forms.CheckBox downloadEpisodesCb;
|
||||||
private System.Windows.Forms.CheckBox importEpisodesCb;
|
private System.Windows.Forms.CheckBox importEpisodesCb;
|
||||||
private System.Windows.Forms.CheckBox splitFilesByChapterCbox;
|
private System.Windows.Forms.CheckBox splitFilesByChapterCbox;
|
||||||
private System.Windows.Forms.TabControl tabControl;
|
private System.Windows.Forms.TabControl tabControl;
|
||||||
private System.Windows.Forms.TabPage tab1ImportantSettings;
|
private System.Windows.Forms.TabPage tab1ImportantSettings;
|
||||||
private System.Windows.Forms.GroupBox booksGb;
|
private System.Windows.Forms.GroupBox booksGb;
|
||||||
@ -1058,7 +1098,7 @@
|
|||||||
private System.Windows.Forms.Button folderTemplateBtn;
|
private System.Windows.Forms.Button folderTemplateBtn;
|
||||||
private System.Windows.Forms.TextBox folderTemplateTb;
|
private System.Windows.Forms.TextBox folderTemplateTb;
|
||||||
private System.Windows.Forms.Label folderTemplateLbl;
|
private System.Windows.Forms.Label folderTemplateLbl;
|
||||||
private System.Windows.Forms.CheckBox showImportedStatsCb;
|
private System.Windows.Forms.CheckBox showImportedStatsCb;
|
||||||
private System.Windows.Forms.CheckBox stripAudibleBrandingCbox;
|
private System.Windows.Forms.CheckBox stripAudibleBrandingCbox;
|
||||||
private System.Windows.Forms.TabPage tab4AudioFileOptions;
|
private System.Windows.Forms.TabPage tab4AudioFileOptions;
|
||||||
private System.Windows.Forms.CheckBox retainAaxFileCbox;
|
private System.Windows.Forms.CheckBox retainAaxFileCbox;
|
||||||
@ -1094,9 +1134,12 @@
|
|||||||
private System.Windows.Forms.Label label17;
|
private System.Windows.Forms.Label label17;
|
||||||
private System.Windows.Forms.Label label16;
|
private System.Windows.Forms.Label label16;
|
||||||
private System.Windows.Forms.CheckBox createCueSheetCbox;
|
private System.Windows.Forms.CheckBox createCueSheetCbox;
|
||||||
private System.Windows.Forms.CheckBox autoScanCb;
|
private System.Windows.Forms.CheckBox autoScanCb;
|
||||||
private System.Windows.Forms.CheckBox downloadCoverArtCbox;
|
private System.Windows.Forms.CheckBox downloadCoverArtCbox;
|
||||||
private System.Windows.Forms.CheckBox autoDownloadEpisodesCb;
|
private System.Windows.Forms.CheckBox autoDownloadEpisodesCb;
|
||||||
private System.Windows.Forms.CheckBox saveEpisodesToSeriesFolderCbox;
|
private System.Windows.Forms.CheckBox saveEpisodesToSeriesFolderCbox;
|
||||||
|
private System.Windows.Forms.GroupBox chapterTitleTemplateGb;
|
||||||
|
private System.Windows.Forms.Button chapterTitleTemplateBtn;
|
||||||
|
private System.Windows.Forms.TextBox chapterTitleTemplateTb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Dinah.Core;
|
||||||
|
using LibationFileManager;
|
||||||
|
|
||||||
|
namespace LibationWinForms.Dialogs
|
||||||
|
{
|
||||||
|
public partial class SettingsDialog
|
||||||
|
{
|
||||||
|
private void folderTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.Folder, folderTemplateTb);
|
||||||
|
private void fileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.File, fileTemplateTb);
|
||||||
|
private void chapterFileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.ChapterFile, chapterFileTemplateTb);
|
||||||
|
|
||||||
|
private void Load_DownloadDecrypt(Configuration config)
|
||||||
|
{
|
||||||
|
inProgressDescLbl.Text = desc(nameof(config.InProgress));
|
||||||
|
badBookGb.Text = desc(nameof(config.BadBook));
|
||||||
|
badBookAskRb.Text = Configuration.BadBookAction.Ask.GetDescription();
|
||||||
|
badBookAbortRb.Text = Configuration.BadBookAction.Abort.GetDescription();
|
||||||
|
badBookRetryRb.Text = Configuration.BadBookAction.Retry.GetDescription();
|
||||||
|
badBookIgnoreRb.Text = Configuration.BadBookAction.Ignore.GetDescription();
|
||||||
|
|
||||||
|
inProgressSelectControl.SetDirectoryItems(new()
|
||||||
|
{
|
||||||
|
Configuration.KnownDirectories.WinTemp,
|
||||||
|
Configuration.KnownDirectories.UserProfile,
|
||||||
|
Configuration.KnownDirectories.AppDir,
|
||||||
|
Configuration.KnownDirectories.MyDocs,
|
||||||
|
Configuration.KnownDirectories.LibationFiles
|
||||||
|
}, Configuration.KnownDirectories.WinTemp);
|
||||||
|
inProgressSelectControl.SelectDirectory(config.InProgress);
|
||||||
|
|
||||||
|
var rb = config.BadBook switch
|
||||||
|
{
|
||||||
|
Configuration.BadBookAction.Ask => this.badBookAskRb,
|
||||||
|
Configuration.BadBookAction.Abort => this.badBookAbortRb,
|
||||||
|
Configuration.BadBookAction.Retry => this.badBookRetryRb,
|
||||||
|
Configuration.BadBookAction.Ignore => this.badBookIgnoreRb,
|
||||||
|
_ => this.badBookAskRb
|
||||||
|
};
|
||||||
|
rb.Checked = true;
|
||||||
|
|
||||||
|
folderTemplateLbl.Text = desc(nameof(config.FolderTemplate));
|
||||||
|
fileTemplateLbl.Text = desc(nameof(config.FileTemplate));
|
||||||
|
chapterFileTemplateLbl.Text = desc(nameof(config.ChapterFileTemplate));
|
||||||
|
folderTemplateTb.Text = config.FolderTemplate;
|
||||||
|
fileTemplateTb.Text = config.FileTemplate;
|
||||||
|
chapterFileTemplateTb.Text = config.ChapterFileTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Save_DownloadDecrypt(Configuration config)
|
||||||
|
{
|
||||||
|
config.InProgress = inProgressSelectControl.SelectedDirectory;
|
||||||
|
|
||||||
|
config.BadBook
|
||||||
|
= badBookAskRb.Checked ? Configuration.BadBookAction.Ask
|
||||||
|
: badBookAbortRb.Checked ? Configuration.BadBookAction.Abort
|
||||||
|
: badBookRetryRb.Checked ? Configuration.BadBookAction.Retry
|
||||||
|
: badBookIgnoreRb.Checked ? Configuration.BadBookAction.Ignore
|
||||||
|
: Configuration.BadBookAction.Ask;
|
||||||
|
|
||||||
|
config.FolderTemplate = folderTemplateTb.Text;
|
||||||
|
config.FileTemplate = fileTemplateTb.Text;
|
||||||
|
config.ChapterFileTemplate = chapterFileTemplateTb.Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
using LibationFileManager;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LibationWinForms.Dialogs
|
||||||
|
{
|
||||||
|
public partial class SettingsDialog
|
||||||
|
{
|
||||||
|
private void Load_ImportLibrary(Configuration config)
|
||||||
|
{
|
||||||
|
this.autoScanCb.Text = desc(nameof(config.AutoScan));
|
||||||
|
this.showImportedStatsCb.Text = desc(nameof(config.ShowImportedStats));
|
||||||
|
this.importEpisodesCb.Text = desc(nameof(config.ImportEpisodes));
|
||||||
|
this.downloadEpisodesCb.Text = desc(nameof(config.DownloadEpisodes));
|
||||||
|
this.autoDownloadEpisodesCb.Text = desc(nameof(config.AutoDownloadEpisodes));
|
||||||
|
|
||||||
|
autoScanCb.Checked = config.AutoScan;
|
||||||
|
showImportedStatsCb.Checked = config.ShowImportedStats;
|
||||||
|
importEpisodesCb.Checked = config.ImportEpisodes;
|
||||||
|
downloadEpisodesCb.Checked = config.DownloadEpisodes;
|
||||||
|
autoDownloadEpisodesCb.Checked = config.AutoDownloadEpisodes;
|
||||||
|
}
|
||||||
|
private void Save_ImportLibrary(Configuration config)
|
||||||
|
{
|
||||||
|
config.AutoScan = autoScanCb.Checked;
|
||||||
|
config.ShowImportedStats = showImportedStatsCb.Checked;
|
||||||
|
config.ImportEpisodes = importEpisodesCb.Checked;
|
||||||
|
config.DownloadEpisodes = downloadEpisodesCb.Checked;
|
||||||
|
config.AutoDownloadEpisodes = autoDownloadEpisodesCb.Checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
98
Source/LibationWinForms/Dialogs/SettingsDialog.Important.cs
Normal file
98
Source/LibationWinForms/Dialogs/SettingsDialog.Important.cs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
using Dinah.Core;
|
||||||
|
using FileManager;
|
||||||
|
using LibationFileManager;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace LibationWinForms.Dialogs
|
||||||
|
{
|
||||||
|
public partial class SettingsDialog
|
||||||
|
{
|
||||||
|
private void logsBtn_Click(object sender, EventArgs e) => Go.To.Folder(((LongPath)Configuration.Instance.LibationFiles).ShortPathName);
|
||||||
|
|
||||||
|
private void Load_Important(Configuration config)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
loggingLevelCb.Items.Clear();
|
||||||
|
foreach (var level in Enum<Serilog.Events.LogEventLevel>.GetValues())
|
||||||
|
loggingLevelCb.Items.Add(level);
|
||||||
|
loggingLevelCb.SelectedItem = config.LogLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
booksLocationDescLbl.Text = desc(nameof(config.Books));
|
||||||
|
this.saveEpisodesToSeriesFolderCbox.Text = desc(nameof(config.SavePodcastsToParentFolder));
|
||||||
|
|
||||||
|
booksSelectControl.SetSearchTitle("books location");
|
||||||
|
booksSelectControl.SetDirectoryItems(
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Configuration.KnownDirectories.UserProfile,
|
||||||
|
Configuration.KnownDirectories.AppDir,
|
||||||
|
Configuration.KnownDirectories.MyDocs
|
||||||
|
},
|
||||||
|
Configuration.KnownDirectories.UserProfile,
|
||||||
|
"Books");
|
||||||
|
booksSelectControl.SelectDirectory(config.Books);
|
||||||
|
|
||||||
|
saveEpisodesToSeriesFolderCbox.Checked = config.SavePodcastsToParentFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Save_Important(Configuration config)
|
||||||
|
{
|
||||||
|
var newBooks = booksSelectControl.SelectedDirectory;
|
||||||
|
|
||||||
|
#region validation
|
||||||
|
static void validationError(string text, string caption)
|
||||||
|
=> MessageBox.Show(text, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
if (string.IsNullOrWhiteSpace(newBooks))
|
||||||
|
{
|
||||||
|
validationError("Cannot set Books Location to blank", "Location is blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(newBooks) && booksSelectControl.SelectedDirectoryIsCustom)
|
||||||
|
{
|
||||||
|
validationError($"Not saving change to Books location. This folder does not exist:\r\n{newBooks}", "Folder does not exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// these 3 should do nothing. Configuration will only init these with a valid value. EditTemplateDialog ensures valid before returning
|
||||||
|
if (!Templates.Folder.IsValid(folderTemplateTb.Text))
|
||||||
|
{
|
||||||
|
validationError($"Not saving change to folder naming template. Invalid format.", "Invalid folder template");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Templates.File.IsValid(fileTemplateTb.Text))
|
||||||
|
{
|
||||||
|
validationError($"Not saving change to file naming template. Invalid format.", "Invalid file template");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!Templates.ChapterFile.IsValid(chapterFileTemplateTb.Text))
|
||||||
|
{
|
||||||
|
validationError($"Not saving change to chapter file naming template. Invalid format.", "Invalid chapter file template");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
if (!Directory.Exists(newBooks) && booksSelectControl.SelectedDirectoryIsKnown)
|
||||||
|
Directory.CreateDirectory(newBooks);
|
||||||
|
|
||||||
|
config.Books = newBooks;
|
||||||
|
|
||||||
|
{
|
||||||
|
var logLevelOld = config.LogLevel;
|
||||||
|
var logLevelNew = (Serilog.Events.LogEventLevel)loggingLevelCb.SelectedItem;
|
||||||
|
|
||||||
|
config.LogLevel = logLevelNew;
|
||||||
|
|
||||||
|
// only warn if changed during this time. don't want to warn every time user happens to change settings while level is verbose
|
||||||
|
if (logLevelOld != logLevelNew)
|
||||||
|
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
config.SavePodcastsToParentFolder = saveEpisodesToSeriesFolderCbox.Checked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.IO;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Dinah.Core;
|
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using FileManager;
|
|
||||||
|
|
||||||
namespace LibationWinForms.Dialogs
|
namespace LibationWinForms.Dialogs
|
||||||
{
|
{
|
||||||
@ -24,111 +21,12 @@ namespace LibationWinForms.Dialogs
|
|||||||
if (this.DesignMode)
|
if (this.DesignMode)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
{
|
Load_Important(config);
|
||||||
loggingLevelCb.Items.Clear();
|
Load_ImportLibrary(config);
|
||||||
foreach (var level in Enum<Serilog.Events.LogEventLevel>.GetValues())
|
Load_DownloadDecrypt(config);
|
||||||
loggingLevelCb.Items.Add(level);
|
Load_AudioSettings(config);
|
||||||
loggingLevelCb.SelectedItem = config.LogLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.autoScanCb.Text = desc(nameof(config.AutoScan));
|
|
||||||
this.showImportedStatsCb.Text = desc(nameof(config.ShowImportedStats));
|
|
||||||
this.importEpisodesCb.Text = desc(nameof(config.ImportEpisodes));
|
|
||||||
this.downloadEpisodesCb.Text = desc(nameof(config.DownloadEpisodes));
|
|
||||||
this.autoDownloadEpisodesCb.Text = desc(nameof(config.AutoDownloadEpisodes));
|
|
||||||
|
|
||||||
this.booksLocationDescLbl.Text = desc(nameof(config.Books));
|
|
||||||
this.inProgressDescLbl.Text = desc(nameof(config.InProgress));
|
|
||||||
this.allowLibationFixupCbox.Text = desc(nameof(config.AllowLibationFixup));
|
|
||||||
this.splitFilesByChapterCbox.Text = desc(nameof(config.SplitFilesByChapter));
|
|
||||||
this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio));
|
|
||||||
this.retainAaxFileCbox.Text = desc(nameof(config.RetainAaxFile));
|
|
||||||
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
|
|
||||||
this.createCueSheetCbox.Text = desc(nameof(config.CreateCueSheet));
|
|
||||||
this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt));
|
|
||||||
this.saveEpisodesToSeriesFolderCbox.Text = desc(nameof(config.SavePodcastsToParentFolder));
|
|
||||||
|
|
||||||
booksSelectControl.SetSearchTitle("books location");
|
|
||||||
booksSelectControl.SetDirectoryItems(
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Configuration.KnownDirectories.UserProfile,
|
|
||||||
Configuration.KnownDirectories.AppDir,
|
|
||||||
Configuration.KnownDirectories.MyDocs
|
|
||||||
},
|
|
||||||
Configuration.KnownDirectories.UserProfile,
|
|
||||||
"Books");
|
|
||||||
booksSelectControl.SelectDirectory(config.Books);
|
|
||||||
|
|
||||||
saveEpisodesToSeriesFolderCbox.Checked = config.SavePodcastsToParentFolder;
|
|
||||||
|
|
||||||
allowLibationFixupCbox.Checked = config.AllowLibationFixup;
|
|
||||||
createCueSheetCbox.Checked = config.CreateCueSheet;
|
|
||||||
retainAaxFileCbox.Checked = config.RetainAaxFile;
|
|
||||||
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
|
|
||||||
stripUnabridgedCbox.Checked = config.StripUnabridged;
|
|
||||||
stripAudibleBrandingCbox.Checked = config.StripAudibleBrandAudio;
|
|
||||||
convertLosslessRb.Checked = !config.DecryptToLossy;
|
|
||||||
convertLossyRb.Checked = config.DecryptToLossy;
|
|
||||||
|
|
||||||
lameTargetBitrateRb.Checked = config.LameTargetBitrate;
|
|
||||||
lameTargetQualityRb.Checked = !config.LameTargetBitrate;
|
|
||||||
lameDownsampleMonoCbox.Checked = config.LameDownsampleMono;
|
|
||||||
lameBitrateTb.Value = config.LameBitrate;
|
|
||||||
lameConstantBitrateCbox.Checked = config.LameConstantBitrate;
|
|
||||||
LameMatchSourceBRCbox.Checked = config.LameMatchSourceBR;
|
|
||||||
lameVBRQualityTb.Value = config.LameVBRQuality;
|
|
||||||
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
|
||||||
|
|
||||||
autoScanCb.Checked = config.AutoScan;
|
|
||||||
showImportedStatsCb.Checked = config.ShowImportedStats;
|
|
||||||
importEpisodesCb.Checked = config.ImportEpisodes;
|
|
||||||
downloadEpisodesCb.Checked = config.DownloadEpisodes;
|
|
||||||
autoDownloadEpisodesCb.Checked = config.AutoDownloadEpisodes;
|
|
||||||
|
|
||||||
lameTargetRb_CheckedChanged(this, e);
|
|
||||||
LameMatchSourceBRCbox_CheckedChanged(this, e);
|
|
||||||
convertFormatRb_CheckedChanged(this, e);
|
|
||||||
allowLibationFixupCbox_CheckedChanged(this, e);
|
|
||||||
|
|
||||||
inProgressSelectControl.SetDirectoryItems(new()
|
|
||||||
{
|
|
||||||
Configuration.KnownDirectories.WinTemp,
|
|
||||||
Configuration.KnownDirectories.UserProfile,
|
|
||||||
Configuration.KnownDirectories.AppDir,
|
|
||||||
Configuration.KnownDirectories.MyDocs,
|
|
||||||
Configuration.KnownDirectories.LibationFiles
|
|
||||||
}, Configuration.KnownDirectories.WinTemp);
|
|
||||||
inProgressSelectControl.SelectDirectory(config.InProgress);
|
|
||||||
|
|
||||||
badBookGb.Text = desc(nameof(config.BadBook));
|
|
||||||
badBookAskRb.Text = Configuration.BadBookAction.Ask.GetDescription();
|
|
||||||
badBookAbortRb.Text = Configuration.BadBookAction.Abort.GetDescription();
|
|
||||||
badBookRetryRb.Text = Configuration.BadBookAction.Retry.GetDescription();
|
|
||||||
badBookIgnoreRb.Text = Configuration.BadBookAction.Ignore.GetDescription();
|
|
||||||
var rb = config.BadBook switch
|
|
||||||
{
|
|
||||||
Configuration.BadBookAction.Ask => this.badBookAskRb,
|
|
||||||
Configuration.BadBookAction.Abort => this.badBookAbortRb,
|
|
||||||
Configuration.BadBookAction.Retry => this.badBookRetryRb,
|
|
||||||
Configuration.BadBookAction.Ignore => this.badBookIgnoreRb,
|
|
||||||
_ => this.badBookAskRb
|
|
||||||
};
|
|
||||||
rb.Checked = true;
|
|
||||||
|
|
||||||
folderTemplateLbl.Text = desc(nameof(config.FolderTemplate));
|
|
||||||
fileTemplateLbl.Text = desc(nameof(config.FileTemplate));
|
|
||||||
chapterFileTemplateLbl.Text = desc(nameof(config.ChapterFileTemplate));
|
|
||||||
folderTemplateTb.Text = config.FolderTemplate;
|
|
||||||
fileTemplateTb.Text = config.FileTemplate;
|
|
||||||
chapterFileTemplateTb.Text = config.ChapterFileTemplate;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logsBtn_Click(object sender, EventArgs e) => Go.To.Folder(((LongPath)Configuration.Instance.LibationFiles).ShortPathName);
|
|
||||||
|
|
||||||
private void folderTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.Folder, folderTemplateTb);
|
|
||||||
private void fileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.File, fileTemplateTb);
|
|
||||||
private void chapterFileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.ChapterFile, chapterFileTemplateTb);
|
|
||||||
private static void editTemplate(Templates template, TextBox textBox)
|
private static void editTemplate(Templates template, TextBox textBox)
|
||||||
{
|
{
|
||||||
var form = new EditTemplateDialog(template, textBox.Text);
|
var form = new EditTemplateDialog(template, textBox.Text);
|
||||||
@ -138,93 +36,10 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
private void saveBtn_Click(object sender, EventArgs e)
|
private void saveBtn_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var newBooks = booksSelectControl.SelectedDirectory;
|
Save_Important(config);
|
||||||
|
Save_ImportLibrary(config);
|
||||||
#region validation
|
Save_DownloadDecrypt(config);
|
||||||
static void validationError(string text, string caption)
|
Save_AudioSettings(config);
|
||||||
=> MessageBox.Show(text, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
||||||
if (string.IsNullOrWhiteSpace(newBooks))
|
|
||||||
{
|
|
||||||
validationError("Cannot set Books Location to blank", "Location is blank");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Directory.Exists(newBooks) && booksSelectControl.SelectedDirectoryIsCustom)
|
|
||||||
{
|
|
||||||
validationError($"Not saving change to Books location. This folder does not exist:\r\n{newBooks}", "Folder does not exist");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// these 3 should do nothing. Configuration will only init these with a valid value. EditTemplateDialog ensures valid before returning
|
|
||||||
if (!Templates.Folder.IsValid(folderTemplateTb.Text))
|
|
||||||
{
|
|
||||||
validationError($"Not saving change to folder naming template. Invalid format.", "Invalid folder template");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Templates.File.IsValid(fileTemplateTb.Text))
|
|
||||||
{
|
|
||||||
validationError($"Not saving change to file naming template. Invalid format.", "Invalid file template");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!Templates.ChapterFile.IsValid(chapterFileTemplateTb.Text))
|
|
||||||
{
|
|
||||||
validationError($"Not saving change to chapter file naming template. Invalid format.", "Invalid chapter file template");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
if (!Directory.Exists(newBooks) && booksSelectControl.SelectedDirectoryIsKnown)
|
|
||||||
Directory.CreateDirectory(newBooks);
|
|
||||||
|
|
||||||
config.Books = newBooks;
|
|
||||||
|
|
||||||
{
|
|
||||||
var logLevelOld = config.LogLevel;
|
|
||||||
var logLevelNew = (Serilog.Events.LogEventLevel)loggingLevelCb.SelectedItem;
|
|
||||||
|
|
||||||
config.LogLevel = logLevelNew;
|
|
||||||
|
|
||||||
// only warn if changed during this time. don't want to warn every time user happens to change settings while level is verbose
|
|
||||||
if (logLevelOld != logLevelNew)
|
|
||||||
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
|
|
||||||
}
|
|
||||||
|
|
||||||
config.SavePodcastsToParentFolder = saveEpisodesToSeriesFolderCbox.Checked;
|
|
||||||
|
|
||||||
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
|
||||||
config.CreateCueSheet = createCueSheetCbox.Checked;
|
|
||||||
config.RetainAaxFile = retainAaxFileCbox.Checked;
|
|
||||||
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
|
|
||||||
config.StripUnabridged = stripUnabridgedCbox.Checked;
|
|
||||||
config.StripAudibleBrandAudio = stripAudibleBrandingCbox.Checked;
|
|
||||||
config.DecryptToLossy = convertLossyRb.Checked;
|
|
||||||
|
|
||||||
config.LameTargetBitrate = lameTargetBitrateRb.Checked;
|
|
||||||
config.LameDownsampleMono = lameDownsampleMonoCbox.Checked;
|
|
||||||
config.LameBitrate = lameBitrateTb.Value;
|
|
||||||
config.LameConstantBitrate = lameConstantBitrateCbox.Checked;
|
|
||||||
config.LameMatchSourceBR = LameMatchSourceBRCbox.Checked;
|
|
||||||
config.LameVBRQuality = lameVBRQualityTb.Value;
|
|
||||||
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
|
|
||||||
|
|
||||||
config.AutoScan = autoScanCb.Checked;
|
|
||||||
config.ShowImportedStats = showImportedStatsCb.Checked;
|
|
||||||
config.ImportEpisodes = importEpisodesCb.Checked;
|
|
||||||
config.DownloadEpisodes = downloadEpisodesCb.Checked;
|
|
||||||
config.AutoDownloadEpisodes = autoDownloadEpisodesCb.Checked;
|
|
||||||
|
|
||||||
config.InProgress = inProgressSelectControl.SelectedDirectory;
|
|
||||||
|
|
||||||
config.BadBook
|
|
||||||
= badBookAskRb.Checked ? Configuration.BadBookAction.Ask
|
|
||||||
: badBookAbortRb.Checked ? Configuration.BadBookAction.Abort
|
|
||||||
: badBookRetryRb.Checked ? Configuration.BadBookAction.Retry
|
|
||||||
: badBookIgnoreRb.Checked ? Configuration.BadBookAction.Ignore
|
|
||||||
: Configuration.BadBookAction.Ask;
|
|
||||||
|
|
||||||
config.FolderTemplate = folderTemplateTb.Text;
|
|
||||||
config.FileTemplate = fileTemplateTb.Text;
|
|
||||||
config.ChapterFileTemplate = chapterFileTemplateTb.Text;
|
|
||||||
|
|
||||||
this.DialogResult = DialogResult.OK;
|
this.DialogResult = DialogResult.OK;
|
||||||
this.Close();
|
this.Close();
|
||||||
@ -235,6 +50,5 @@ namespace LibationWinForms.Dialogs
|
|||||||
this.DialogResult = DialogResult.Cancel;
|
this.DialogResult = DialogResult.Cancel;
|
||||||
this.Close();
|
this.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user