mirror of
https://github.com/rmcrackan/Libation.git
synced 2025-12-31 09:58:43 -05:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
283a46e1e2 | ||
|
|
6ff2859c39 | ||
|
|
e8df4952fc | ||
|
|
b19e1e8a30 | ||
|
|
a3cf6ac40d | ||
|
|
ab450c37c4 | ||
|
|
c837fefbdd | ||
|
|
46b120ee41 | ||
|
|
cae8ca7ef3 | ||
|
|
904665da7f | ||
|
|
2478c61df6 | ||
|
|
288ed75b5d | ||
|
|
ad5efbd9a9 | ||
|
|
7eb7b2a0f9 | ||
|
|
d0051c0f02 | ||
|
|
d20517063e | ||
|
|
bcca69a102 | ||
|
|
35f8c05106 | ||
|
|
a3d38e082d | ||
|
|
b2e956e70b | ||
|
|
e5119357b2 | ||
|
|
b42ff827d5 | ||
|
|
68da9779da | ||
|
|
8e358d8f04 |
@@ -5,8 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AAXClean" Version="0.1.9" />
|
||||
<PackageReference Include="Dinah.Core" Version="2.0.1.1" />
|
||||
<PackageReference Include="AAXClean" Version="0.1.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,23 +12,13 @@ namespace AaxDecrypter
|
||||
{
|
||||
protected override StepSequence Steps { get; }
|
||||
|
||||
private Func<string, int, int, NewSplitCallback, string> multipartFileNameCallback { get; }
|
||||
private static string DefaultMultipartFilename(string outputFileName, int partsPosition, int partsTotal, NewSplitCallback newSplitCallback)
|
||||
{
|
||||
var template = Path.ChangeExtension(outputFileName, null) + " - <chapter> - <title>" + Path.GetExtension(outputFileName);
|
||||
|
||||
var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " };
|
||||
fileTemplate.AddParameterReplacement("chapter", FileUtility.GetSequenceFormatted(partsPosition, partsTotal));
|
||||
fileTemplate.AddParameterReplacement("title", newSplitCallback?.Chapter?.Title ?? "");
|
||||
|
||||
return fileTemplate.GetFilePath();
|
||||
}
|
||||
private Func<MultiConvertFileProperties, string> multipartFileNameCallback { get; }
|
||||
|
||||
private static TimeSpan minChapterLength { get; } = TimeSpan.FromSeconds(3);
|
||||
private List<string> multiPartFilePaths { get; } = new List<string>();
|
||||
|
||||
public AaxcDownloadMultiConverter(string outFileName, string cacheDirectory, DownloadLicense dlLic, OutputFormat outputFormat,
|
||||
Func<string, int, int, NewSplitCallback, string> multipartFileNameCallback = null)
|
||||
Func<MultiConvertFileProperties, string> multipartFileNameCallback = null)
|
||||
: base(outFileName, cacheDirectory, dlLic, outputFormat)
|
||||
{
|
||||
Steps = new StepSequence
|
||||
@@ -39,7 +29,7 @@ namespace AaxDecrypter
|
||||
["Step 2: Download Decrypted Audiobook"] = Step_DownloadAudiobookAsMultipleFilesPerChapter,
|
||||
["Step 3: Cleanup"] = Step_Cleanup,
|
||||
};
|
||||
this.multipartFileNameCallback = multipartFileNameCallback ?? DefaultMultipartFilename;
|
||||
this.multipartFileNameCallback = multipartFileNameCallback ?? MultiConvertFileProperties.DefaultMultipartFilename;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -133,7 +123,13 @@ That naming may not be desirable for everyone, but it's an easy change to instea
|
||||
|
||||
private void createOutputFileStream(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)
|
||||
{
|
||||
var fileName = multipartFileNameCallback(OutputFileName, currentChapter, splitChapters.Count, newSplitCallback);
|
||||
var fileName = multipartFileNameCallback(new()
|
||||
{
|
||||
OutputFileName = OutputFileName,
|
||||
PartsPosition = currentChapter,
|
||||
PartsTotal = splitChapters.Count,
|
||||
Title = newSplitCallback?.Chapter?.Title
|
||||
});
|
||||
fileName = FileUtility.GetValidFilename(fileName);
|
||||
|
||||
multiPartFilePaths.Add(fileName);
|
||||
|
||||
25
AaxDecrypter/MultiConvertFileProperties.cs
Normal file
25
AaxDecrypter/MultiConvertFileProperties.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using FileManager;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
public class MultiConvertFileProperties
|
||||
{
|
||||
public string OutputFileName { get; set; }
|
||||
public int PartsPosition { get; set; }
|
||||
public int PartsTotal { get; set; }
|
||||
public string Title { get; set; }
|
||||
|
||||
public static string DefaultMultipartFilename(MultiConvertFileProperties multiConvertFileProperties)
|
||||
{
|
||||
var template = Path.ChangeExtension(multiConvertFileProperties.OutputFileName, null) + " - <ch# 0> - <title>" + Path.GetExtension(multiConvertFileProperties.OutputFileName);
|
||||
|
||||
var fileNamingTemplate = new FileNamingTemplate(template) { IllegalCharacterReplacements = " " };
|
||||
fileNamingTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(multiConvertFileProperties.PartsPosition, multiConvertFileProperties.PartsTotal));
|
||||
fileNamingTemplate.AddParameterReplacement("title", multiConvertFileProperties.Title ?? "");
|
||||
|
||||
return fileNamingTemplate.GetFilePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Version>6.2.8.1</Version>
|
||||
<Version>6.4.2.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -59,6 +59,7 @@ namespace AppScaffolding
|
||||
Migrations.migrate_to_v5_7_1(config);
|
||||
Migrations.migrate_to_v6_1_2(config);
|
||||
Migrations.migrate_to_v6_2_0(config);
|
||||
Migrations.migrate_to_v6_2_9(config);
|
||||
}
|
||||
|
||||
/// <summary>Initialize logging. Run after migration</summary>
|
||||
@@ -345,5 +346,18 @@ namespace AppScaffolding
|
||||
if (!config.Exists(nameof(config.SplitFilesByChapter)))
|
||||
config.SplitFilesByChapter = false;
|
||||
}
|
||||
|
||||
// add file naming templates
|
||||
public static void migrate_to_v6_2_9(Configuration config)
|
||||
{
|
||||
if (!config.Exists(nameof(config.FolderTemplate)))
|
||||
config.FolderTemplate = Templates.Folder.DefaultTemplate;
|
||||
|
||||
if (!config.Exists(nameof(config.FileTemplate)))
|
||||
config.FileTemplate = Templates.File.DefaultTemplate;
|
||||
|
||||
if (!config.Exists(nameof(config.ChapterFileTemplate)))
|
||||
config.ChapterFileTemplate = Templates.ChapterFile.DefaultTemplate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="27.1.1" />
|
||||
<PackageReference Include="NPOI" Version="2.5.4" />
|
||||
<PackageReference Include="NPOI" Version="2.5.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="2.3.1.1" />
|
||||
<PackageReference Include="AudibleApi" Version="2.3.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
1
AudibleUtilities/_InternalsVisible.cs
Normal file
1
AudibleUtilities/_InternalsVisible.cs
Normal file
@@ -0,0 +1 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(nameof(AudibleUtilities) + ".Tests")]
|
||||
@@ -185,6 +185,9 @@ namespace DataLayer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_seriesLink is null)
|
||||
return "";
|
||||
|
||||
// first: alphabetical by name
|
||||
var withNames = _seriesLink
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
||||
|
||||
@@ -11,59 +11,46 @@ namespace FileLiberator
|
||||
{
|
||||
public static class AudioFileStorageExt
|
||||
{
|
||||
public static string MultipartFilename(this AudioFileStorage _, string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback)
|
||||
=> MultipartFilename(outputFileName, partsPosition, partsTotal, newSplitCallback);
|
||||
public static string MultipartFilename(string outputFileName, int partsPosition, int partsTotal, AAXClean.NewSplitCallback newSplitCallback)
|
||||
private class MultipartRenamer
|
||||
{
|
||||
var template = Path.ChangeExtension(outputFileName, null) + " - <chapter> - <title>" + Path.GetExtension(outputFileName);
|
||||
private LibraryBook libraryBook { get; }
|
||||
|
||||
var fileTemplate = new FileTemplate(template) { IllegalCharacterReplacements = " " };
|
||||
fileTemplate.AddParameterReplacement("chapter", FileUtility.GetSequenceFormatted(partsPosition, partsTotal));
|
||||
fileTemplate.AddParameterReplacement("title", newSplitCallback?.Chapter?.Title ?? "");
|
||||
internal MultipartRenamer(LibraryBook libraryBook) => this.libraryBook = libraryBook;
|
||||
|
||||
return fileTemplate.GetFilePath();
|
||||
internal string MultipartFilename(AaxDecrypter.MultiConvertFileProperties props)
|
||||
=> Templates.ChapterFile.GetFilename(libraryBook.ToDto(), props);
|
||||
}
|
||||
|
||||
public static Func<AaxDecrypter.MultiConvertFileProperties, string> CreateMultipartRenamerFunc(this AudioFileStorage _, LibraryBook libraryBook)
|
||||
=> new MultipartRenamer(libraryBook).MultipartFilename;
|
||||
|
||||
/// <summary>
|
||||
/// DownloadDecryptBook:
|
||||
/// File path for where to move files into.
|
||||
/// Path: directory nested inside of Books directory
|
||||
/// File name: n/a
|
||||
/// </summary>
|
||||
public static string GetDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook)
|
||||
=> Templates.Folder.GetFilename(libraryBook.ToDto());
|
||||
|
||||
/// <summary>
|
||||
/// DownloadDecryptBook:
|
||||
/// Path: in progress directory.
|
||||
/// File name: final file name.
|
||||
/// </summary>
|
||||
public static string GetInProgressFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension)
|
||||
=> GetInProgressFilename(libraryBook, extension);
|
||||
public static string GetInProgressFilename(LibraryBook libraryBook, string extension)
|
||||
=> GetValidFilename(AudibleFileStorage.DecryptInProgressDirectory, libraryBook.Book.Title, extension, libraryBook);
|
||||
=> Templates.File.GetFilename(libraryBook.ToDto(), AudibleFileStorage.DecryptInProgressDirectory, extension);
|
||||
|
||||
/// <summary>
|
||||
/// PDF: audio file does not exist
|
||||
/// </summary>
|
||||
public static string GetBooksDirectoryFilename(this AudioFileStorage _, LibraryBook libraryBook, string extension)
|
||||
=> GetBooksDirectoryFilename(libraryBook, extension);
|
||||
public static string GetBooksDirectoryFilename(LibraryBook libraryBook, string extension)
|
||||
=> GetValidFilename(AudibleFileStorage.BooksDirectory, libraryBook.Book.Title, extension, libraryBook);
|
||||
=> Templates.File.GetFilename(libraryBook.ToDto(), AudibleFileStorage.BooksDirectory, extension);
|
||||
|
||||
public static string CreateDestinationDirectory(this AudioFileStorage _, LibraryBook libraryBook)
|
||||
=> CreateDestinationDirectory(libraryBook);
|
||||
public static string CreateDestinationDirectory(LibraryBook libraryBook)
|
||||
{
|
||||
var title = libraryBook.Book.Title;
|
||||
|
||||
// to prevent the paths from getting too long, we don't need after the 1st ":" for the folder
|
||||
var underscoreIndex = title.IndexOf(':');
|
||||
var titleDir
|
||||
= underscoreIndex < 4
|
||||
? title
|
||||
: title.Substring(0, underscoreIndex);
|
||||
var destinationDir = GetValidFilename(AudibleFileStorage.BooksDirectory, titleDir, null, libraryBook);
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
return destinationDir;
|
||||
}
|
||||
|
||||
public static string GetValidFilename(string dirFullPath, string filename, string extension, LibraryBook libraryBook)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(dirFullPath, nameof(dirFullPath));
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(filename, nameof(filename));
|
||||
|
||||
var template = $"<title> [<id>]";
|
||||
|
||||
var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension));
|
||||
|
||||
var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
|
||||
fileTemplate.AddParameterReplacement("title", filename);
|
||||
fileTemplate.AddParameterReplacement("id", libraryBook.Book.AudibleProductId);
|
||||
return fileTemplate.GetFilePath();
|
||||
}
|
||||
/// <summary>
|
||||
/// PDF: audio file already exists
|
||||
/// </summary>
|
||||
public static string GetCustomDirFilename(this AudioFileStorage _, LibraryBook libraryBook, string dirFullPath, string extension)
|
||||
=> Templates.File.GetFilename(libraryBook.ToDto(), dirFullPath, extension);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +121,10 @@ namespace FileLiberator
|
||||
|
||||
abDownloader
|
||||
= contentLic.DrmType != AudibleApi.Common.DrmType.Adrm ? new UnencryptedAudiobookDownloader(outFileName, cacheDir, audiobookDlLic)
|
||||
: Configuration.Instance.SplitFilesByChapter ? new AaxcDownloadMultiConverter(outFileName, cacheDir, audiobookDlLic, outputFormat, AudibleFileStorage.Audio.MultipartFilename)
|
||||
: Configuration.Instance.SplitFilesByChapter ? new AaxcDownloadMultiConverter(
|
||||
outFileName, cacheDir, audiobookDlLic, outputFormat,
|
||||
AudibleFileStorage.Audio.CreateMultipartRenamerFunc(libraryBook)
|
||||
)
|
||||
: new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic, outputFormat);
|
||||
abDownloader.DecryptProgressUpdate += (_, progress) => OnStreamingProgressChanged(progress);
|
||||
abDownloader.DecryptTimeRemaining += (_, remaining) => OnStreamingTimeRemaining(remaining);
|
||||
@@ -176,9 +179,10 @@ namespace FileLiberator
|
||||
private static bool moveFilesToBooksDir(LibraryBook libraryBook, List<FilePathCache.CacheEntry> entries)
|
||||
{
|
||||
// create final directory. move each file into it
|
||||
var destinationDir = AudibleFileStorage.Audio.CreateDestinationDirectory(libraryBook);
|
||||
var destinationDir = AudibleFileStorage.Audio.GetDestinationDirectory(libraryBook);
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio);
|
||||
FilePathCache.CacheEntry getFirstAudio() => entries.FirstOrDefault(f => f.FileType == FileType.Audio);
|
||||
|
||||
if (getFirstAudio() == default)
|
||||
return false;
|
||||
|
||||
@@ -40,14 +40,14 @@ namespace FileLiberator
|
||||
|
||||
private static string getProposedDownloadFilePath(LibraryBook libraryBook)
|
||||
{
|
||||
var extension = Path.GetExtension(getdownloadUrl(libraryBook));
|
||||
|
||||
// if audio file exists, get it's dir. else return base Book dir
|
||||
var existingPath = Path.GetDirectoryName(AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId));
|
||||
var file = getdownloadUrl(libraryBook);
|
||||
if (existingPath is not null)
|
||||
return AudibleFileStorage.Audio.GetCustomDirFilename(libraryBook, existingPath, extension);
|
||||
|
||||
if (existingPath != null)
|
||||
return Path.Combine(existingPath, Path.GetFileName(file));
|
||||
|
||||
return AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, Path.GetExtension(file));
|
||||
return AudibleFileStorage.Audio.GetBooksDirectoryFilename(libraryBook, extension);
|
||||
}
|
||||
|
||||
private static string getdownloadUrl(LibraryBook libraryBook)
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DataLayer;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace FileLiberator
|
||||
{
|
||||
@@ -22,5 +23,21 @@ namespace FileLiberator
|
||||
var apiExtended = await AudibleUtilities.ApiExtended.CreateAsync(libraryBook.Account, libraryBook.Book.Locale);
|
||||
return apiExtended.Api;
|
||||
}
|
||||
|
||||
public static LibraryBookDto ToDto(this LibraryBook libraryBook) => new()
|
||||
{
|
||||
Account = libraryBook.Account,
|
||||
|
||||
AudibleProductId = libraryBook.Book.AudibleProductId,
|
||||
Title = libraryBook.Book.Title ?? "",
|
||||
Locale = libraryBook.Book.Locale,
|
||||
|
||||
Authors = libraryBook.Book.Authors.Select(c => c.Name).ToList(),
|
||||
|
||||
Narrators = libraryBook.Book.Narrators.Select(c => c.Name).ToList(),
|
||||
|
||||
SeriesName = libraryBook.Book.SeriesLink.FirstOrDefault()?.Series.Name,
|
||||
SeriesNumber = libraryBook.Book.SeriesLink.FirstOrDefault()?.Order
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
1
FileLiberator/_InternalsVisible.cs
Normal file
1
FileLiberator/_InternalsVisible.cs
Normal file
@@ -0,0 +1 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(nameof(FileLiberator) + ".Tests")]
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="2.0.1.1" />
|
||||
<PackageReference Include="Dinah.Core" Version="3.0.1.1" />
|
||||
<PackageReference Include="Polly" Version="7.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dinah.Core;
|
||||
|
||||
namespace FileManager
|
||||
{
|
||||
/// <summary>Get valid filename. Advanced features incl. parameterized template</summary>
|
||||
public class FileTemplate
|
||||
public class FileNamingTemplate
|
||||
{
|
||||
/// <summary>Proposed full file path. 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 FileTemplate(string template) => Template = ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
||||
public FileNamingTemplate(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, string> ParameterReplacements { get; } = new Dictionary<string, string>();
|
||||
public Dictionary<string, object> ParameterReplacements { get; } = new Dictionary<string, object>();
|
||||
|
||||
/// <summary>Convenience method</summary>
|
||||
public void AddParameterReplacement(string key ,string value) => ParameterReplacements.Add(key, value);
|
||||
public void AddParameterReplacement(string key, object value)
|
||||
// using .Add() instead of "[key] = value" will make unintended overwriting throw exception
|
||||
=> ParameterReplacements.Add(key, value);
|
||||
|
||||
/// <summary>If set, truncate each parameter replacement to this many characters. Default 50</summary>
|
||||
public int? ParameterMaxSize { get; set; } = 50;
|
||||
@@ -38,14 +39,26 @@ namespace FileManager
|
||||
return FileUtility.GetValidFilename(filename, IllegalCharacterReplacements);
|
||||
}
|
||||
|
||||
private string formatKey(string key)
|
||||
private static string formatKey(string key)
|
||||
=> key
|
||||
.Replace("<", "")
|
||||
.Replace(">", "");
|
||||
|
||||
private string formatValue(string value)
|
||||
=> ParameterMaxSize.HasValue && ParameterMaxSize.Value > 0
|
||||
? value?.Truncate(ParameterMaxSize.Value)
|
||||
: value;
|
||||
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.
|
||||
var val = value
|
||||
.ToString()
|
||||
.Replace($"{System.IO.Path.DirectorySeparatorChar}", IllegalCharacterReplacements)
|
||||
.Replace($"{System.IO.Path.AltDirectorySeparatorChar}", IllegalCharacterReplacements);
|
||||
return
|
||||
ParameterMaxSize.HasValue && ParameterMaxSize.Value > 0
|
||||
? val.Truncate(ParameterMaxSize.Value)
|
||||
: val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ namespace FileManager
|
||||
public static string GetStandardizedExtension(string extension)
|
||||
=> string.IsNullOrWhiteSpace(extension)
|
||||
? (extension ?? "")?.Trim()
|
||||
: '.' + extension.Trim('.');
|
||||
: '.' + extension.Trim().Trim('.');
|
||||
|
||||
/// <summary>
|
||||
/// Return position with correct number of leading zeros.
|
||||
@@ -86,10 +86,15 @@ namespace FileManager
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
||||
|
||||
var invalidChars = Path.GetInvalidPathChars().Union(new[] {
|
||||
'*', '?',
|
||||
// these are weird. If you run Path.GetInvalidPathChars() in C# interactive, these characters are included.
|
||||
// In live code, Path.GetInvalidPathChars() does not include them
|
||||
'"', '<', '>'
|
||||
}).ToArray();
|
||||
|
||||
var fixedPath = string
|
||||
.Join(illegalCharacterReplacements ?? "", path.Split(Path.GetInvalidPathChars()))
|
||||
.Replace("*", illegalCharacterReplacements)
|
||||
.Replace("?", illegalCharacterReplacements)
|
||||
.Join(illegalCharacterReplacements ?? "", path.Split(invalidChars))
|
||||
.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
|
||||
// replace all colons except within the first 2 chars
|
||||
@@ -103,6 +108,11 @@ namespace FileManager
|
||||
builder.Append(c);
|
||||
}
|
||||
fixedPath = builder.ToString();
|
||||
|
||||
var dblSeparator = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}";
|
||||
while (fixedPath.Contains(dblSeparator))
|
||||
fixedPath = fixedPath.Replace(dblSeparator, $"{Path.DirectorySeparatorChar}");
|
||||
|
||||
return fixedPath;
|
||||
}
|
||||
|
||||
|
||||
@@ -205,8 +205,25 @@ namespace FileManager
|
||||
|
||||
private JObject readFile()
|
||||
{
|
||||
if (!File.Exists(Filepath))
|
||||
{
|
||||
var msg = "Unrecoverable error. Settings file cannot be found";
|
||||
var ex = new FileNotFoundException(msg, Filepath);
|
||||
Serilog.Log.Logger.Error(msg, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
var settingsJsonContents = File.ReadAllText(Filepath);
|
||||
var jObject = JsonConvert.DeserializeObject<JObject>(settingsJsonContents);
|
||||
|
||||
if (jObject is null)
|
||||
{
|
||||
var msg = "Unrecoverable error. Unable to read settings from Settings file";
|
||||
var ex = new NullReferenceException(msg);
|
||||
Serilog.Log.Logger.Error(msg, ex);
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return jObject;
|
||||
}
|
||||
}
|
||||
|
||||
1
FileManager/_InternalsVisible.cs
Normal file
1
FileManager/_InternalsVisible.cs
Normal file
@@ -0,0 +1 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(nameof(FileManager) + ".Tests")]
|
||||
@@ -62,6 +62,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileLiberator.Tests", "_Tes
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileManager.Tests", "_Tests\FileManager.Tests\FileManager.Tests.csproj", "{F2E04270-4551-41C4-99FF-E7125BED708C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibationFileManager.Tests", "_Tests\LibationFileManager.Tests\LibationFileManager.Tests.csproj", "{EB781571-8548-477E-82AD-FB9FAB548D2F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -136,6 +138,10 @@ Global
|
||||
{F2E04270-4551-41C4-99FF-E7125BED708C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F2E04270-4551-41C4-99FF-E7125BED708C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F2E04270-4551-41C4-99FF-E7125BED708C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EB781571-8548-477E-82AD-FB9FAB548D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EB781571-8548-477E-82AD-FB9FAB548D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EB781571-8548-477E-82AD-FB9FAB548D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EB781571-8548-477E-82AD-FB9FAB548D2F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -158,6 +164,7 @@ Global
|
||||
{788294BE-0D8E-40D4-9CEE-67896FBB52CE} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
{5B8FC827-BF58-4CB1-A59E-BDEB9C62A05E} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
{F2E04270-4551-41C4-99FF-E7125BED708C} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
{EB781571-8548-477E-82AD-FB9FAB548D2F} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9}
|
||||
|
||||
@@ -145,8 +145,40 @@ namespace LibationFileManager
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(DownloadEpisodes));
|
||||
set => persistentDictionary.SetNonString(nameof(DownloadEpisodes), value);
|
||||
}
|
||||
|
||||
#region templates: custom file naming
|
||||
|
||||
[Description("How to format the folders in which files will be saved")]
|
||||
public string FolderTemplate
|
||||
{
|
||||
get => getTemplate(nameof(FolderTemplate), Templates.Folder);
|
||||
set => setTemplate(nameof(FolderTemplate), Templates.Folder, value);
|
||||
}
|
||||
|
||||
[Description("How to format the saved pdf and audio files")]
|
||||
public string FileTemplate
|
||||
{
|
||||
get => getTemplate(nameof(FileTemplate), Templates.File);
|
||||
set => setTemplate(nameof(FileTemplate), Templates.File, value);
|
||||
}
|
||||
|
||||
[Description("How to format the saved audio files when split by chapters")]
|
||||
public string ChapterFileTemplate
|
||||
{
|
||||
get => getTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile);
|
||||
set => setTemplate(nameof(ChapterFileTemplate), Templates.ChapterFile, value);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
#endregion
|
||||
|
||||
#region known directories
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AaxDecrypter\AaxDecrypter.csproj" />
|
||||
<ProjectReference Include="..\FileManager\FileManager.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
29
LibationFileManager/LibraryBookDto.cs
Normal file
29
LibationFileManager/LibraryBookDto.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
public class BookDto
|
||||
{
|
||||
public string AudibleProductId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Locale { get; set; }
|
||||
|
||||
public IEnumerable<string> Authors { get; set; }
|
||||
public string AuthorNames => string.Join(", ", Authors);
|
||||
public string FirstAuthor => Authors.FirstOrDefault();
|
||||
|
||||
public IEnumerable<string> Narrators { get; set; }
|
||||
public string NarratorNames => string.Join(", ", Narrators);
|
||||
public string FirstNarrator => Narrators.FirstOrDefault();
|
||||
|
||||
public string SeriesName { get; set; }
|
||||
public string SeriesNumber { get; set; }
|
||||
}
|
||||
|
||||
public class LibraryBookDto : BookDto
|
||||
{
|
||||
public string Account { get; set; }
|
||||
}
|
||||
}
|
||||
40
LibationFileManager/TemplateTags.cs
Normal file
40
LibationFileManager/TemplateTags.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dinah.Core;
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
public sealed class TemplateTags : Enumeration<TemplateTags>
|
||||
{
|
||||
public string TagName => DisplayName;
|
||||
public string Description { get; }
|
||||
public bool IsChapterOnly { get; }
|
||||
|
||||
private static int value = 0;
|
||||
private TemplateTags(string tagName, string description, bool isChapterOnly = false) : base(value++, tagName)
|
||||
{
|
||||
Description = description;
|
||||
IsChapterOnly = isChapterOnly;
|
||||
}
|
||||
|
||||
// putting these first is the incredibly lazy way to make them show up first in the EditTemplateDialog
|
||||
public static TemplateTags ChCount { get; } = new TemplateTags("ch count", "Number of chapters", true);
|
||||
public static TemplateTags ChTitle { get; } = new TemplateTags("ch title", "Chapter title", true);
|
||||
public static TemplateTags ChNumber { get; } = new TemplateTags("ch#", "Chapter number", true);
|
||||
public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter number with leading zeros", true);
|
||||
|
||||
public static TemplateTags Id { get; } = new TemplateTags("id", "Audible ID");
|
||||
public static TemplateTags Title { get; } = new TemplateTags("title", "Full title");
|
||||
public static TemplateTags TitleShort { get; } = new TemplateTags("title short", "Title. Stop at first colon");
|
||||
public static TemplateTags Author { get; } = new TemplateTags("author", "Author(s)");
|
||||
public static TemplateTags FirstAuthor { get; } = new TemplateTags("first author", "First author");
|
||||
public static TemplateTags Narrator { get; } = new TemplateTags("narrator", "Narrator(s)");
|
||||
public static TemplateTags FirstNarrator { get; } = new TemplateTags("first narrator", "First narrator");
|
||||
public static TemplateTags Series { get; } = new TemplateTags("series", "Name of series");
|
||||
// can't also have a leading zeros version. Too many weird edge cases. Eg: "1-4"
|
||||
public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series");
|
||||
public static TemplateTags Account { get; } = new TemplateTags("account", "Audible account of this book");
|
||||
public static TemplateTags Locale { get; } = new TemplateTags("locale", "Region/country");
|
||||
}
|
||||
}
|
||||
274
LibationFileManager/Templates.cs
Normal file
274
LibationFileManager/Templates.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
using Dinah.Core;
|
||||
using FileManager;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
public abstract class Templates
|
||||
{
|
||||
protected static string[] Valid => Array.Empty<string>();
|
||||
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_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_WHITE_SPACE = "Template is white space.";
|
||||
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_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 FileTemplate File { get; } = new FileTemplate();
|
||||
public static ChapterFileTemplate ChapterFile { get; } = new ChapterFileTemplate();
|
||||
|
||||
public abstract string Name { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string DefaultTemplate { get; }
|
||||
protected abstract bool IsChapterized { get; }
|
||||
|
||||
protected Templates() { }
|
||||
|
||||
#region validation
|
||||
internal string GetValid(string configValue)
|
||||
{
|
||||
var value = configValue?.Trim();
|
||||
return IsValid(value) ? value : DefaultTemplate;
|
||||
}
|
||||
|
||||
public abstract IEnumerable<string> GetErrors(string template);
|
||||
public bool IsValid(string template) => !GetErrors(template).Any();
|
||||
|
||||
public abstract IEnumerable<string> GetWarnings(string template);
|
||||
public bool HasWarnings(string template) => GetWarnings(template).Any();
|
||||
|
||||
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.
|
||||
|
||||
// null is invalid. whitespace is valid but not recommended
|
||||
if (template is null)
|
||||
return new[] { ERROR_NULL_IS_INVALID };
|
||||
|
||||
if (template.Contains(':')
|
||||
|| template.Contains(Path.DirectorySeparatorChar)
|
||||
|| template.Contains(Path.AltDirectorySeparatorChar)
|
||||
)
|
||||
return new[] { ERROR_INVALID_FILE_NAME_CHAR };
|
||||
|
||||
return Valid;
|
||||
}
|
||||
|
||||
protected IEnumerable<string> GetStandardWarnings(string template)
|
||||
{
|
||||
var warnings = GetErrors(template).ToList();
|
||||
if (template is null)
|
||||
return warnings;
|
||||
|
||||
if (string.IsNullOrEmpty(template))
|
||||
warnings.Add(WARNING_EMPTY);
|
||||
else if (string.IsNullOrWhiteSpace(template))
|
||||
warnings.Add(WARNING_WHITE_SPACE);
|
||||
|
||||
if (TagCount(template) == 0)
|
||||
warnings.Add(WARNING_NO_TAGS);
|
||||
|
||||
if (!IsChapterized && ContainsChapterOnlyTags(template))
|
||||
warnings.Add(WARNING_HAS_CHAPTER_TAGS);
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
internal int TagCount(string template)
|
||||
=> GetTemplateTags()
|
||||
// for <id><id> == 1, use:
|
||||
// .Count(t => template.Contains($"<{t.TagName}>"))
|
||||
// .Sum() impl: <id><id> == 2
|
||||
.Sum(t => template.Split($"<{t.TagName}>").Length - 1);
|
||||
|
||||
internal static bool ContainsChapterOnlyTags(string template)
|
||||
=> TemplateTags.GetAll()
|
||||
.Where(t => t.IsChapterOnly)
|
||||
.Any(t => ContainsTag(template, t.TagName));
|
||||
|
||||
internal static bool ContainsTag(string template, string tag) => template.Contains($"<{tag}>");
|
||||
#endregion
|
||||
|
||||
#region to file name
|
||||
/// <summary>
|
||||
/// EditTemplateDialog: Get template generated filename for portion of path
|
||||
/// </summary>
|
||||
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template)
|
||||
=> string.IsNullOrWhiteSpace(template)
|
||||
? ""
|
||||
: getFileNamingTemplate(libraryBookDto, template, null, null)
|
||||
.GetFilePath();
|
||||
|
||||
internal static FileNamingTemplate getFileNamingTemplate(LibraryBookDto libraryBookDto, string template, string dirFullPath, string extension)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
|
||||
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
||||
|
||||
dirFullPath = dirFullPath?.Trim() ?? "";
|
||||
var t = template + FileUtility.GetStandardizedExtension(extension);
|
||||
var fullfilename = dirFullPath == "" ? t : Path.Combine(dirFullPath, t);
|
||||
|
||||
var fileNamingTemplate = new FileNamingTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
|
||||
|
||||
var title = libraryBookDto.Title ?? "";
|
||||
var titleShort = title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':'));
|
||||
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Id, libraryBookDto.AudibleProductId);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Title, title);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.TitleShort, titleShort);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Author, libraryBookDto.AuthorNames);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.FirstAuthor, libraryBookDto.FirstAuthor);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Narrator, libraryBookDto.NarratorNames);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.FirstNarrator, libraryBookDto.FirstNarrator);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Series, libraryBookDto.SeriesName);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.SeriesNumber, libraryBookDto.SeriesNumber);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Account, libraryBookDto.Account);
|
||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.Locale, libraryBookDto.Locale);
|
||||
|
||||
return fileNamingTemplate;
|
||||
}
|
||||
#endregion
|
||||
|
||||
public IEnumerable<TemplateTags> GetTemplateTags()
|
||||
=> TemplateTags.GetAll()
|
||||
// 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);
|
||||
|
||||
public string Sanitize(string template)
|
||||
{
|
||||
var value = template ?? "";
|
||||
|
||||
// don't use alt slash
|
||||
value = value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
|
||||
// don't allow double slashes
|
||||
var sing = $"{Path.DirectorySeparatorChar}";
|
||||
var dbl = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}";
|
||||
while (value.Contains(dbl))
|
||||
value = value.Replace(dbl, sing);
|
||||
|
||||
// trim. don't start or end with slash
|
||||
while (true)
|
||||
{
|
||||
var start = value.Length;
|
||||
value = value
|
||||
.Trim()
|
||||
.Trim(Path.DirectorySeparatorChar);
|
||||
var end = value.Length;
|
||||
if (start == end)
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public class FolderTemplate : Templates
|
||||
{
|
||||
public override string Name => "Folder Template";
|
||||
public override string Description => Configuration.GetDescription(nameof(Configuration.FolderTemplate));
|
||||
public override string DefaultTemplate { get; } = "<title short> [<id>]";
|
||||
protected override bool IsChapterized { get; } = false;
|
||||
|
||||
internal FolderTemplate() : base() { }
|
||||
|
||||
#region validation
|
||||
public override IEnumerable<string> GetErrors(string template)
|
||||
{
|
||||
// null is invalid. whitespace is valid but not recommended
|
||||
if (template is null)
|
||||
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.
|
||||
if (template.Contains(':'))
|
||||
return new[] { ERROR_FULL_PATH_IS_INVALID };
|
||||
|
||||
return Valid;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetWarnings(string template) => GetStandardWarnings(template);
|
||||
#endregion
|
||||
|
||||
#region to file name
|
||||
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
||||
public string GetFilename(LibraryBookDto libraryBookDto)
|
||||
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FolderTemplate, AudibleFileStorage.BooksDirectory, null)
|
||||
.GetFilePath();
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class FileTemplate : Templates
|
||||
{
|
||||
public override string Name => "File Template";
|
||||
public override string Description => Configuration.GetDescription(nameof(Configuration.FileTemplate));
|
||||
public override string DefaultTemplate { get; } = "<title> [<id>]";
|
||||
protected override bool IsChapterized { get; } = false;
|
||||
|
||||
internal FileTemplate() : base() { }
|
||||
|
||||
#region validation
|
||||
public override IEnumerable<string> GetErrors(string template) => GetFileErrors(template);
|
||||
|
||||
public override IEnumerable<string> GetWarnings(string template) => GetStandardWarnings(template);
|
||||
#endregion
|
||||
|
||||
#region to file name
|
||||
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
||||
public string GetFilename(LibraryBookDto libraryBookDto, string dirFullPath, string extension)
|
||||
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FileTemplate, dirFullPath, extension)
|
||||
.GetFilePath();
|
||||
#endregion
|
||||
}
|
||||
|
||||
public class ChapterFileTemplate : Templates
|
||||
{
|
||||
public override string Name => "Chapter File Template";
|
||||
public override string Description => Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate));
|
||||
public override string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
|
||||
protected override bool IsChapterized { get; } = true;
|
||||
|
||||
internal ChapterFileTemplate() : base() { }
|
||||
|
||||
#region validation
|
||||
public override IEnumerable<string> GetErrors(string template) => GetFileErrors(template);
|
||||
|
||||
public override IEnumerable<string> GetWarnings(string template)
|
||||
{
|
||||
var warnings = GetStandardWarnings(template).ToList();
|
||||
if (template is null)
|
||||
return warnings;
|
||||
|
||||
// recommended to incl. <ch#> or <ch# 0>
|
||||
if (!ContainsTag(template, TemplateTags.ChNumber.TagName) && !ContainsTag(template, TemplateTags.ChNumber0.TagName))
|
||||
warnings.Add(WARNING_NO_CHAPTER_NUMBER_TAG);
|
||||
|
||||
return warnings;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region to file name
|
||||
/// <summary>USES LIVE CONFIGURATION VALUES</summary>
|
||||
public string GetFilename(LibraryBookDto libraryBookDto, AaxDecrypter.MultiConvertFileProperties props)
|
||||
=> GetPortionFilename(libraryBookDto, Configuration.Instance.ChapterFileTemplate, props, AudibleFileStorage.DecryptInProgressDirectory);
|
||||
|
||||
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath)
|
||||
{
|
||||
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName));
|
||||
|
||||
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.GetFilePath();
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
13
LibationFileManager/UtilityExtensions.cs
Normal file
13
LibationFileManager/UtilityExtensions.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FileManager;
|
||||
|
||||
namespace LibationFileManager
|
||||
{
|
||||
public static class UtilityExtensions
|
||||
{
|
||||
public static void AddParameterReplacement(this FileNamingTemplate fileNamingTemplate, TemplateTags templateTags, object value)
|
||||
=> fileNamingTemplate.AddParameterReplacement(templateTags.TagName, value);
|
||||
}
|
||||
}
|
||||
1
LibationFileManager/_InternalsVisible.cs
Normal file
1
LibationFileManager/_InternalsVisible.cs
Normal file
@@ -0,0 +1 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(nameof(LibationFileManager) + ".Tests")]
|
||||
1
LibationSearchEngine/_InternalsVisible.cs
Normal file
1
LibationSearchEngine/_InternalsVisible.cs
Normal file
@@ -0,0 +1 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(nameof(LibationSearchEngine) + ".Tests")]
|
||||
@@ -103,6 +103,15 @@ namespace LibationWinForms.BookLiberation
|
||||
=> bookInfoLbl.UIThreadAsync(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}");
|
||||
|
||||
private void updateRemainingTime(int remaining)
|
||||
=> remainingTimeLbl.UIThreadAsync(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec");
|
||||
=> remainingTimeLbl.UIThreadAsync(() => remainingTimeLbl.Text = $"ETA:\r\n{formatTime(remaining)}");
|
||||
|
||||
private string formatTime(int seconds)
|
||||
{
|
||||
var timeSpan = new TimeSpan(0, 0, seconds);
|
||||
return
|
||||
timeSpan.TotalHours >= 1 ? $"{timeSpan:%h}h {timeSpan:mm}m {timeSpan:ss}s"
|
||||
: timeSpan.TotalMinutes >= 1 ? $"{timeSpan:%m}m {timeSpan:ss}s"
|
||||
: $"{seconds} sec";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
198
LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs
generated
Normal file
198
LibationWinForms/Dialogs/EditTemplateDialog.Designer.cs
generated
Normal file
@@ -0,0 +1,198 @@
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
partial class EditTemplateDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.templateTb = new System.Windows.Forms.TextBox();
|
||||
this.templateLbl = new System.Windows.Forms.Label();
|
||||
this.resetToDefaultBtn = new System.Windows.Forms.Button();
|
||||
this.listView1 = new System.Windows.Forms.ListView();
|
||||
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
|
||||
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
|
||||
this.richTextBox1 = new System.Windows.Forms.RichTextBox();
|
||||
this.warningsLbl = new System.Windows.Forms.Label();
|
||||
this.exampleLbl = new System.Windows.Forms.Label();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.saveBtn.Location = new System.Drawing.Point(714, 345);
|
||||
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.saveBtn.Name = "saveBtn";
|
||||
this.saveBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.saveBtn.TabIndex = 98;
|
||||
this.saveBtn.Text = "Save";
|
||||
this.saveBtn.UseVisualStyleBackColor = true;
|
||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||
//
|
||||
// cancelBtn
|
||||
//
|
||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||
this.cancelBtn.Location = new System.Drawing.Point(832, 345);
|
||||
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.cancelBtn.Name = "cancelBtn";
|
||||
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
|
||||
this.cancelBtn.TabIndex = 99;
|
||||
this.cancelBtn.Text = "Cancel";
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// templateTb
|
||||
//
|
||||
this.templateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.templateTb.Location = new System.Drawing.Point(12, 27);
|
||||
this.templateTb.Name = "templateTb";
|
||||
this.templateTb.Size = new System.Drawing.Size(779, 23);
|
||||
this.templateTb.TabIndex = 1;
|
||||
this.templateTb.TextChanged += new System.EventHandler(this.templateTb_TextChanged);
|
||||
//
|
||||
// templateLbl
|
||||
//
|
||||
this.templateLbl.AutoSize = true;
|
||||
this.templateLbl.Location = new System.Drawing.Point(12, 9);
|
||||
this.templateLbl.Name = "templateLbl";
|
||||
this.templateLbl.Size = new System.Drawing.Size(89, 15);
|
||||
this.templateLbl.TabIndex = 0;
|
||||
this.templateLbl.Text = "[template desc]";
|
||||
//
|
||||
// resetToDefaultBtn
|
||||
//
|
||||
this.resetToDefaultBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.resetToDefaultBtn.Location = new System.Drawing.Point(797, 26);
|
||||
this.resetToDefaultBtn.Name = "resetToDefaultBtn";
|
||||
this.resetToDefaultBtn.Size = new System.Drawing.Size(124, 23);
|
||||
this.resetToDefaultBtn.TabIndex = 2;
|
||||
this.resetToDefaultBtn.Text = "Reset to default";
|
||||
this.resetToDefaultBtn.UseVisualStyleBackColor = true;
|
||||
this.resetToDefaultBtn.Click += new System.EventHandler(this.resetToDefaultBtn_Click);
|
||||
//
|
||||
// listView1
|
||||
//
|
||||
this.listView1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
||||
this.columnHeader1,
|
||||
this.columnHeader2});
|
||||
this.listView1.HideSelection = false;
|
||||
this.listView1.Location = new System.Drawing.Point(12, 56);
|
||||
this.listView1.Name = "listView1";
|
||||
this.listView1.Size = new System.Drawing.Size(328, 283);
|
||||
this.listView1.TabIndex = 3;
|
||||
this.listView1.UseCompatibleStateImageBehavior = false;
|
||||
this.listView1.View = System.Windows.Forms.View.Details;
|
||||
//
|
||||
// columnHeader1
|
||||
//
|
||||
this.columnHeader1.Text = "Tag";
|
||||
this.columnHeader1.Width = 90;
|
||||
//
|
||||
// columnHeader2
|
||||
//
|
||||
this.columnHeader2.Text = "Description";
|
||||
this.columnHeader2.Width = 230;
|
||||
//
|
||||
// richTextBox1
|
||||
//
|
||||
this.richTextBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.richTextBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||
this.richTextBox1.Location = new System.Drawing.Point(346, 74);
|
||||
this.richTextBox1.Name = "richTextBox1";
|
||||
this.richTextBox1.ReadOnly = true;
|
||||
this.richTextBox1.Size = new System.Drawing.Size(574, 185);
|
||||
this.richTextBox1.TabIndex = 5;
|
||||
this.richTextBox1.Text = "";
|
||||
//
|
||||
// warningsLbl
|
||||
//
|
||||
this.warningsLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.warningsLbl.AutoSize = true;
|
||||
this.warningsLbl.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
|
||||
this.warningsLbl.ForeColor = System.Drawing.Color.Firebrick;
|
||||
this.warningsLbl.Location = new System.Drawing.Point(346, 262);
|
||||
this.warningsLbl.Name = "warningsLbl";
|
||||
this.warningsLbl.Size = new System.Drawing.Size(14, 15);
|
||||
this.warningsLbl.TabIndex = 100;
|
||||
this.warningsLbl.Text = "6";
|
||||
//
|
||||
// exampleLbl
|
||||
//
|
||||
this.exampleLbl.AutoSize = true;
|
||||
this.exampleLbl.Location = new System.Drawing.Point(346, 56);
|
||||
this.exampleLbl.Name = "exampleLbl";
|
||||
this.exampleLbl.Size = new System.Drawing.Size(55, 15);
|
||||
this.exampleLbl.TabIndex = 4;
|
||||
this.exampleLbl.Text = "Example:";
|
||||
//
|
||||
// EditTemplateDialog
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(933, 388);
|
||||
this.Controls.Add(this.exampleLbl);
|
||||
this.Controls.Add(this.warningsLbl);
|
||||
this.Controls.Add(this.richTextBox1);
|
||||
this.Controls.Add(this.listView1);
|
||||
this.Controls.Add(this.resetToDefaultBtn);
|
||||
this.Controls.Add(this.templateLbl);
|
||||
this.Controls.Add(this.templateTb);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.Name = "EditTemplateDialog";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Edit Template";
|
||||
this.Load += new System.EventHandler(this.EditTemplateDialog_Load);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.Button saveBtn;
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.TextBox templateTb;
|
||||
private System.Windows.Forms.Label templateLbl;
|
||||
private System.Windows.Forms.Button resetToDefaultBtn;
|
||||
private System.Windows.Forms.ListView listView1;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader1;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader2;
|
||||
private System.Windows.Forms.RichTextBox richTextBox1;
|
||||
private System.Windows.Forms.Label warningsLbl;
|
||||
private System.Windows.Forms.Label exampleLbl;
|
||||
}
|
||||
}
|
||||
173
LibationWinForms/Dialogs/EditTemplateDialog.cs
Normal file
173
LibationWinForms/Dialogs/EditTemplateDialog.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core;
|
||||
using LibationFileManager;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class EditTemplateDialog : Form
|
||||
{
|
||||
// final value. post-validity check
|
||||
public string TemplateText { get; private set; }
|
||||
|
||||
// hold the work-in-progress value. not guaranteed to be valid
|
||||
private string _workingTemplateText;
|
||||
private string workingTemplateText
|
||||
{
|
||||
get => _workingTemplateText;
|
||||
set => _workingTemplateText = template.Sanitize(value);
|
||||
}
|
||||
|
||||
private void resetTextBox(string value) => this.templateTb.Text = workingTemplateText = value;
|
||||
|
||||
private Configuration config { get; } = Configuration.Instance;
|
||||
|
||||
private Templates template { get; }
|
||||
private string inputTemplateText { get; }
|
||||
|
||||
public EditTemplateDialog() => InitializeComponent();
|
||||
public EditTemplateDialog(Templates template, string inputTemplateText) : this()
|
||||
{
|
||||
this.template = ArgumentValidator.EnsureNotNull(template, nameof(template));
|
||||
this.inputTemplateText = inputTemplateText ?? "";
|
||||
}
|
||||
|
||||
private void EditTemplateDialog_Load(object sender, EventArgs e)
|
||||
{
|
||||
if (this.DesignMode)
|
||||
return;
|
||||
|
||||
if (template is null)
|
||||
{
|
||||
MessageBoxAlertAdmin.Show($"Programming error. {nameof(EditTemplateDialog)} was not created correctly", "Edit template error", new NullReferenceException($"{nameof(template)} is null"));
|
||||
return;
|
||||
}
|
||||
|
||||
warningsLbl.Text = "";
|
||||
|
||||
this.Text = $"Edit {template.Name}";
|
||||
|
||||
this.templateLbl.Text = template.Description;
|
||||
resetTextBox(inputTemplateText);
|
||||
|
||||
// populate list view
|
||||
foreach (var tag in template.GetTemplateTags())
|
||||
listView1.Items.Add(new ListViewItem(new[] { $"<{tag.TagName}>", tag.Description }));
|
||||
}
|
||||
|
||||
private void resetToDefaultBtn_Click(object sender, EventArgs e) => resetTextBox(template.DefaultTemplate);
|
||||
|
||||
private void templateTb_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
workingTemplateText = templateTb.Text;
|
||||
|
||||
var isFolder = template == Templates.Folder;
|
||||
|
||||
var libraryBookDto = new LibraryBookDto
|
||||
{
|
||||
Account = "my account",
|
||||
AudibleProductId = "123456789",
|
||||
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
|
||||
Locale = "us",
|
||||
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
|
||||
Narrators = new List<string> { "Stephen Fry" },
|
||||
SeriesName = "Sherlock Holmes",
|
||||
SeriesNumber = "1"
|
||||
};
|
||||
var chapterName = "A Flight for Life";
|
||||
var chapterNumber = 4;
|
||||
var chaptersTotal = 10;
|
||||
|
||||
var books = config.Books;
|
||||
var folder = Templates.Folder.GetPortionFilename(
|
||||
libraryBookDto,
|
||||
isFolder ? workingTemplateText : config.FolderTemplate);
|
||||
var file
|
||||
= template == Templates.ChapterFile
|
||||
? Templates.ChapterFile.GetPortionFilename(
|
||||
libraryBookDto,
|
||||
workingTemplateText,
|
||||
new() { OutputFileName = "", PartsPosition = chapterNumber, PartsTotal = chaptersTotal, Title = chapterName },
|
||||
"")
|
||||
: Templates.File.GetPortionFilename(
|
||||
libraryBookDto,
|
||||
isFolder ? config.FileTemplate : workingTemplateText);
|
||||
var ext = config.DecryptToLossy ? "mp3" : "m4b";
|
||||
|
||||
const char ZERO_WIDTH_SPACE = '\u200B';
|
||||
var sing = $"{Path.DirectorySeparatorChar}";
|
||||
|
||||
// result: can wrap long paths. eg:
|
||||
// |-- LINE WRAP BOUNDARIES --|
|
||||
// \books\author with a very <= normal line break on space between words
|
||||
// long name\narrator narrator
|
||||
// \title <= line break on the zero-with space we added before slashes
|
||||
string slashWrap(string val) => val.Replace(sing, $"{ZERO_WIDTH_SPACE}{sing}");
|
||||
|
||||
warningsLbl.Text
|
||||
= !template.HasWarnings(workingTemplateText)
|
||||
? ""
|
||||
: "Warning:\r\n" +
|
||||
template
|
||||
.GetWarnings(workingTemplateText)
|
||||
.Select(err => $"- {err}")
|
||||
.Aggregate((a, b) => $"{a}\r\n{b}");
|
||||
|
||||
var bold = new System.Drawing.Font(richTextBox1.Font, System.Drawing.FontStyle.Bold);
|
||||
var reg = new System.Drawing.Font(richTextBox1.Font, System.Drawing.FontStyle.Regular);
|
||||
|
||||
richTextBox1.Clear();
|
||||
richTextBox1.SelectionFont = reg;
|
||||
|
||||
richTextBox1.AppendText(slashWrap(books));
|
||||
richTextBox1.AppendText(sing);
|
||||
|
||||
if (isFolder)
|
||||
richTextBox1.SelectionFont = bold;
|
||||
|
||||
richTextBox1.AppendText(slashWrap(folder));
|
||||
|
||||
if (isFolder)
|
||||
richTextBox1.SelectionFont = reg;
|
||||
|
||||
richTextBox1.AppendText(sing);
|
||||
|
||||
if (!isFolder)
|
||||
richTextBox1.SelectionFont = bold;
|
||||
|
||||
richTextBox1.AppendText(file);
|
||||
|
||||
if (!isFolder)
|
||||
richTextBox1.SelectionFont = reg;
|
||||
|
||||
richTextBox1.AppendText($".{ext}");
|
||||
}
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (!template.IsValid(workingTemplateText))
|
||||
{
|
||||
var errors = template
|
||||
.GetErrors(workingTemplateText)
|
||||
.Select(err => $"- {err}")
|
||||
.Aggregate((a, b) => $"{a}\r\n{b}");
|
||||
MessageBox.Show($"This template text is not valid. Errors:\r\n{errors}", "Invalid", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
TemplateText = workingTemplateText;
|
||||
|
||||
this.DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void cancelBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
this.DialogResult = DialogResult.Cancel;
|
||||
this.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
60
LibationWinForms/Dialogs/EditTemplateDialog.resx
Normal file
60
LibationWinForms/Dialogs/EditTemplateDialog.resx
Normal file
@@ -0,0 +1,60 @@
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
315
LibationWinForms/Dialogs/SettingsDialog.Designer.cs
generated
315
LibationWinForms/Dialogs/SettingsDialog.Designer.cs
generated
@@ -32,7 +32,6 @@
|
||||
this.inProgressDescLbl = new System.Windows.Forms.Label();
|
||||
this.saveBtn = new System.Windows.Forms.Button();
|
||||
this.cancelBtn = new System.Windows.Forms.Button();
|
||||
this.advancedSettingsGb = new System.Windows.Forms.GroupBox();
|
||||
this.importEpisodesCb = new System.Windows.Forms.CheckBox();
|
||||
this.downloadEpisodesCb = new System.Windows.Forms.CheckBox();
|
||||
this.badBookGb = new System.Windows.Forms.GroupBox();
|
||||
@@ -48,13 +47,33 @@
|
||||
this.inProgressSelectControl = new LibationWinForms.Dialogs.DirectorySelectControl();
|
||||
this.logsBtn = new System.Windows.Forms.Button();
|
||||
this.booksSelectControl = new LibationWinForms.Dialogs.DirectoryOrCustomSelectControl();
|
||||
this.booksGb = new System.Windows.Forms.GroupBox();
|
||||
this.loggingLevelLbl = new System.Windows.Forms.Label();
|
||||
this.loggingLevelCb = new System.Windows.Forms.ComboBox();
|
||||
this.advancedSettingsGb.SuspendLayout();
|
||||
this.tabControl = new System.Windows.Forms.TabControl();
|
||||
this.tab1ImportantSettings = new System.Windows.Forms.TabPage();
|
||||
this.booksGb = new System.Windows.Forms.GroupBox();
|
||||
this.tab2ImportLibrary = new System.Windows.Forms.TabPage();
|
||||
this.tab3DownloadDecrypt = new System.Windows.Forms.TabPage();
|
||||
this.inProgressFilesGb = new System.Windows.Forms.GroupBox();
|
||||
this.customFileNamingGb = new System.Windows.Forms.GroupBox();
|
||||
this.chapterFileTemplateBtn = new System.Windows.Forms.Button();
|
||||
this.chapterFileTemplateTb = new System.Windows.Forms.TextBox();
|
||||
this.chapterFileTemplateLbl = new System.Windows.Forms.Label();
|
||||
this.fileTemplateBtn = new System.Windows.Forms.Button();
|
||||
this.fileTemplateTb = new System.Windows.Forms.TextBox();
|
||||
this.fileTemplateLbl = new System.Windows.Forms.Label();
|
||||
this.folderTemplateBtn = new System.Windows.Forms.Button();
|
||||
this.folderTemplateTb = new System.Windows.Forms.TextBox();
|
||||
this.folderTemplateLbl = new System.Windows.Forms.Label();
|
||||
this.badBookGb.SuspendLayout();
|
||||
this.decryptAndConvertGb.SuspendLayout();
|
||||
this.tabControl.SuspendLayout();
|
||||
this.tab1ImportantSettings.SuspendLayout();
|
||||
this.booksGb.SuspendLayout();
|
||||
this.tab2ImportLibrary.SuspendLayout();
|
||||
this.tab3DownloadDecrypt.SuspendLayout();
|
||||
this.inProgressFilesGb.SuspendLayout();
|
||||
this.customFileNamingGb.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// booksLocationDescLbl
|
||||
@@ -70,12 +89,12 @@
|
||||
// inProgressDescLbl
|
||||
//
|
||||
this.inProgressDescLbl.AutoSize = true;
|
||||
this.inProgressDescLbl.Location = new System.Drawing.Point(8, 199);
|
||||
this.inProgressDescLbl.Location = new System.Drawing.Point(7, 19);
|
||||
this.inProgressDescLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
|
||||
this.inProgressDescLbl.Name = "inProgressDescLbl";
|
||||
this.inProgressDescLbl.Size = new System.Drawing.Size(43, 45);
|
||||
this.inProgressDescLbl.Size = new System.Drawing.Size(100, 45);
|
||||
this.inProgressDescLbl.TabIndex = 18;
|
||||
this.inProgressDescLbl.Text = "[desc]\r\n[line 2]\r\n[line 3]";
|
||||
this.inProgressDescLbl.Text = "[in progress desc]\r\n[line 2]\r\n[line 3]";
|
||||
//
|
||||
// saveBtn
|
||||
//
|
||||
@@ -102,30 +121,10 @@
|
||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||
//
|
||||
// advancedSettingsGb
|
||||
//
|
||||
this.advancedSettingsGb.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.advancedSettingsGb.Controls.Add(this.importEpisodesCb);
|
||||
this.advancedSettingsGb.Controls.Add(this.downloadEpisodesCb);
|
||||
this.advancedSettingsGb.Controls.Add(this.badBookGb);
|
||||
this.advancedSettingsGb.Controls.Add(this.decryptAndConvertGb);
|
||||
this.advancedSettingsGb.Controls.Add(this.inProgressSelectControl);
|
||||
this.advancedSettingsGb.Controls.Add(this.inProgressDescLbl);
|
||||
this.advancedSettingsGb.Location = new System.Drawing.Point(12, 176);
|
||||
this.advancedSettingsGb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.advancedSettingsGb.Name = "advancedSettingsGb";
|
||||
this.advancedSettingsGb.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.advancedSettingsGb.Size = new System.Drawing.Size(908, 309);
|
||||
this.advancedSettingsGb.TabIndex = 6;
|
||||
this.advancedSettingsGb.TabStop = false;
|
||||
this.advancedSettingsGb.Text = "Advanced settings for control freaks";
|
||||
//
|
||||
// importEpisodesCb
|
||||
//
|
||||
this.importEpisodesCb.AutoSize = true;
|
||||
this.importEpisodesCb.Location = new System.Drawing.Point(7, 22);
|
||||
this.importEpisodesCb.Location = new System.Drawing.Point(6, 6);
|
||||
this.importEpisodesCb.Name = "importEpisodesCb";
|
||||
this.importEpisodesCb.Size = new System.Drawing.Size(146, 19);
|
||||
this.importEpisodesCb.TabIndex = 7;
|
||||
@@ -135,7 +134,7 @@
|
||||
// downloadEpisodesCb
|
||||
//
|
||||
this.downloadEpisodesCb.AutoSize = true;
|
||||
this.downloadEpisodesCb.Location = new System.Drawing.Point(7, 47);
|
||||
this.downloadEpisodesCb.Location = new System.Drawing.Point(6, 31);
|
||||
this.downloadEpisodesCb.Name = "downloadEpisodesCb";
|
||||
this.downloadEpisodesCb.Size = new System.Drawing.Size(163, 19);
|
||||
this.downloadEpisodesCb.TabIndex = 8;
|
||||
@@ -148,9 +147,9 @@
|
||||
this.badBookGb.Controls.Add(this.badBookRetryRb);
|
||||
this.badBookGb.Controls.Add(this.badBookAbortRb);
|
||||
this.badBookGb.Controls.Add(this.badBookAskRb);
|
||||
this.badBookGb.Location = new System.Drawing.Point(372, 72);
|
||||
this.badBookGb.Location = new System.Drawing.Point(371, 6);
|
||||
this.badBookGb.Name = "badBookGb";
|
||||
this.badBookGb.Size = new System.Drawing.Size(529, 124);
|
||||
this.badBookGb.Size = new System.Drawing.Size(524, 124);
|
||||
this.badBookGb.TabIndex = 13;
|
||||
this.badBookGb.TabStop = false;
|
||||
this.badBookGb.Text = "[bad book desc]";
|
||||
@@ -205,7 +204,7 @@
|
||||
this.decryptAndConvertGb.Controls.Add(this.allowLibationFixupCbox);
|
||||
this.decryptAndConvertGb.Controls.Add(this.convertLossyRb);
|
||||
this.decryptAndConvertGb.Controls.Add(this.convertLosslessRb);
|
||||
this.decryptAndConvertGb.Location = new System.Drawing.Point(7, 72);
|
||||
this.decryptAndConvertGb.Location = new System.Drawing.Point(6, 6);
|
||||
this.decryptAndConvertGb.Name = "decryptAndConvertGb";
|
||||
this.decryptAndConvertGb.Size = new System.Drawing.Size(359, 124);
|
||||
this.decryptAndConvertGb.TabIndex = 9;
|
||||
@@ -261,15 +260,15 @@
|
||||
//
|
||||
this.inProgressSelectControl.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.inProgressSelectControl.Location = new System.Drawing.Point(7, 247);
|
||||
this.inProgressSelectControl.Location = new System.Drawing.Point(7, 68);
|
||||
this.inProgressSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.inProgressSelectControl.Name = "inProgressSelectControl";
|
||||
this.inProgressSelectControl.Size = new System.Drawing.Size(894, 52);
|
||||
this.inProgressSelectControl.Size = new System.Drawing.Size(875, 52);
|
||||
this.inProgressSelectControl.TabIndex = 19;
|
||||
//
|
||||
// logsBtn
|
||||
//
|
||||
this.logsBtn.Location = new System.Drawing.Point(262, 147);
|
||||
this.logsBtn.Location = new System.Drawing.Point(256, 169);
|
||||
this.logsBtn.Name = "logsBtn";
|
||||
this.logsBtn.Size = new System.Drawing.Size(132, 23);
|
||||
this.logsBtn.TabIndex = 5;
|
||||
@@ -284,26 +283,13 @@
|
||||
this.booksSelectControl.Location = new System.Drawing.Point(7, 37);
|
||||
this.booksSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.booksSelectControl.Name = "booksSelectControl";
|
||||
this.booksSelectControl.Size = new System.Drawing.Size(895, 87);
|
||||
this.booksSelectControl.Size = new System.Drawing.Size(876, 87);
|
||||
this.booksSelectControl.TabIndex = 2;
|
||||
//
|
||||
// booksGb
|
||||
//
|
||||
this.booksGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.booksGb.Controls.Add(this.booksSelectControl);
|
||||
this.booksGb.Controls.Add(this.booksLocationDescLbl);
|
||||
this.booksGb.Location = new System.Drawing.Point(12, 12);
|
||||
this.booksGb.Name = "booksGb";
|
||||
this.booksGb.Size = new System.Drawing.Size(908, 129);
|
||||
this.booksGb.TabIndex = 0;
|
||||
this.booksGb.TabStop = false;
|
||||
this.booksGb.Text = "Books location";
|
||||
//
|
||||
// loggingLevelLbl
|
||||
//
|
||||
this.loggingLevelLbl.AutoSize = true;
|
||||
this.loggingLevelLbl.Location = new System.Drawing.Point(12, 150);
|
||||
this.loggingLevelLbl.Location = new System.Drawing.Point(6, 172);
|
||||
this.loggingLevelLbl.Name = "loggingLevelLbl";
|
||||
this.loggingLevelLbl.Size = new System.Drawing.Size(78, 15);
|
||||
this.loggingLevelLbl.TabIndex = 3;
|
||||
@@ -313,11 +299,201 @@
|
||||
//
|
||||
this.loggingLevelCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.loggingLevelCb.FormattingEnabled = true;
|
||||
this.loggingLevelCb.Location = new System.Drawing.Point(96, 147);
|
||||
this.loggingLevelCb.Location = new System.Drawing.Point(90, 169);
|
||||
this.loggingLevelCb.Name = "loggingLevelCb";
|
||||
this.loggingLevelCb.Size = new System.Drawing.Size(129, 23);
|
||||
this.loggingLevelCb.TabIndex = 4;
|
||||
//
|
||||
// tabControl
|
||||
//
|
||||
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.Right)));
|
||||
this.tabControl.Controls.Add(this.tab1ImportantSettings);
|
||||
this.tabControl.Controls.Add(this.tab2ImportLibrary);
|
||||
this.tabControl.Controls.Add(this.tab3DownloadDecrypt);
|
||||
this.tabControl.Location = new System.Drawing.Point(12, 12);
|
||||
this.tabControl.Name = "tabControl";
|
||||
this.tabControl.SelectedIndex = 0;
|
||||
this.tabControl.Size = new System.Drawing.Size(909, 478);
|
||||
this.tabControl.TabIndex = 100;
|
||||
//
|
||||
// tab1ImportantSettings
|
||||
//
|
||||
this.tab1ImportantSettings.Controls.Add(this.booksGb);
|
||||
this.tab1ImportantSettings.Controls.Add(this.logsBtn);
|
||||
this.tab1ImportantSettings.Controls.Add(this.loggingLevelCb);
|
||||
this.tab1ImportantSettings.Controls.Add(this.loggingLevelLbl);
|
||||
this.tab1ImportantSettings.Location = new System.Drawing.Point(4, 24);
|
||||
this.tab1ImportantSettings.Name = "tab1ImportantSettings";
|
||||
this.tab1ImportantSettings.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tab1ImportantSettings.Size = new System.Drawing.Size(901, 450);
|
||||
this.tab1ImportantSettings.TabIndex = 0;
|
||||
this.tab1ImportantSettings.Text = "Important settings";
|
||||
this.tab1ImportantSettings.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// booksGb
|
||||
//
|
||||
this.booksGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.booksGb.Controls.Add(this.booksSelectControl);
|
||||
this.booksGb.Controls.Add(this.booksLocationDescLbl);
|
||||
this.booksGb.Location = new System.Drawing.Point(6, 6);
|
||||
this.booksGb.Name = "booksGb";
|
||||
this.booksGb.Size = new System.Drawing.Size(889, 129);
|
||||
this.booksGb.TabIndex = 0;
|
||||
this.booksGb.TabStop = false;
|
||||
this.booksGb.Text = "Books location";
|
||||
//
|
||||
// tab2ImportLibrary
|
||||
//
|
||||
this.tab2ImportLibrary.Controls.Add(this.importEpisodesCb);
|
||||
this.tab2ImportLibrary.Controls.Add(this.downloadEpisodesCb);
|
||||
this.tab2ImportLibrary.Location = new System.Drawing.Point(4, 24);
|
||||
this.tab2ImportLibrary.Name = "tab2ImportLibrary";
|
||||
this.tab2ImportLibrary.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tab2ImportLibrary.Size = new System.Drawing.Size(901, 450);
|
||||
this.tab2ImportLibrary.TabIndex = 1;
|
||||
this.tab2ImportLibrary.Text = "Import library";
|
||||
this.tab2ImportLibrary.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// tab3DownloadDecrypt
|
||||
//
|
||||
this.tab3DownloadDecrypt.Controls.Add(this.inProgressFilesGb);
|
||||
this.tab3DownloadDecrypt.Controls.Add(this.customFileNamingGb);
|
||||
this.tab3DownloadDecrypt.Controls.Add(this.decryptAndConvertGb);
|
||||
this.tab3DownloadDecrypt.Controls.Add(this.badBookGb);
|
||||
this.tab3DownloadDecrypt.Location = new System.Drawing.Point(4, 24);
|
||||
this.tab3DownloadDecrypt.Name = "tab3DownloadDecrypt";
|
||||
this.tab3DownloadDecrypt.Padding = new System.Windows.Forms.Padding(3);
|
||||
this.tab3DownloadDecrypt.Size = new System.Drawing.Size(901, 450);
|
||||
this.tab3DownloadDecrypt.TabIndex = 2;
|
||||
this.tab3DownloadDecrypt.Text = "Download/Decrypt";
|
||||
this.tab3DownloadDecrypt.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// inProgressFilesGb
|
||||
//
|
||||
this.inProgressFilesGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.inProgressFilesGb.Controls.Add(this.inProgressDescLbl);
|
||||
this.inProgressFilesGb.Controls.Add(this.inProgressSelectControl);
|
||||
this.inProgressFilesGb.Location = new System.Drawing.Point(7, 299);
|
||||
this.inProgressFilesGb.Name = "inProgressFilesGb";
|
||||
this.inProgressFilesGb.Size = new System.Drawing.Size(888, 128);
|
||||
this.inProgressFilesGb.TabIndex = 21;
|
||||
this.inProgressFilesGb.TabStop = false;
|
||||
this.inProgressFilesGb.Text = "In progress files";
|
||||
//
|
||||
// customFileNamingGb
|
||||
//
|
||||
this.customFileNamingGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateBtn);
|
||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateTb);
|
||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateLbl);
|
||||
this.customFileNamingGb.Controls.Add(this.fileTemplateBtn);
|
||||
this.customFileNamingGb.Controls.Add(this.fileTemplateTb);
|
||||
this.customFileNamingGb.Controls.Add(this.fileTemplateLbl);
|
||||
this.customFileNamingGb.Controls.Add(this.folderTemplateBtn);
|
||||
this.customFileNamingGb.Controls.Add(this.folderTemplateTb);
|
||||
this.customFileNamingGb.Controls.Add(this.folderTemplateLbl);
|
||||
this.customFileNamingGb.Location = new System.Drawing.Point(7, 136);
|
||||
this.customFileNamingGb.Name = "customFileNamingGb";
|
||||
this.customFileNamingGb.Size = new System.Drawing.Size(888, 157);
|
||||
this.customFileNamingGb.TabIndex = 20;
|
||||
this.customFileNamingGb.TabStop = false;
|
||||
this.customFileNamingGb.Text = "Custom file naming";
|
||||
//
|
||||
// chapterFileTemplateBtn
|
||||
//
|
||||
this.chapterFileTemplateBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.chapterFileTemplateBtn.Location = new System.Drawing.Point(808, 124);
|
||||
this.chapterFileTemplateBtn.Name = "chapterFileTemplateBtn";
|
||||
this.chapterFileTemplateBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.chapterFileTemplateBtn.TabIndex = 8;
|
||||
this.chapterFileTemplateBtn.Text = "Edit...";
|
||||
this.chapterFileTemplateBtn.UseVisualStyleBackColor = true;
|
||||
this.chapterFileTemplateBtn.Click += new System.EventHandler(this.chapterFileTemplateBtn_Click);
|
||||
//
|
||||
// chapterFileTemplateTb
|
||||
//
|
||||
this.chapterFileTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.chapterFileTemplateTb.Location = new System.Drawing.Point(6, 125);
|
||||
this.chapterFileTemplateTb.Name = "chapterFileTemplateTb";
|
||||
this.chapterFileTemplateTb.ReadOnly = true;
|
||||
this.chapterFileTemplateTb.Size = new System.Drawing.Size(796, 23);
|
||||
this.chapterFileTemplateTb.TabIndex = 7;
|
||||
//
|
||||
// chapterFileTemplateLbl
|
||||
//
|
||||
this.chapterFileTemplateLbl.AutoSize = true;
|
||||
this.chapterFileTemplateLbl.Location = new System.Drawing.Point(6, 107);
|
||||
this.chapterFileTemplateLbl.Name = "chapterFileTemplateLbl";
|
||||
this.chapterFileTemplateLbl.Size = new System.Drawing.Size(123, 15);
|
||||
this.chapterFileTemplateLbl.TabIndex = 6;
|
||||
this.chapterFileTemplateLbl.Text = "[folder template desc]";
|
||||
//
|
||||
// fileTemplateBtn
|
||||
//
|
||||
this.fileTemplateBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.fileTemplateBtn.Location = new System.Drawing.Point(808, 80);
|
||||
this.fileTemplateBtn.Name = "fileTemplateBtn";
|
||||
this.fileTemplateBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.fileTemplateBtn.TabIndex = 5;
|
||||
this.fileTemplateBtn.Text = "Edit...";
|
||||
this.fileTemplateBtn.UseVisualStyleBackColor = true;
|
||||
this.fileTemplateBtn.Click += new System.EventHandler(this.fileTemplateBtn_Click);
|
||||
//
|
||||
// fileTemplateTb
|
||||
//
|
||||
this.fileTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.fileTemplateTb.Location = new System.Drawing.Point(6, 81);
|
||||
this.fileTemplateTb.Name = "fileTemplateTb";
|
||||
this.fileTemplateTb.ReadOnly = true;
|
||||
this.fileTemplateTb.Size = new System.Drawing.Size(796, 23);
|
||||
this.fileTemplateTb.TabIndex = 4;
|
||||
//
|
||||
// fileTemplateLbl
|
||||
//
|
||||
this.fileTemplateLbl.AutoSize = true;
|
||||
this.fileTemplateLbl.Location = new System.Drawing.Point(6, 63);
|
||||
this.fileTemplateLbl.Name = "fileTemplateLbl";
|
||||
this.fileTemplateLbl.Size = new System.Drawing.Size(123, 15);
|
||||
this.fileTemplateLbl.TabIndex = 3;
|
||||
this.fileTemplateLbl.Text = "[folder template desc]";
|
||||
//
|
||||
// folderTemplateBtn
|
||||
//
|
||||
this.folderTemplateBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.folderTemplateBtn.Location = new System.Drawing.Point(807, 36);
|
||||
this.folderTemplateBtn.Name = "folderTemplateBtn";
|
||||
this.folderTemplateBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.folderTemplateBtn.TabIndex = 2;
|
||||
this.folderTemplateBtn.Text = "Edit...";
|
||||
this.folderTemplateBtn.UseVisualStyleBackColor = true;
|
||||
this.folderTemplateBtn.Click += new System.EventHandler(this.folderTemplateBtn_Click);
|
||||
//
|
||||
// folderTemplateTb
|
||||
//
|
||||
this.folderTemplateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.folderTemplateTb.Location = new System.Drawing.Point(5, 37);
|
||||
this.folderTemplateTb.Name = "folderTemplateTb";
|
||||
this.folderTemplateTb.ReadOnly = true;
|
||||
this.folderTemplateTb.Size = new System.Drawing.Size(796, 23);
|
||||
this.folderTemplateTb.TabIndex = 1;
|
||||
//
|
||||
// folderTemplateLbl
|
||||
//
|
||||
this.folderTemplateLbl.AutoSize = true;
|
||||
this.folderTemplateLbl.Location = new System.Drawing.Point(5, 19);
|
||||
this.folderTemplateLbl.Name = "folderTemplateLbl";
|
||||
this.folderTemplateLbl.Size = new System.Drawing.Size(123, 15);
|
||||
this.folderTemplateLbl.TabIndex = 0;
|
||||
this.folderTemplateLbl.Text = "[folder template desc]";
|
||||
//
|
||||
// SettingsDialog
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
@@ -325,11 +501,7 @@
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(933, 539);
|
||||
this.Controls.Add(this.logsBtn);
|
||||
this.Controls.Add(this.loggingLevelCb);
|
||||
this.Controls.Add(this.loggingLevelLbl);
|
||||
this.Controls.Add(this.booksGb);
|
||||
this.Controls.Add(this.advancedSettingsGb);
|
||||
this.Controls.Add(this.tabControl);
|
||||
this.Controls.Add(this.cancelBtn);
|
||||
this.Controls.Add(this.saveBtn);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
@@ -338,16 +510,23 @@
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Edit Settings";
|
||||
this.Load += new System.EventHandler(this.SettingsDialog_Load);
|
||||
this.advancedSettingsGb.ResumeLayout(false);
|
||||
this.advancedSettingsGb.PerformLayout();
|
||||
this.badBookGb.ResumeLayout(false);
|
||||
this.badBookGb.PerformLayout();
|
||||
this.decryptAndConvertGb.ResumeLayout(false);
|
||||
this.decryptAndConvertGb.PerformLayout();
|
||||
this.tabControl.ResumeLayout(false);
|
||||
this.tab1ImportantSettings.ResumeLayout(false);
|
||||
this.tab1ImportantSettings.PerformLayout();
|
||||
this.booksGb.ResumeLayout(false);
|
||||
this.booksGb.PerformLayout();
|
||||
this.tab2ImportLibrary.ResumeLayout(false);
|
||||
this.tab2ImportLibrary.PerformLayout();
|
||||
this.tab3DownloadDecrypt.ResumeLayout(false);
|
||||
this.inProgressFilesGb.ResumeLayout(false);
|
||||
this.inProgressFilesGb.PerformLayout();
|
||||
this.customFileNamingGb.ResumeLayout(false);
|
||||
this.customFileNamingGb.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
@@ -356,13 +535,11 @@
|
||||
private System.Windows.Forms.Label inProgressDescLbl;
|
||||
private System.Windows.Forms.Button saveBtn;
|
||||
private System.Windows.Forms.Button cancelBtn;
|
||||
private System.Windows.Forms.GroupBox advancedSettingsGb;
|
||||
private System.Windows.Forms.CheckBox allowLibationFixupCbox;
|
||||
private DirectoryOrCustomSelectControl booksSelectControl;
|
||||
private DirectorySelectControl inProgressSelectControl;
|
||||
private System.Windows.Forms.RadioButton convertLossyRb;
|
||||
private System.Windows.Forms.RadioButton convertLosslessRb;
|
||||
private System.Windows.Forms.GroupBox booksGb;
|
||||
private System.Windows.Forms.Button logsBtn;
|
||||
private System.Windows.Forms.Label loggingLevelLbl;
|
||||
private System.Windows.Forms.ComboBox loggingLevelCb;
|
||||
@@ -375,5 +552,21 @@
|
||||
private System.Windows.Forms.CheckBox downloadEpisodesCb;
|
||||
private System.Windows.Forms.CheckBox importEpisodesCb;
|
||||
private System.Windows.Forms.CheckBox splitFilesByChapterCbox;
|
||||
}
|
||||
private System.Windows.Forms.TabControl tabControl;
|
||||
private System.Windows.Forms.TabPage tab1ImportantSettings;
|
||||
private System.Windows.Forms.GroupBox booksGb;
|
||||
private System.Windows.Forms.TabPage tab2ImportLibrary;
|
||||
private System.Windows.Forms.TabPage tab3DownloadDecrypt;
|
||||
private System.Windows.Forms.GroupBox inProgressFilesGb;
|
||||
private System.Windows.Forms.GroupBox customFileNamingGb;
|
||||
private System.Windows.Forms.Button chapterFileTemplateBtn;
|
||||
private System.Windows.Forms.TextBox chapterFileTemplateTb;
|
||||
private System.Windows.Forms.Label chapterFileTemplateLbl;
|
||||
private System.Windows.Forms.Button fileTemplateBtn;
|
||||
private System.Windows.Forms.TextBox fileTemplateTb;
|
||||
private System.Windows.Forms.Label fileTemplateLbl;
|
||||
private System.Windows.Forms.Button folderTemplateBtn;
|
||||
private System.Windows.Forms.TextBox folderTemplateTb;
|
||||
private System.Windows.Forms.Label folderTemplateLbl;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
@@ -78,6 +79,13 @@ namespace LibationWinForms.Dialogs
|
||||
_ => 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 allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e)
|
||||
@@ -95,28 +103,56 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
private void logsBtn_Click(object sender, EventArgs e) => Go.To.Folder(Configuration.Instance.LibationFiles);
|
||||
|
||||
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)
|
||||
{
|
||||
var form = new EditTemplateDialog(template, textBox.Text);
|
||||
if (form.ShowDialog() == DialogResult.OK)
|
||||
textBox.Text = form.TemplateText;
|
||||
}
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
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))
|
||||
{
|
||||
MessageBox.Show("Cannot set Books Location to blank", "Location is blank", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
validationError("Cannot set Books Location to blank", "Location is blank");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(newBooks))
|
||||
if (!Directory.Exists(newBooks) && booksSelectControl.SelectedDirectoryIsCustom)
|
||||
{
|
||||
if (booksSelectControl.SelectedDirectoryIsCustom)
|
||||
{
|
||||
MessageBox.Show($"Not saving change to Books location. This folder does not exist:\r\n{newBooks}", "Folder does not exist", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (booksSelectControl.SelectedDirectoryIsKnown)
|
||||
Directory.CreateDirectory(newBooks);
|
||||
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;
|
||||
|
||||
{
|
||||
@@ -145,6 +181,10 @@ namespace LibationWinForms.Dialogs
|
||||
: 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.Close();
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="2.0.1.2" />
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="2.1.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
- [Files and folders](#files-and-folders)
|
||||
- [Linux and Mac (unofficial)](#linux-and-mac)
|
||||
- [Settings](#settings)
|
||||
- [Custom File Naming](#custom-file-naming)
|
||||
- [Command Line Interface](#command-line-interface)
|
||||
|
||||
## Audible audiobook manager
|
||||
@@ -243,6 +244,12 @@ Although Libation only currently officially supports Windows, [some users](https
|
||||
|
||||
* Allow Libation to fix up audiobook metadata. After decrypting a title, Libation attempts to fix details like chapters and cover art. Some power users and/or control freaks prefer to manage this themselves. By unchecking this setting, Libation will only decrypt the book and will leave metadata as-is, warts and all.
|
||||
|
||||
### Custom File Naming
|
||||
|
||||
In Settings, on the Download/Decrypt tab, you can specify the format in which you want your files to be named. As you edit these templates, a live example will be shown. Parameters are listed for folders, files, and files split by chapter including an explanation of what each naming option means. For instance: you can use template `<title short> - <ch# 0> of <ch count> - <ch title>` to create the file `A Study in Scarlet - 04 of 10 - A Flight for Life.m4b`.
|
||||
|
||||
These templates apply to GUI and CLI.
|
||||
|
||||
### Command Line Interface
|
||||
|
||||
Libationcli.exe allows limited access to Libation's functionalities as a CLI.
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dinah.Core;
|
||||
using FileLiberator;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace AudioFileStorageExtTests
|
||||
{
|
||||
[TestClass]
|
||||
public class GetValidFilename
|
||||
{
|
||||
private DataLayer.LibraryBook GetLibraryBook(string asin)
|
||||
{
|
||||
var book = new DataLayer.Book(new DataLayer.AudibleProductId(asin), "title", "desc", 1, DataLayer.ContentType.Product, new List<DataLayer.Contributor> { new DataLayer.Contributor("author") }, new List<DataLayer.Contributor> { new DataLayer.Contributor("narrator") }, new DataLayer.Category(new DataLayer.AudibleCategoryId("seriesId") , "name"), "us");
|
||||
var libraryBook = new DataLayer.LibraryBook(book, DateTime.Now, "my us");
|
||||
return libraryBook;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(null, "name", "ext", "suffix")]
|
||||
[DataRow(@"C:\", null, "ext", "suffix")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void arg_null_exception(string dirFullPath, string filename, string extension, string metadataSuffix)
|
||||
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix));
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("", "name", "ext", "suffix")]
|
||||
[DataRow(" ", "name", "ext", "suffix")]
|
||||
[DataRow(@"C:\", "", "ext", "suffix")]
|
||||
[DataRow(@"C:\", " ", "ext", "suffix")]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void arg_exception(string dirFullPath, string filename, string extension, string metadataSuffix)
|
||||
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix));
|
||||
|
||||
[TestMethod]
|
||||
public void null_extension() => Tests(@"C:\foo\bar", "my file", null, "meta", @"C:\foo\bar\my file [meta]");
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"C:\foo\bar", "my file", "txt", "my id", @"C:\foo\bar\my file [my id].txt")]
|
||||
public void Tests(string dirFullPath, string filename, string extension, string metadataSuffix, string expected)
|
||||
=> AudioFileStorageExt.GetValidFilename(dirFullPath, filename, extension, GetLibraryBook(metadataSuffix)).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
|
||||
@@ -7,7 +7,7 @@ using FileManager;
|
||||
using FluentAssertions;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace FileTemplateTests
|
||||
namespace FileNamingTemplateTests
|
||||
{
|
||||
[TestClass]
|
||||
public class GetFilePath
|
||||
@@ -17,7 +17,7 @@ namespace FileTemplateTests
|
||||
{
|
||||
var expected = @"C:\foo\bar\my_ book LONG_1234567890_1234567890_1234567890_123 [ID123456].txt";
|
||||
var f1 = OLD_GetValidFilename(@"C:\foo\bar", "my: book LONG_1234567890_1234567890_1234567890_12345", "txt", "ID123456");
|
||||
var f2 = NEW_GetValidFilename_FileTemplate(@"C:\foo\bar", "my: book LONG_1234567890_1234567890_1234567890_12345", "txt", "ID123456");
|
||||
var f2 = NEW_GetValidFilename_FileNamingTemplate(@"C:\foo\bar", "my: book LONG_1234567890_1234567890_1234567890_12345", "txt", "ID123456");
|
||||
|
||||
f1.Should().Be(expected);
|
||||
f1.Should().Be(f2);
|
||||
@@ -49,16 +49,16 @@ namespace FileTemplateTests
|
||||
|
||||
return fullfilename;
|
||||
}
|
||||
private static string NEW_GetValidFilename_FileTemplate(string dirFullPath, string filename, string extension, string metadataSuffix)
|
||||
private static string NEW_GetValidFilename_FileNamingTemplate(string dirFullPath, string filename, string extension, string metadataSuffix)
|
||||
{
|
||||
var template = $"<title> [<id>]";
|
||||
|
||||
var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension));
|
||||
|
||||
var fileTemplate = new FileTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
|
||||
fileTemplate.AddParameterReplacement("title", filename);
|
||||
fileTemplate.AddParameterReplacement("id", metadataSuffix);
|
||||
return fileTemplate.GetFilePath();
|
||||
var fileNamingTemplate = new FileNamingTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
|
||||
fileNamingTemplate.AddParameterReplacement("title", filename);
|
||||
fileNamingTemplate.AddParameterReplacement("id", metadataSuffix);
|
||||
return fileNamingTemplate.GetFilePath();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -66,7 +66,7 @@ namespace FileTemplateTests
|
||||
{
|
||||
var expected = @"C:\foo\bar\my file - 002 - title.txt";
|
||||
var f1 = OLD_GetMultipartFileName(@"C:\foo\bar\my file.txt", 2, 100, "title");
|
||||
var f2 = NEW_GetMultipartFileName_FileTemplate(@"C:\foo\bar\my file.txt", 2, 100, "title");
|
||||
var f2 = NEW_GetMultipartFileName_FileNamingTemplate(@"C:\foo\bar\my file.txt", 2, 100, "title");
|
||||
|
||||
f1.Should().Be(expected);
|
||||
f1.Should().Be(f2);
|
||||
@@ -89,7 +89,7 @@ namespace FileTemplateTests
|
||||
var path = Path.Combine(Path.GetDirectoryName(originalPath), fileName + extension);
|
||||
return path;
|
||||
}
|
||||
private static string NEW_GetMultipartFileName_FileTemplate(string originalPath, int partsPosition, int partsTotal, string suffix)
|
||||
private static string NEW_GetMultipartFileName_FileNamingTemplate(string originalPath, int partsPosition, int partsTotal, string suffix)
|
||||
{
|
||||
// 1-9 => 1-9
|
||||
// 10-99 => 01-99
|
||||
@@ -98,11 +98,18 @@ namespace FileTemplateTests
|
||||
|
||||
var t = Path.ChangeExtension(originalPath, null) + " - <chapter> - <title>" + Path.GetExtension(originalPath);
|
||||
|
||||
var fileTemplate = new FileTemplate(t) { IllegalCharacterReplacements = " " };
|
||||
fileTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros);
|
||||
fileTemplate.AddParameterReplacement("title", suffix);
|
||||
var fileNamingTemplate = new FileNamingTemplate(t) { IllegalCharacterReplacements = " " };
|
||||
fileNamingTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros);
|
||||
fileNamingTemplate.AddParameterReplacement("title", suffix);
|
||||
return fileNamingTemplate.GetFilePath();
|
||||
}
|
||||
|
||||
return fileTemplate.GetFilePath();
|
||||
[TestMethod]
|
||||
public void remove_slashes()
|
||||
{
|
||||
var fileNamingTemplate = new FileNamingTemplate(@"\foo\<title>.txt");
|
||||
fileNamingTemplate.AddParameterReplacement("title", @"s\l/a\s/h\e/s");
|
||||
fileNamingTemplate.GetFilePath().Should().Be(@"\foo\slashes.txt");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ namespace FileUtilityTests
|
||||
|
||||
// needs separate method. middle null param not running correctly in TestExplorer when used in DataRow()
|
||||
[TestMethod]
|
||||
[DataRow("http://test.com/a/b/c", @"http\\test.com\a\b\c")]
|
||||
[DataRow("http://test.com/a/b/c", @"http\test.com\a\b\c")]
|
||||
public void null_replacement(string inStr, string outStr) => Tests(inStr, null, outStr);
|
||||
|
||||
[TestMethod]
|
||||
@@ -31,8 +31,11 @@ namespace FileUtilityTests
|
||||
[DataRow("a*?:z.txt", "Z", "aZZZz.txt")]
|
||||
// retain drive letter path colon
|
||||
[DataRow(@"C:\az.txt", "Z", @"C:\az.txt")]
|
||||
// replace all other colongs
|
||||
// replace all other colons
|
||||
[DataRow(@"a\b:c\d.txt", "ZZZ", @"a\bZZZc\d.txt")]
|
||||
// remove empty directories
|
||||
[DataRow(@"C:\a\\\b\c\\\d.txt", "ZZZ", @"C:\a\b\c\d.txt")]
|
||||
[DataRow(@"C:\""foo\<id>", "ZZZ", @"C:\ZZZfoo\ZZZidZZZ")]
|
||||
public void Tests(string inStr, string replacement, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, replacement));
|
||||
}
|
||||
|
||||
@@ -90,4 +93,24 @@ namespace FileUtilityTests
|
||||
public void Tests(int partsPosition, int partsTotal, string expected)
|
||||
=> FileUtility.GetSequenceFormatted(partsPosition, partsTotal).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class GetStandardizedExtension
|
||||
{
|
||||
[TestMethod]
|
||||
public void is_null() => Tests(null, "");
|
||||
|
||||
[TestMethod]
|
||||
public void is_empty() => Tests("", "");
|
||||
|
||||
[TestMethod]
|
||||
public void is_whitespace() => Tests(" ", "");
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("txt", ".txt")]
|
||||
[DataRow(".txt", ".txt")]
|
||||
[DataRow(" .txt ", ".txt")]
|
||||
public void Tests(string input, string expected)
|
||||
=> FileUtility.GetStandardizedExtension(input).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\LibationFileManager\LibationFileManager.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
375
_Tests/LibationFileManager.Tests/TemplatesTests.cs
Normal file
375
_Tests/LibationFileManager.Tests/TemplatesTests.cs
Normal file
@@ -0,0 +1,375 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dinah.Core;
|
||||
using FluentAssertions;
|
||||
using LibationFileManager;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using static TemplatesTests.Shared;
|
||||
|
||||
namespace TemplatesTests
|
||||
{
|
||||
public static class Shared
|
||||
{
|
||||
public static LibraryBookDto GetLibraryBook(string asin)
|
||||
=> new()
|
||||
{
|
||||
Account = "my account",
|
||||
AudibleProductId = asin,
|
||||
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
|
||||
Locale = "us",
|
||||
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
|
||||
Narrators = new List<string> { "Stephen Fry" },
|
||||
SeriesName = "Sherlock Holmes",
|
||||
SeriesNumber = "1"
|
||||
};
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class ContainsChapterOnlyTags
|
||||
{
|
||||
[TestMethod]
|
||||
[DataRow("<ch>", false)]
|
||||
[DataRow("<ch#>", true)]
|
||||
[DataRow("<id>", false)]
|
||||
[DataRow("<id><ch#>", true)]
|
||||
public void Tests(string template, bool expected) => Templates.ContainsChapterOnlyTags(template).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class ContainsTag
|
||||
{
|
||||
[TestMethod]
|
||||
[DataRow("<ch#>", "ch#", true)]
|
||||
[DataRow("<id>", "ch#", false)]
|
||||
[DataRow("<id><ch#>", "ch#", true)]
|
||||
public void Tests(string template, string tag, bool expected) => Templates.ContainsTag(template, tag).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class getFileNamingTemplate
|
||||
{
|
||||
[TestMethod]
|
||||
[DataRow(null, "asin", @"C:\", "ext")]
|
||||
[ExpectedException(typeof(ArgumentNullException))]
|
||||
public void arg_null_exception(string template, string asin, string dirFullPath, string extension)
|
||||
=> Templates.getFileNamingTemplate(GetLibraryBook(asin), template, dirFullPath, extension);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("", "asin", @"C:\foo\bar", "ext")]
|
||||
[DataRow(" ", "asin", @"C:\foo\bar", "ext")]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void arg_exception(string template, string asin, string dirFullPath, string extension)
|
||||
=> Templates.getFileNamingTemplate(GetLibraryBook(asin), template, dirFullPath, extension);
|
||||
|
||||
[TestMethod]
|
||||
public void null_extension() => Tests("f.txt", "asin", @"C:\foo\bar", null, @"C:\foo\bar\f.txt");
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("f.txt", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\f.txt.ext")]
|
||||
[DataRow("f", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\f.ext")]
|
||||
[DataRow("<id>", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\asin.ext")]
|
||||
public void Tests(string template, string asin, string dirFullPath, string extension, string expected)
|
||||
=> Templates.getFileNamingTemplate(GetLibraryBook(asin), template, dirFullPath, extension)
|
||||
.GetFilePath()
|
||||
.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Templates_Folder_Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class GetErrors
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_is_invalid() => Tests(null, new[] { Templates.ERROR_NULL_IS_INVALID });
|
||||
|
||||
[TestMethod]
|
||||
public void empty_is_valid() => valid_tests("");
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_is_valid() => valid_tests(" ");
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"foo")]
|
||||
[DataRow(@"\foo")]
|
||||
[DataRow(@"foo\")]
|
||||
[DataRow(@"\foo\")]
|
||||
[DataRow(@"foo\bar")]
|
||||
[DataRow(@"<id>")]
|
||||
[DataRow(@"<id>\<title>")]
|
||||
public void valid_tests(string template) => Tests(template, Array.Empty<string>());
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"C:\", Templates.ERROR_FULL_PATH_IS_INVALID)]
|
||||
public void Tests(string template, params string[] expected)
|
||||
{
|
||||
var result = Templates.Folder.GetErrors(template);
|
||||
result.Count().Should().Be(expected.Length);
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class IsValid
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_is_invalid() => Tests(null, false);
|
||||
|
||||
[TestMethod]
|
||||
public void empty_is_valid() => Tests("", true);
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_is_valid() => Tests(" ", true);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"C:\", false)]
|
||||
[DataRow(@"foo", true)]
|
||||
[DataRow(@"\foo", true)]
|
||||
[DataRow(@"foo\", true)]
|
||||
[DataRow(@"\foo\", true)]
|
||||
[DataRow(@"foo\bar", true)]
|
||||
[DataRow(@"<id>", true)]
|
||||
[DataRow(@"<id>\<title>", true)]
|
||||
public void Tests(string template, bool expected) => Templates.Folder.IsValid(template).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class GetWarnings
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_is_invalid() => Tests(null, new[] { Templates.ERROR_NULL_IS_INVALID });
|
||||
|
||||
[TestMethod]
|
||||
public void empty_has_warnings() => Tests("", Templates.WARNING_EMPTY, Templates.WARNING_NO_TAGS);
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_has_warnings() => Tests(" ", Templates.WARNING_WHITE_SPACE, Templates.WARNING_NO_TAGS);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"<id>\foo\bar")]
|
||||
public void valid_tests(string template) => Tests(template, Array.Empty<string>());
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"no tags", Templates.WARNING_NO_TAGS)]
|
||||
[DataRow("<ch#> <id>", Templates.WARNING_HAS_CHAPTER_TAGS)]
|
||||
[DataRow("<ch#> chapter tag", Templates.WARNING_NO_TAGS, Templates.WARNING_HAS_CHAPTER_TAGS)]
|
||||
public void Tests(string template, params string[] expected)
|
||||
{
|
||||
var result = Templates.Folder.GetWarnings(template);
|
||||
result.Count().Should().Be(expected.Length);
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class HasWarnings
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_has_warnings() => Tests(null, true);
|
||||
|
||||
[TestMethod]
|
||||
public void empty_has_warnings() => Tests("", true);
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_has_warnings() => Tests(" ", true);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"no tags", true)]
|
||||
[DataRow(@"<id>\foo\bar", false)]
|
||||
[DataRow("<ch#> <id>", true)]
|
||||
[DataRow("<ch#> chapter tag", true)]
|
||||
public void Tests(string template, bool expected) => Templates.Folder.HasWarnings(template).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class TagCount
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_throws() => Assert.ThrowsException<NullReferenceException>(() => Templates.Folder.TagCount(null));
|
||||
|
||||
[TestMethod]
|
||||
public void empty() => Tests("", 0);
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace() => Tests(" ", 0);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("no tags", 0)]
|
||||
[DataRow(@"<id>\foo\bar", 1)]
|
||||
[DataRow("<id> <id>", 2)]
|
||||
[DataRow("<id <id> >", 1)]
|
||||
[DataRow("<id> <title>", 2)]
|
||||
[DataRow("id> <title incomplete tags", 0)]
|
||||
[DataRow("<not a real tag>", 0)]
|
||||
[DataRow("<ch#> non-folder tag", 0)]
|
||||
[DataRow("<ID> case specific", 0)]
|
||||
public void Tests(string template, int expected) => Templates.Folder.TagCount(template).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Templates_File_Tests
|
||||
{
|
||||
[TestClass]
|
||||
public class GetErrors
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_is_invalid() => Tests(null, new[] { Templates.ERROR_NULL_IS_INVALID });
|
||||
|
||||
[TestMethod]
|
||||
public void empty_is_valid() => valid_tests("");
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_is_valid() => valid_tests(" ");
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"foo")]
|
||||
[DataRow(@"<id>")]
|
||||
public void valid_tests(string template) => Tests(template, Array.Empty<string>());
|
||||
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"C:\", Templates.ERROR_INVALID_FILE_NAME_CHAR)]
|
||||
[DataRow(@"\foo", Templates.ERROR_INVALID_FILE_NAME_CHAR)]
|
||||
[DataRow(@"/foo", Templates.ERROR_INVALID_FILE_NAME_CHAR)]
|
||||
[DataRow(@"C:\", Templates.ERROR_INVALID_FILE_NAME_CHAR)]
|
||||
public void Tests(string template, params string[] expected)
|
||||
{
|
||||
var result = Templates.File.GetErrors(template);
|
||||
result.Count().Should().Be(expected.Length);
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class IsValid
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_is_invalid() => Tests(null, false);
|
||||
|
||||
[TestMethod]
|
||||
public void empty_is_valid() => Tests("", true);
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_is_valid() => Tests(" ", true);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"C:\", false)]
|
||||
[DataRow(@"foo", true)]
|
||||
[DataRow(@"\foo", false)]
|
||||
[DataRow(@"/foo", false)]
|
||||
[DataRow(@"<id>", true)]
|
||||
public void Tests(string template, bool expected) => Templates.File.IsValid(template).Should().Be(expected);
|
||||
}
|
||||
|
||||
// same as Templates.Folder.GetWarnings
|
||||
//[TestClass]
|
||||
//public class GetWarnings { }
|
||||
|
||||
// same as Templates.Folder.HasWarnings
|
||||
//[TestClass]
|
||||
//public class HasWarnings { }
|
||||
|
||||
// same as Templates.Folder.TagCount
|
||||
//[TestClass]
|
||||
//public class TagCount { }
|
||||
}
|
||||
|
||||
namespace Templates_ChapterFile_Tests
|
||||
{
|
||||
// same as Templates.File.GetErrors
|
||||
//[TestClass]
|
||||
//public class GetErrors { }
|
||||
|
||||
// same as Templates.File.IsValid
|
||||
//[TestClass]
|
||||
//public class IsValid { }
|
||||
|
||||
[TestClass]
|
||||
public class GetWarnings
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_is_invalid() => Tests(null, new[] { Templates.ERROR_NULL_IS_INVALID });
|
||||
|
||||
[TestMethod]
|
||||
public void empty_has_warnings() => Tests("", Templates.WARNING_EMPTY, Templates.WARNING_NO_TAGS, Templates.WARNING_NO_CHAPTER_NUMBER_TAG);
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_has_warnings() => Tests(" ", Templates.WARNING_WHITE_SPACE, Templates.WARNING_NO_TAGS, Templates.WARNING_NO_CHAPTER_NUMBER_TAG);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("<ch#>")]
|
||||
[DataRow("<ch#> <id>")]
|
||||
public void valid_tests(string template) => Tests(template, Array.Empty<string>());
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"no tags", Templates.WARNING_NO_TAGS, Templates.WARNING_NO_CHAPTER_NUMBER_TAG)]
|
||||
[DataRow(@"<id>\foo\bar", Templates.ERROR_INVALID_FILE_NAME_CHAR, Templates.WARNING_NO_CHAPTER_NUMBER_TAG)]
|
||||
[DataRow("<chapter count> -- chapter tag but not ch# or ch_#", Templates.WARNING_NO_TAGS, Templates.WARNING_NO_CHAPTER_NUMBER_TAG)]
|
||||
public void Tests(string template, params string[] expected)
|
||||
{
|
||||
var result = Templates.ChapterFile.GetWarnings(template);
|
||||
result.Count().Should().Be(expected.Length);
|
||||
result.Should().BeEquivalentTo(expected);
|
||||
}
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class HasWarnings
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_has_warnings() => Tests(null, true);
|
||||
|
||||
[TestMethod]
|
||||
public void empty_has_warnings() => Tests("", true);
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_has_warnings() => Tests(" ", true);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"no tags", true)]
|
||||
[DataRow(@"<id>\foo\bar", true)]
|
||||
[DataRow("<ch#> <id>", false)]
|
||||
[DataRow("<ch#> -- chapter tag", false)]
|
||||
[DataRow("<chapter count> -- chapter tag but not ch# or ch_#", true)]
|
||||
public void Tests(string template, bool expected) => Templates.ChapterFile.HasWarnings(template).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class TagCount
|
||||
{
|
||||
[TestMethod]
|
||||
public void null_is_not_recommended() => Assert.ThrowsException<NullReferenceException>(() => Tests(null, -1));
|
||||
|
||||
[TestMethod]
|
||||
public void empty_is_not_recommended() => Tests("", 0);
|
||||
|
||||
[TestMethod]
|
||||
public void whitespace_is_not_recommended() => Tests(" ", 0);
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("no tags", 0)]
|
||||
[DataRow(@"<id>\foo\bar", 1)]
|
||||
[DataRow("<id> <id>", 2)]
|
||||
[DataRow("<id <id> >", 1)]
|
||||
[DataRow("<id> <title>", 2)]
|
||||
[DataRow("id> <title incomplete tags", 0)]
|
||||
[DataRow("<not a real tag>", 0)]
|
||||
[DataRow("<ch#> non-folder tag", 1)]
|
||||
[DataRow("<ID> case specific", 0)]
|
||||
public void Tests(string template, int expected) => Templates.ChapterFile.TagCount(template).Should().Be(expected);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public class GetPortionFilename
|
||||
{
|
||||
[TestMethod]
|
||||
[DataRow("asin", "[<id>] <ch# 0> of <ch count> - <ch title>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\[asin] 06 of 10 - chap.txt")]
|
||||
[DataRow("asin", "<ch#>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\6.txt")]
|
||||
public void Tests(string asin, string template, string dir, string ext, int pos, int total, string chapter, string expected)
|
||||
=> Templates.ChapterFile.GetPortionFilename(GetLibraryBook(asin), template, new() { OutputFileName = $"xyz.{ext}", PartsPosition = pos, PartsTotal = total, Title = chapter }, dir)
|
||||
.Should().Be(expected);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||
|
||||
@@ -24,7 +24,7 @@ STRUCTURE
|
||||
* 2 Utilities (domain ignorant)
|
||||
Stand-alone libraries with no knowledge of anything having to do with Libation or other programs. In theory any of these should be able to one day be converted to a nuget pkg
|
||||
* 3 Domain Internal Utilities (db ignorant)
|
||||
Can have knowledge of Libation concepts. Cannot access the database.
|
||||
Cannot access the database. Can have knowledge of Libation concepts. Can even have knowledge of some db concepts, but no actual db access.
|
||||
* 4 Domain (db)
|
||||
All database access
|
||||
* 5 Domain Utilities (db aware)
|
||||
|
||||
Reference in New Issue
Block a user