Date format naming templates

This commit is contained in:
Michael Bucari-Tovo 2023-01-20 00:37:59 -07:00
parent 9309aea6d9
commit 3479dbc3f0
11 changed files with 291 additions and 60 deletions

View File

@ -10,6 +10,6 @@ namespace AaxDecrypter
public int PartsPosition { get; set; } public int PartsPosition { get; set; }
public int PartsTotal { get; set; } public int PartsTotal { get; set; }
public string Title { get; set; } public string Title { get; set; }
public DateTime FileDate { get; } = DateTime.Now;
} }
} }

View File

@ -27,11 +27,13 @@ namespace FileLiberator
public static LibraryBookDto ToDto(this LibraryBook libraryBook) => new() public static LibraryBookDto ToDto(this LibraryBook libraryBook) => new()
{ {
Account = libraryBook.Account, Account = libraryBook.Account,
DateAdded = libraryBook.DateAdded,
AudibleProductId = libraryBook.Book.AudibleProductId, AudibleProductId = libraryBook.Book.AudibleProductId,
Title = libraryBook.Book.Title ?? "", Title = libraryBook.Book.Title ?? "",
Locale = libraryBook.Book.Locale, Locale = libraryBook.Book.Locale,
YearPublished = libraryBook.Book.DatePublished?.Year, YearPublished = libraryBook.Book.DatePublished?.Year,
DatePublished = libraryBook.Book.DatePublished,
Authors = libraryBook.Book.Authors.Select(c => c.Name).ToList(), Authors = libraryBook.Book.Authors.Select(c => c.Name).ToList(),

View File

@ -9,11 +9,15 @@ namespace FileManager
/// <summary>Get valid filename. Advanced features incl. parameterized template</summary> /// <summary>Get valid filename. Advanced features incl. parameterized template</summary>
public class FileNamingTemplate : NamingTemplate public class FileNamingTemplate : NamingTemplate
{ {
public ReplacementCharacters ReplacementCharacters { get; }
/// <param name="template">Proposed file name with optional html-styled template tags.</param> /// <param name="template">Proposed file name with optional html-styled template tags.</param>
public FileNamingTemplate(string template) : base(template) { } public FileNamingTemplate(string template, ReplacementCharacters replacement) : base(template)
{
ReplacementCharacters = replacement ?? ReplacementCharacters.Default;
}
/// <summary>Generate a valid path for this file or directory</summary> /// <summary>Generate a valid path for this file or directory</summary>
public LongPath GetFilePath(ReplacementCharacters replacements, string fileExtension, bool returnFirstExisting = false) public LongPath GetFilePath(string fileExtension, bool returnFirstExisting = false)
{ {
string fileName = string fileName =
Template.EndsWith(Path.DirectorySeparatorChar) || Template.EndsWith(Path.AltDirectorySeparatorChar) ? Template.EndsWith(Path.DirectorySeparatorChar) || Template.EndsWith(Path.AltDirectorySeparatorChar) ?
@ -22,7 +26,7 @@ namespace FileManager
List<string> pathParts = new(); List<string> pathParts = new();
var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value, replacements)); var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value, ReplacementCharacters));
while (!string.IsNullOrEmpty(fileName)) while (!string.IsNullOrEmpty(fileName))
{ {
@ -54,7 +58,7 @@ namespace FileManager
return FileUtility return FileUtility
.GetValidFilename( .GetValidFilename(
Path.Join(directory, replaceFileName(fileNamePart, paramReplacements, LongPath.MaxFilenameLength - fileExtension.Length - 5)) + fileExtension, Path.Join(directory, replaceFileName(fileNamePart, paramReplacements, LongPath.MaxFilenameLength - fileExtension.Length - 5)) + fileExtension,
replacements, ReplacementCharacters,
fileExtension, fileExtension,
returnFirstExisting returnFirstExisting
); );

View File

@ -50,7 +50,9 @@ namespace LibationAvalonia.Dialogs
{ {
var dataGrid = sender as DataGrid; var dataGrid = sender as DataGrid;
var item = (dataGrid.SelectedItem as Tuple<string, string>).Item1.Replace("\x200C", "").Replace("...", ""); var item = (dataGrid.SelectedItem as Tuple<string, string, string>).Item3;
if (string.IsNullOrWhiteSpace(item)) return;
var text = userEditTbox.Text; var text = userEditTbox.Text;
userEditTbox.Text = text.Insert(Math.Min(Math.Max(0, userEditTbox.CaretIndex), text.Length), item); userEditTbox.Text = text.Insert(Math.Min(Math.Max(0, userEditTbox.CaretIndex), text.Length), item);
@ -84,13 +86,14 @@ namespace LibationAvalonia.Dialogs
Template = templates; Template = templates;
Description = templates.Description; Description = templates.Description;
ListItems ListItems
= new AvaloniaList<Tuple<string, string>>( = new AvaloniaList<Tuple<string, string, string>>(
Template Template
.GetTemplateTags() .GetTemplateTags()
.Select( .Select(
t => new Tuple<string, string>( t => new Tuple<string, string, string>(
$"<{t.TagName.Replace("->", "-\x200C>").Replace("<-", "<\x200C-")}>", $"<{t.TagName.Replace("->", "-\x200C>").Replace("<-", "<\x200C-")}>",
t.Description) t.Description,
t.DefaultValue)
) )
); );
@ -108,13 +111,13 @@ namespace LibationAvalonia.Dialogs
} }
} }
public string workingTemplateText => Template.Sanitize(UserTemplateText); public string workingTemplateText => Template.Sanitize(UserTemplateText, Configuration.Instance.ReplacementCharacters);
private string _warningText; private string _warningText;
public string WarningText { get => _warningText; set => this.RaiseAndSetIfChanged(ref _warningText, value); } public string WarningText { get => _warningText; set => this.RaiseAndSetIfChanged(ref _warningText, value); }
public string Description { get; } public string Description { get; }
public AvaloniaList<Tuple<string, string>> ListItems { get; set; } public AvaloniaList<Tuple<string, string, string>> ListItems { get; set; }
public void resetTextBox(string value) => UserTemplateText = value; public void resetTextBox(string value) => UserTemplateText = value;
@ -138,6 +141,8 @@ namespace LibationAvalonia.Dialogs
var libraryBookDto = new LibraryBookDto var libraryBookDto = new LibraryBookDto
{ {
Account = "my account", Account = "my account",
DateAdded = new DateTime(2022, 6, 9, 0, 0, 0),
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
AudibleProductId = "123456789", AudibleProductId = "123456789",
Title = "A Study in Scarlet: A Sherlock Holmes Novel", Title = "A Study in Scarlet: A Sherlock Holmes Novel",
Locale = "us", Locale = "us",

View File

@ -25,10 +25,14 @@ namespace LibationFileManager
public int BitRate { get; set; } public int BitRate { get; set; }
public int SampleRate { get; set; } public int SampleRate { get; set; }
public int Channels { get; set; } public int Channels { get; set; }
} public DateTime FileDate { get; set; } = DateTime.Now;
public DateTime? DatePublished { get; set; }
}
public class LibraryBookDto : BookDto public class LibraryBookDto : BookDto
{ {
public string Account { get; set; } public DateTime? DateAdded { get; set; }
public string Account { get; set; }
} }
} }

View File

@ -7,16 +7,19 @@ namespace LibationFileManager
{ {
public sealed class TemplateTags : Enumeration<TemplateTags> public sealed class TemplateTags : Enumeration<TemplateTags>
{ {
public string TagName => DisplayName; public string TagName => DisplayName;
public string DefaultValue { get; }
public string Description { get; } public string Description { get; }
public bool IsChapterOnly { get; } public bool IsChapterOnly { get; }
private static int value = 0; private static int value = 0;
private TemplateTags(string tagName, string description, bool isChapterOnly = false) : base(value++, tagName) private TemplateTags(string tagName, string description, bool isChapterOnly = false, string defaultValue = null) : base(value++, tagName)
{ {
Description = description; Description = description;
IsChapterOnly = isChapterOnly; IsChapterOnly = isChapterOnly;
} DefaultValue = defaultValue ?? $"<{tagName}>";
}
// putting these first is the incredibly lazy way to make them show up first in the EditTemplateDialog // 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 ChCount { get; } = new TemplateTags("ch count", "Number of chapters", true);
@ -43,7 +46,9 @@ namespace LibationFileManager
// Special cases. Aren't mapped to replacements in Templates.cs // Special cases. Aren't mapped to replacements in Templates.cs
// Included here for display by EditTemplateDialog // Included here for display by EditTemplateDialog
public static TemplateTags Date { get; } = new TemplateTags("date[...]", "File date/time. e.g. yyyy-MM-dd HH-mm"); public static TemplateTags FileDate { get; } = new TemplateTags("file date [...]", "File date/time. e.g. yyyy-MM-dd HH-mm", false, $"<file date [{Templates.DEFAULT_DATE_FORMAT}]>");
public static TemplateTags IfSeries { get; } = new TemplateTags("if series->...<-if series", "Only include if part of a series"); public static TemplateTags DatePublished { get; } = new TemplateTags("pub date [...]", "Publication date. e.g. yyyy-MM-dd", false, $"<pub date [{Templates.DEFAULT_DATE_FORMAT}]>");
public static TemplateTags DateAdded { get; } = new TemplateTags("date added [...]", "Date added to you Audible account. e.g. yyyy-MM-dd", false, $"<date added [{Templates.DEFAULT_DATE_FORMAT}]>");
public static TemplateTags IfSeries { get; } = new TemplateTags("if series->...<-if series", "Only include if part of a series", false, "<if series-><-if series>");
} }
} }

View File

@ -4,6 +4,7 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dinah.Core; using Dinah.Core;
using Dinah.Core.Collections.Generic;
using FileManager; using FileManager;
namespace LibationFileManager namespace LibationFileManager
@ -102,17 +103,21 @@ namespace LibationFileManager
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, string fileExtension) public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, string fileExtension)
=> string.IsNullOrWhiteSpace(template) => string.IsNullOrWhiteSpace(template)
? "" ? ""
: getFileNamingTemplate(libraryBookDto, template, null, fileExtension) : getFileNamingTemplate(libraryBookDto, template, null, fileExtension, Configuration.Instance.ReplacementCharacters)
.GetFilePath(Configuration.Instance.ReplacementCharacters, fileExtension).PathWithoutPrefix; .GetFilePath(fileExtension).PathWithoutPrefix;
private static Regex dateFormatRegex { get; } = new Regex(@"<date\[(.*?)\]>", RegexOptions.Compiled | RegexOptions.IgnoreCase); public const string DEFAULT_DATE_FORMAT = "yyyy-MM-dd";
private static Regex fileDateTagRegex { get; } = new Regex(@"<file\s*?date\s*?(?:\s*?|\[(.*?)\])\s*?>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex dateAddedTagRegex { get; } = new Regex(@"<date\s*?added\s*?(?:\s*?|\[(.*?)\])\s*?>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex datePublishedTagRegex { get; } = new Regex(@"<pub\s*?date\s*?(?:\s*?|\[(.*?)\])\s*?>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static Regex ifSeriesRegex { get; } = new Regex("<if series->(.*?)<-if series>", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static Regex ifSeriesRegex { get; } = new Regex("<if series->(.*?)<-if series>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static FileNamingTemplate getFileNamingTemplate(LibraryBookDto libraryBookDto, string template, string dirFullPath, string extension) internal static FileNamingTemplate getFileNamingTemplate(LibraryBookDto libraryBookDto, string template, string dirFullPath, string extension, ReplacementCharacters replacements)
{ {
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template)); ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto)); ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
replacements ??= Configuration.Instance.ReplacementCharacters;
dirFullPath = dirFullPath?.Trim() ?? ""; dirFullPath = dirFullPath?.Trim() ?? "";
// for non-series, remove <if series-> and <-if series> tags and everything in between // for non-series, remove <if series-> and <-if series> tags and everything in between
@ -121,12 +126,16 @@ namespace LibationFileManager
template, template,
string.IsNullOrWhiteSpace(libraryBookDto.SeriesName) ? "" : "$1"); string.IsNullOrWhiteSpace(libraryBookDto.SeriesName) ? "" : "$1");
template = dateFormatRegex.Replace(template, dateMatchEvaluator); //Get date replacement parameters. Sanitizes the format text and replaces
//the template with the sanitized text before creating FileNamingTemplate
var fileDateParams = getSanitizeDateReplacementParameters(fileDateTagRegex, ref template, replacements, libraryBookDto.FileDate);
var dateAddedParams = getSanitizeDateReplacementParameters(dateAddedTagRegex, ref template, replacements, libraryBookDto.DateAdded);
var pubDateParams = getSanitizeDateReplacementParameters(datePublishedTagRegex, ref template, replacements, libraryBookDto.DatePublished);
var t = template + FileUtility.GetStandardizedExtension(extension); var t = template + FileUtility.GetStandardizedExtension(extension);
var fullfilename = dirFullPath == "" ? t : Path.Combine(dirFullPath, t); var fullfilename = dirFullPath == "" ? t : Path.Combine(dirFullPath, t);
var fileNamingTemplate = new FileNamingTemplate(fullfilename); var fileNamingTemplate = new FileNamingTemplate(fullfilename, replacements);
var title = libraryBookDto.Title ?? ""; var title = libraryBookDto.Title ?? "";
var titleShort = title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':')); var titleShort = title.IndexOf(':') < 1 ? title : title.Substring(0, title.IndexOf(':'));
@ -147,31 +156,92 @@ namespace LibationFileManager
fileNamingTemplate.AddParameterReplacement(TemplateTags.Locale, libraryBookDto.Locale); fileNamingTemplate.AddParameterReplacement(TemplateTags.Locale, libraryBookDto.Locale);
fileNamingTemplate.AddParameterReplacement(TemplateTags.YearPublished, libraryBookDto.YearPublished?.ToString() ?? "1900"); fileNamingTemplate.AddParameterReplacement(TemplateTags.YearPublished, libraryBookDto.YearPublished?.ToString() ?? "1900");
return fileNamingTemplate; //Add the sanitized replacement parameters
foreach (var param in fileDateParams)
fileNamingTemplate.ParameterReplacements.AddIfNotContains(param);
foreach (var param in dateAddedParams)
fileNamingTemplate.ParameterReplacements.AddIfNotContains(param);
foreach (var param in pubDateParams)
fileNamingTemplate.ParameterReplacements.AddIfNotContains(param);
return fileNamingTemplate;
} }
#endregion #endregion
private static string dateMatchEvaluator(Match match) #region DateTime Tags
/// <param name="template">the file naming template. Any found date tags will be sanitized,
/// and the template's original date tag will be replaced with the sanitized tag.</param>
/// <returns>A list of parameter replacement key-value pairs</returns>
private static List<KeyValuePair<string, object>> getSanitizeDateReplacementParameters(Regex datePattern, ref string template, ReplacementCharacters replacements, DateTime? dateTime)
{ {
List<KeyValuePair<string, object>> dateParams = new();
foreach (Match dateTag in datePattern.Matches(template))
{
var sanitizedTag = sanitizeDateParameterTag(dateTag, replacements, out var sanitizedFormatter);
if (tryFormatDateTime(dateTime, sanitizedFormatter, replacements, out var formattedDateString))
{
dateParams.Add(new(sanitizedTag, formattedDateString));
template = template.Replace(dateTag.Value, sanitizedTag);
}
}
return dateParams;
}
/// <returns>a date parameter replacement tag with the format string sanitized</returns>
private static string sanitizeDateParameterTag(Match dateTag, ReplacementCharacters replacements, out string sanitizedFormatter)
{
if (dateTag.Groups.Count < 2 || string.IsNullOrWhiteSpace(dateTag.Groups[1].Value))
{
sanitizedFormatter = DEFAULT_DATE_FORMAT;
return dateTag.Value;
}
var formatter = dateTag.Groups[1].Value;
sanitizedFormatter = replacements.ReplaceFilenameChars(formatter).Trim();
return dateTag.Value.Replace(formatter, sanitizedFormatter);
}
private static bool tryFormatDateTime(DateTime? dateTime, string sanitizedFormatter, ReplacementCharacters replacements, out string formattedDateString)
{
if (!dateTime.HasValue)
{
formattedDateString = string.Empty;
return true;
}
try try
{ {
return DateTime.Now.ToString(match.Groups[1].Value); formattedDateString = replacements.ReplaceFilenameChars(dateTime.Value.ToString(sanitizedFormatter)).Trim();
return true;
} }
catch catch
{ {
return match.Value; formattedDateString = null;
return false;
} }
} }
#endregion
public virtual IEnumerable<TemplateTags> GetTemplateTags() public virtual IEnumerable<TemplateTags> GetTemplateTags()
=> TemplateTags.GetAll() => TemplateTags.GetAll()
// yeah, this line is a little funky but it works when you think through it. also: trust the unit tests // yeah, this line is a little funky but it works when you think through it. also: trust the unit tests
.Where(t => IsChapterized || !t.IsChapterOnly); .Where(t => IsChapterized || !t.IsChapterOnly);
public string Sanitize(string template) public string Sanitize(string template, ReplacementCharacters replacements)
{ {
var value = template ?? ""; var value = template ?? "";
// Replace invalid filename characters in the DateTime format provider so we don't trip any alarms.
// Illegal filename characters in the formatter are allowed because they will be replaced by
// getFileNamingTemplate()
value = fileDateTagRegex.Replace(value, m => sanitizeDateParameterTag(m, replacements, out _));
value = dateAddedTagRegex.Replace(value, m => sanitizeDateParameterTag(m, replacements, out _));
value = datePublishedTagRegex.Replace(value, m => sanitizeDateParameterTag(m, replacements, out _));
// don't use alt slash // don't use alt slash
value = value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); value = value.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
@ -218,7 +288,7 @@ namespace LibationFileManager
// must be relative. no colons. all other path chars are valid enough to pass this check and will be handled on final save. // must be relative. no colons. all other path chars are valid enough to pass this check and will be handled on final save.
if (ReplacementCharacters.ContainsInvalidPathChar(template.Replace("<", "").Replace(">", ""))) if (ReplacementCharacters.ContainsInvalidPathChar(template.Replace("<", "").Replace(">", "")))
return new[] { ERROR_INVALID_FILE_NAME_CHAR }; return new[] { ERROR_INVALID_FILE_NAME_CHAR };
return Valid; return Valid;
} }
@ -229,8 +299,8 @@ namespace LibationFileManager
#region to file name #region to file name
/// <summary>USES LIVE CONFIGURATION VALUES</summary> /// <summary>USES LIVE CONFIGURATION VALUES</summary>
public string GetFilename(LibraryBookDto libraryBookDto, string baseDir = null) public string GetFilename(LibraryBookDto libraryBookDto, string baseDir = null)
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FolderTemplate, baseDir ?? AudibleFileStorage.BooksDirectory, null) => getFileNamingTemplate(libraryBookDto, Configuration.Instance.FolderTemplate, baseDir ?? AudibleFileStorage.BooksDirectory, null, Configuration.Instance.ReplacementCharacters)
.GetFilePath(Configuration.Instance.ReplacementCharacters, string.Empty); .GetFilePath(string.Empty);
#endregion #endregion
} }
@ -252,8 +322,8 @@ namespace LibationFileManager
#region to file name #region to file name
/// <summary>USES LIVE CONFIGURATION VALUES</summary> /// <summary>USES LIVE CONFIGURATION VALUES</summary>
public string GetFilename(LibraryBookDto libraryBookDto, string dirFullPath, string extension, bool returnFirstExisting = false) public string GetFilename(LibraryBookDto libraryBookDto, string dirFullPath, string extension, bool returnFirstExisting = false)
=> getFileNamingTemplate(libraryBookDto, Configuration.Instance.FileTemplate, dirFullPath, extension) => getFileNamingTemplate(libraryBookDto, Configuration.Instance.FileTemplate, dirFullPath, extension, Configuration.Instance.ReplacementCharacters)
.GetFilePath(Configuration.Instance.ReplacementCharacters, extension, returnFirstExisting); .GetFilePath(extension, returnFirstExisting);
#endregion #endregion
} }
@ -294,14 +364,21 @@ namespace LibationFileManager
replacements ??= Configuration.Instance.ReplacementCharacters; replacements ??= Configuration.Instance.ReplacementCharacters;
var fileExtension = Path.GetExtension(props.OutputFileName); var fileExtension = Path.GetExtension(props.OutputFileName);
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, fileExtension); var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, fileExtension, replacements);
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChCount, props.PartsTotal); fileNamingTemplate.AddParameterReplacement(TemplateTags.ChCount, props.PartsTotal);
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber, props.PartsPosition); fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber, props.PartsPosition);
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(props.PartsPosition, props.PartsTotal)); fileNamingTemplate.AddParameterReplacement(TemplateTags.ChNumber0, FileUtility.GetSequenceFormatted(props.PartsPosition, props.PartsTotal));
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChTitle, props.Title ?? ""); fileNamingTemplate.AddParameterReplacement(TemplateTags.ChTitle, props.Title ?? "");
return fileNamingTemplate.GetFilePath(replacements, fileExtension).PathWithoutPrefix; foreach (Match dateTag in fileDateTagRegex.Matches(fileNamingTemplate.Template))
{
var sanitizedTag = sanitizeDateParameterTag(dateTag, replacements, out string sanitizedFormatter);
if (tryFormatDateTime(props.FileDate, sanitizedFormatter, replacements, out var formattedDateString))
fileNamingTemplate.ParameterReplacements[sanitizedTag] = formattedDateString;
}
return fileNamingTemplate.GetFilePath(fileExtension).PathWithoutPrefix;
} }
#endregion #endregion
} }

View File

@ -9,5 +9,8 @@ namespace LibationFileManager
{ {
public static void AddParameterReplacement(this NamingTemplate fileNamingTemplate, TemplateTags templateTags, object value) public static void AddParameterReplacement(this NamingTemplate fileNamingTemplate, TemplateTags templateTags, object value)
=> fileNamingTemplate.AddParameterReplacement(templateTags.TagName, value); => fileNamingTemplate.AddParameterReplacement(templateTags.TagName, value);
public static void AddUniqueParameterReplacement(this NamingTemplate namingTemplate, string key, object value)
=> namingTemplate.ParameterReplacements[key] = value;
} }
} }

View File

@ -18,7 +18,7 @@ namespace LibationWinForms.Dialogs
private string workingTemplateText private string workingTemplateText
{ {
get => _workingTemplateText; get => _workingTemplateText;
set => _workingTemplateText = template.Sanitize(value); set => _workingTemplateText = template.Sanitize(value, Configuration.Instance.ReplacementCharacters);
} }
private void resetTextBox(string value) => this.templateTb.Text = workingTemplateText = value; private void resetTextBox(string value) => this.templateTb.Text = workingTemplateText = value;
@ -59,7 +59,7 @@ namespace LibationWinForms.Dialogs
// populate list view // populate list view
foreach (var tag in template.GetTemplateTags()) foreach (var tag in template.GetTemplateTags())
listView1.Items.Add(new ListViewItem(new[] { $"<{tag.TagName}>", tag.Description })); listView1.Items.Add(new ListViewItem(new[] { $"<{tag.TagName}>", tag.Description }) { Tag = tag.DefaultValue });
} }
private void resetToDefaultBtn_Click(object sender, EventArgs e) => resetTextBox(template.DefaultTemplate); private void resetToDefaultBtn_Click(object sender, EventArgs e) => resetTextBox(template.DefaultTemplate);
@ -73,6 +73,8 @@ namespace LibationWinForms.Dialogs
var libraryBookDto = new LibraryBookDto var libraryBookDto = new LibraryBookDto
{ {
Account = "my account", Account = "my account",
DateAdded = new DateTime(2022, 6, 9, 0, 0, 0),
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
AudibleProductId = "123456789", AudibleProductId = "123456789",
Title = "A Study in Scarlet: A Sherlock Holmes Novel", Title = "A Study in Scarlet: A Sherlock Holmes Novel",
Locale = "us", Locale = "us",
@ -207,9 +209,11 @@ namespace LibationWinForms.Dialogs
private void listView1_DoubleClick(object sender, EventArgs e) private void listView1_DoubleClick(object sender, EventArgs e)
{ {
var itemText = listView1.SelectedItems[0].Text.Replace("...", ""); var itemText = listView1.SelectedItems[0].Tag as string;
var text = templateTb.Text;
if (string.IsNullOrEmpty(itemText)) return;
var text = templateTb.Text;
var selStart = Math.Min(Math.Max(0, templateTb.SelectionStart), text.Length); var selStart = Math.Min(Math.Max(0, templateTb.SelectionStart), text.Length);
templateTb.Text = text.Insert(selStart, itemText); templateTb.Text = text.Insert(selStart, itemText);

View File

@ -36,10 +36,10 @@ namespace FileNamingTemplateTests
extension = FileUtility.GetStandardizedExtension(extension); extension = FileUtility.GetStandardizedExtension(extension);
var fullfilename = Path.Combine(dirFullPath, template + extension); var fullfilename = Path.Combine(dirFullPath, template + extension);
var fileNamingTemplate = new FileNamingTemplate(fullfilename); var fileNamingTemplate = new FileNamingTemplate(fullfilename, Replacements);
fileNamingTemplate.AddParameterReplacement("title", filename); fileNamingTemplate.AddParameterReplacement("title", filename);
fileNamingTemplate.AddParameterReplacement("id", metadataSuffix); fileNamingTemplate.AddParameterReplacement("id", metadataSuffix);
return fileNamingTemplate.GetFilePath(Replacements, extension).PathWithoutPrefix; return fileNamingTemplate.GetFilePath(extension).PathWithoutPrefix;
} }
[TestMethod] [TestMethod]
@ -61,10 +61,10 @@ namespace FileNamingTemplateTests
var estension = Path.GetExtension(originalPath); var estension = Path.GetExtension(originalPath);
var t = Path.ChangeExtension(originalPath, null) + " - <chapter> - <title>" + estension; var t = Path.ChangeExtension(originalPath, null) + " - <chapter> - <title>" + estension;
var fileNamingTemplate = new FileNamingTemplate(t); var fileNamingTemplate = new FileNamingTemplate(t, Replacements);
fileNamingTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros); fileNamingTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros);
fileNamingTemplate.AddParameterReplacement("title", suffix); fileNamingTemplate.AddParameterReplacement("title", suffix);
return fileNamingTemplate.GetFilePath(Replacements, estension).PathWithoutPrefix; return fileNamingTemplate.GetFilePath(estension).PathWithoutPrefix;
} }
[TestMethod] [TestMethod]
@ -74,9 +74,9 @@ namespace FileNamingTemplateTests
{ {
if (Environment.OSVersion.Platform == platformID) if (Environment.OSVersion.Platform == platformID)
{ {
var fileNamingTemplate = new FileNamingTemplate(inStr); var fileNamingTemplate = new FileNamingTemplate(inStr, Replacements);
fileNamingTemplate.AddParameterReplacement("title", @"s\l/a\s/h\e/s"); fileNamingTemplate.AddParameterReplacement("title", @"s\l/a\s/h\e/s");
fileNamingTemplate.GetFilePath(Replacements, "txt").PathWithoutPrefix.Should().Be(outStr); fileNamingTemplate.GetFilePath("txt").PathWithoutPrefix.Should().Be(outStr);
} }
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -26,11 +26,32 @@ namespace TemplatesTests
=> new() => new()
{ {
Account = "my account", Account = "my account",
DateAdded = new DateTime(2022, 6, 9, 0, 0, 0),
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
FileDate = new DateTime(2023, 1, 28, 0, 0, 0),
AudibleProductId = "asin", AudibleProductId = "asin",
Title = "A Study in Scarlet: A Sherlock Holmes Novel", Title = "A Study in Scarlet: A Sherlock Holmes Novel",
Locale = "us", Locale = "us",
YearPublished = 2017, YearPublished = 2017,
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" }, Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
Narrators = new List<string> { "Stephen Fry" },
SeriesName = seriesName ?? "",
SeriesNumber = "1",
BitRate = 128,
SampleRate = 44100,
Channels = 2
};
public static LibraryBookDto GetLibraryBookWithNullDates(string seriesName = "Sherlock Holmes")
=> new()
{
Account = "my account",
FileDate = new DateTime(2023, 1, 28, 0, 0, 0),
AudibleProductId = "asin",
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
Locale = "us",
YearPublished = 2017,
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
Narrators = new List<string> { "Stephen Fry" }, Narrators = new List<string> { "Stephen Fry" },
SeriesName = seriesName ?? "", SeriesName = seriesName ?? "",
SeriesNumber = "1", SeriesNumber = "1",
@ -71,14 +92,14 @@ namespace TemplatesTests
[DataRow(null, @"C:\", "ext")] [DataRow(null, @"C:\", "ext")]
[ExpectedException(typeof(ArgumentNullException))] [ExpectedException(typeof(ArgumentNullException))]
public void arg_null_exception(string template, string dirFullPath, string extension) public void arg_null_exception(string template, string dirFullPath, string extension)
=> Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension); => Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension, Replacements);
[TestMethod] [TestMethod]
[DataRow("", @"C:\foo\bar", "ext")] [DataRow("", @"C:\foo\bar", "ext")]
[DataRow(" ", @"C:\foo\bar", "ext")] [DataRow(" ", @"C:\foo\bar", "ext")]
[ExpectedException(typeof(ArgumentException))] [ExpectedException(typeof(ArgumentException))]
public void arg_exception(string template, string dirFullPath, string extension) public void arg_exception(string template, string dirFullPath, string extension)
=> Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension); => Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension, Replacements);
[TestMethod] [TestMethod]
[DataRow("f.txt", @"C:\foo\bar", "", @"C:\foo\bar\f.txt", PlatformID.Win32NT)] [DataRow("f.txt", @"C:\foo\bar", "", @"C:\foo\bar\f.txt", PlatformID.Win32NT)]
@ -98,20 +119,126 @@ namespace TemplatesTests
public void Tests(string template, string dirFullPath, string extension, string expected, PlatformID platformID) public void Tests(string template, string dirFullPath, string extension, string expected, PlatformID platformID)
{ {
if (Environment.OSVersion.Platform == platformID) if (Environment.OSVersion.Platform == platformID)
Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension) Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension, Replacements)
.GetFilePath(Replacements, extension) .GetFilePath(extension)
.PathWithoutPrefix .PathWithoutPrefix
.Should().Be(expected); .Should().Be(expected);
} }
[TestMethod]
[DataRow("<id> - <filedate[yy-MM-dd]>", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 23-01-28.m4b")]
[DataRow("<id> - <filedate [ yy-MM-dd ] >", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 23-01-28.m4b")]
[DataRow("<id> - <file date [yy-MM-dd] >", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 23-01-28.m4b")]
[DataRow("<id> - <file date[yy-MM-dd]>", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 23-01-28.m4b")]
[DataRow("<id> - <file date[]>", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 2023-01-28.m4b")]
[DataRow("<id> - <filedate[]>", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 2023-01-28.m4b")]
[DataRow("<id> - <filedate [ ] >", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 2023-01-28.m4b")]
[DataRow("<id> - <filedate>", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 2023-01-28.m4b")]
[DataRow("<id> - <filedate >", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 2023-01-28.m4b")]
[DataRow("<id> - <file date>", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 2023-01-28.m4b")]
[DataRow("<id> - <file date>", @"C:\foo\bar", "m4b", @"C:\foo\bar\asin - 2023-01-28.m4b")]
public void DateFormat_pattern(string template, string dirFullPath, string extension, string expected)
{
if (Environment.OSVersion.Platform is not PlatformID.Win32NT)
{
dirFullPath = dirFullPath.Replace("C:", "").Replace('\\', '/');
expected = expected.Replace("C:", "").Replace('\\', '/');
}
Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension, Replacements)
.GetFilePath(extension)
.PathWithoutPrefix
.Should().Be(expected);
}
[TestMethod]
[DataRow("<filedate[h]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\filedate[h].m4b")]
[DataRow("< filedate[yyyy]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\ filedate[yyyy].m4b")]
[DataRow("<filedate yyyy]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\filedate yyyy].m4b")]
[DataRow("<filedate [yyyy>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\filedate [yyyy.m4b")]
[DataRow("<filedate yyyy>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\filedate yyyy.m4b")]
[DataRow("<filedate[yyyy]", @"C:\foo\bar", ".m4b", @"C:\foo\bar\filedate[yyyy].m4b")]
[DataRow("<fil edate[yyyy]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\fil edate[yyyy].m4b")]
[DataRow("<filed ate[yyyy]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\filed ate[yyyy].m4b")]
public void DateFormat_invalid(string template, string dirFullPath, string extension, string expected)
{
if (Environment.OSVersion.Platform is not PlatformID.Win32NT)
{
dirFullPath = dirFullPath.Replace("C:", "").Replace('\\', '/');
expected = expected.Replace("C:", "").Replace('\\', '/').Replace('', '<').Replace('','>');
}
Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension, Replacements)
.GetFilePath(extension)
.PathWithoutPrefix
.Should().Be(expected);
}
[TestMethod]
[DataRow("<filedate[yy-MM-dd]> <date added[yy-MM-dd]> <pubdate[yy-MM]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\23-01-28 22-06-09 17-02.m4b")]
[DataRow("<filedate[yy-MM-dd]> <filedate[yy-MM-dd]> <filedate[yy-MM-dd]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\23-01-28 23-01-28 23-01-28.m4b")]
[DataRow("<file date [ yy-MM-dd ] > <filedate [ yy-MM-dd ] > <file date [ yy-MM-dd] >", @"C:\foo\bar", ".m4b", @"C:\foo\bar\23-01-28 23-01-28 23-01-28.m4b")]
public void DateFormat_multiple(string template, string dirFullPath, string extension, string expected)
{
if (Environment.OSVersion.Platform is not PlatformID.Win32NT)
{
dirFullPath = dirFullPath.Replace("C:", "").Replace('\\', '/');
expected = expected.Replace("C:", "").Replace('\\', '/');
}
Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension, Replacements)
.GetFilePath(extension)
.PathWithoutPrefix
.Should().Be(expected);
}
[TestMethod]
[DataRow("<id> - <pubdate[MM/dd/yy HH:mm]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\asin - 022717 0000.m4b", PlatformID.Win32NT)]
[DataRow("<id> - <pubdate[MM/dd/yy HH:mm]>", @"/foo/bar", ".m4b", @"/foo/bar/asin - 022717 00:00.m4b", PlatformID.Unix)]
[DataRow("<id> - <filedate[MM/dd/yy HH:mm]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\asin - 012823 0000.m4b", PlatformID.Win32NT)]
[DataRow("<id> - <filedate[MM/dd/yy HH:mm]>", @"/foo/bar", ".m4b", @"/foo/bar/asin - 012823 00:00.m4b", PlatformID.Unix)]
[DataRow("<id> - <date added[MM/dd/yy HH:mm]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\asin - 060922 0000.m4b", PlatformID.Win32NT)]
[DataRow("<id> - <date added[MM/dd/yy HH:mm]>", @"/foo/bar", ".m4b", @"/foo/bar/asin - 060922 00:00.m4b", PlatformID.Unix)]
public void DateFormat_illegal(string template, string dirFullPath, string extension, string expected, PlatformID platformID)
{
if (Environment.OSVersion.Platform == platformID)
{
Templates.File.HasWarnings(template).Should().BeTrue();
Templates.File.HasWarnings(Templates.File.Sanitize(template, Replacements)).Should().BeFalse();
Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension, Replacements)
.GetFilePath(extension)
.PathWithoutPrefix
.Should().Be(expected);
}
}
[TestMethod]
[DataRow("<filedate[yy-MM-dd]> <date added[yy-MM-dd]> <pubdate[yy-MM]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\23-01-28.m4b")]
public void DateFormat_null(string template, string dirFullPath, string extension, string expected)
{
if (Environment.OSVersion.Platform is not PlatformID.Win32NT)
{
dirFullPath = dirFullPath.Replace("C:", "").Replace('\\', '/');
expected = expected.Replace("C:", "").Replace('\\', '/');
}
Templates.getFileNamingTemplate(GetLibraryBookWithNullDates(), template, dirFullPath, extension, Replacements)
.GetFilePath(extension)
.PathWithoutPrefix
.Should().Be(expected);
}
[TestMethod] [TestMethod]
[DataRow(@"C:\a\b", @"C:\a\b\foobar.ext", PlatformID.Win32NT)] [DataRow(@"C:\a\b", @"C:\a\b\foobar.ext", PlatformID.Win32NT)]
[DataRow(@"/a/b", @"/a/b/foobar.ext", PlatformID.Unix)] [DataRow(@"/a/b", @"/a/b/foobar.ext", PlatformID.Unix)]
public void IfSeries_empty(string directory, string expected, PlatformID platformID) public void IfSeries_empty(string directory, string expected, PlatformID platformID)
{ {
if (Environment.OSVersion.Platform == platformID) if (Environment.OSVersion.Platform == platformID)
Templates.getFileNamingTemplate(GetLibraryBook(), "foo<if series-><-if series>bar", directory, "ext") Templates.getFileNamingTemplate(GetLibraryBook(), "foo<if series-><-if series>bar", directory, "ext", Replacements)
.GetFilePath(Replacements, ".ext") .GetFilePath(".ext")
.PathWithoutPrefix .PathWithoutPrefix
.Should().Be(expected); .Should().Be(expected);
} }
@ -122,8 +249,8 @@ namespace TemplatesTests
public void IfSeries_no_series(string directory, string expected, PlatformID platformID) public void IfSeries_no_series(string directory, string expected, PlatformID platformID)
{ {
if (Environment.OSVersion.Platform == platformID) if (Environment.OSVersion.Platform == platformID)
Templates.getFileNamingTemplate(GetLibraryBook(null), "foo<if series->-<series>-<id>-<-if series>bar", directory, "ext") Templates.getFileNamingTemplate(GetLibraryBook(null), "foo<if series->-<series>-<id>-<-if series>bar", directory, "ext", Replacements)
.GetFilePath(Replacements, ".ext") .GetFilePath(".ext")
.PathWithoutPrefix .PathWithoutPrefix
.Should().Be(expected); .Should().Be(expected);
} }
@ -134,8 +261,8 @@ namespace TemplatesTests
public void IfSeries_with_series(string directory, string expected, PlatformID platformID) public void IfSeries_with_series(string directory, string expected, PlatformID platformID)
{ {
if (Environment.OSVersion.Platform == platformID) if (Environment.OSVersion.Platform == platformID)
Templates.getFileNamingTemplate(GetLibraryBook(), "foo<if series->-<series>-<id>-<-if series>bar", directory, "ext") Templates.getFileNamingTemplate(GetLibraryBook(), "foo<if series->-<series>-<id>-<-if series>bar", directory, "ext", Replacements)
.GetFilePath(Replacements, ".ext") .GetFilePath(".ext")
.PathWithoutPrefix .PathWithoutPrefix
.Should().Be(expected); .Should().Be(expected);
} }