Bug fix: slashes in template values (eg: title) breaks file management #145
This commit is contained in:
parent
cae8ca7ef3
commit
46b120ee41
@ -3,7 +3,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Version>6.3.3.1</Version>
|
||||
<Version>6.3.4.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@ -45,8 +45,20 @@ namespace FileManager
|
||||
.Replace(">", "");
|
||||
|
||||
private string formatValue(object value)
|
||||
=> ParameterMaxSize.HasValue && ParameterMaxSize.Value > 0
|
||||
? value?.ToString().Truncate(ParameterMaxSize.Value)
|
||||
: value?.ToString();
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,8 +16,6 @@ namespace LibationFileManager
|
||||
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>";
|
||||
// actual possible to se?
|
||||
public const string WARNING_NO_CHAPTER_TAGS = "Should include chapter tags in template used for naming files which are split by chapter. Eg: <ch title>";
|
||||
|
||||
public static Templates Folder { get; } = new FolderTemplate();
|
||||
public static Templates File { get; } = new FileTemplate();
|
||||
@ -26,6 +24,7 @@ namespace LibationFileManager
|
||||
public abstract string Name { get; }
|
||||
public abstract string Description { get; }
|
||||
public abstract string DefaultTemplate { get; }
|
||||
protected abstract bool IsChapterized { get; }
|
||||
|
||||
public abstract IEnumerable<string> GetErrors(string template);
|
||||
public bool IsValid(string template) => !GetErrors(template).Any();
|
||||
@ -33,7 +32,17 @@ namespace LibationFileManager
|
||||
public abstract IEnumerable<string> GetWarnings(string template);
|
||||
public bool HasWarnings(string template) => GetWarnings(template).Any();
|
||||
|
||||
public abstract int TagCount(string template);
|
||||
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 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);
|
||||
|
||||
public static bool ContainsChapterOnlyTags(string template)
|
||||
=> TemplateTags.GetAll()
|
||||
@ -59,7 +68,7 @@ namespace LibationFileManager
|
||||
return Valid;
|
||||
}
|
||||
|
||||
protected IEnumerable<string> getWarnings(string template, bool isChapter)
|
||||
protected IEnumerable<string> getWarnings(string template)
|
||||
{
|
||||
var warnings = GetErrors(template).ToList();
|
||||
if (template is null)
|
||||
@ -73,28 +82,18 @@ namespace LibationFileManager
|
||||
if (TagCount(template) == 0)
|
||||
warnings.Add(WARNING_NO_TAGS);
|
||||
|
||||
var containsChapterOnlyTags = ContainsChapterOnlyTags(template);
|
||||
if (isChapter && !containsChapterOnlyTags)
|
||||
warnings.Add(WARNING_NO_CHAPTER_TAGS);
|
||||
if (!isChapter && containsChapterOnlyTags)
|
||||
if (!IsChapterized && ContainsChapterOnlyTags(template))
|
||||
warnings.Add(WARNING_HAS_CHAPTER_TAGS);
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
protected static int tagCount(string template, Func<TemplateTags, bool> func)
|
||||
=> TemplateTags.GetAll()
|
||||
.Where(func)
|
||||
// for <id><id> == 1, use:
|
||||
// .Count(t => template.Contains($"<{t.TagName}>"))
|
||||
// .Sum() impl: <id><id> == 2
|
||||
.Sum(t => template.Split($"<{t.TagName}>").Length - 1);
|
||||
|
||||
private 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;
|
||||
|
||||
public override IEnumerable<string> GetErrors(string template)
|
||||
{
|
||||
@ -109,9 +108,7 @@ namespace LibationFileManager
|
||||
return Valid;
|
||||
}
|
||||
|
||||
public override IEnumerable<string> GetWarnings(string template) => getWarnings(template, false);
|
||||
|
||||
public override int TagCount(string template) => tagCount(template, t => !t.IsChapterOnly);
|
||||
public override IEnumerable<string> GetWarnings(string template) => getWarnings(template);
|
||||
}
|
||||
|
||||
private class FileTemplate : Templates
|
||||
@ -119,12 +116,11 @@ namespace LibationFileManager
|
||||
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;
|
||||
|
||||
public override IEnumerable<string> GetErrors(string template) => getFileErrors(template);
|
||||
|
||||
public override IEnumerable<string> GetWarnings(string template) => getWarnings(template, false);
|
||||
|
||||
public override int TagCount(string template) => tagCount(template, t => !t.IsChapterOnly);
|
||||
public override IEnumerable<string> GetWarnings(string template) => getWarnings(template);
|
||||
}
|
||||
|
||||
private class ChapterFileTemplate : Templates
|
||||
@ -132,26 +128,22 @@ namespace LibationFileManager
|
||||
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;
|
||||
|
||||
public override IEnumerable<string> GetErrors(string template) => getFileErrors(template);
|
||||
|
||||
public override IEnumerable<string> GetWarnings(string template)
|
||||
{
|
||||
var warnings = getWarnings(template, true).ToList();
|
||||
var warnings = getWarnings(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.Remove(WARNING_NO_CHAPTER_TAGS);
|
||||
warnings.Add(WARNING_NO_CHAPTER_NUMBER_TAG);
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
public override int TagCount(string template) => tagCount(template, t => true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,9 @@
|
||||
this.templateLbl = new System.Windows.Forms.Label();
|
||||
this.resetToDefaultBtn = new System.Windows.Forms.Button();
|
||||
this.outputTb = new System.Windows.Forms.TextBox();
|
||||
this.listView1 = new System.Windows.Forms.ListView();
|
||||
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
|
||||
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// saveBtn
|
||||
@ -93,12 +96,41 @@
|
||||
//
|
||||
// outputTb
|
||||
//
|
||||
this.outputTb.Location = new System.Drawing.Point(12, 153);
|
||||
this.outputTb.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.outputTb.Location = new System.Drawing.Point(346, 56);
|
||||
this.outputTb.Multiline = true;
|
||||
this.outputTb.Name = "outputTb";
|
||||
this.outputTb.ReadOnly = true;
|
||||
this.outputTb.Size = new System.Drawing.Size(759, 205);
|
||||
this.outputTb.TabIndex = 100;
|
||||
this.outputTb.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.outputTb.Size = new System.Drawing.Size(574, 434);
|
||||
this.outputTb.TabIndex = 4;
|
||||
//
|
||||
// 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, 434);
|
||||
this.listView1.TabIndex = 100;
|
||||
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;
|
||||
//
|
||||
// EditTemplateDialog
|
||||
//
|
||||
@ -107,6 +139,7 @@
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.CancelButton = this.cancelBtn;
|
||||
this.ClientSize = new System.Drawing.Size(933, 539);
|
||||
this.Controls.Add(this.listView1);
|
||||
this.Controls.Add(this.outputTb);
|
||||
this.Controls.Add(this.resetToDefaultBtn);
|
||||
this.Controls.Add(this.templateLbl);
|
||||
@ -131,5 +164,8 @@
|
||||
private System.Windows.Forms.Label templateLbl;
|
||||
private System.Windows.Forms.Button resetToDefaultBtn;
|
||||
private System.Windows.Forms.TextBox outputTb;
|
||||
private System.Windows.Forms.ListView listView1;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader1;
|
||||
private System.Windows.Forms.ColumnHeader columnHeader2;
|
||||
}
|
||||
}
|
||||
@ -39,16 +39,32 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
this.templateLbl.Text = template.Description;
|
||||
this.templateTb.Text = 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) => templateTb.Text = template.DefaultTemplate;
|
||||
|
||||
private void templateTb_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
var t = templateTb.Text;
|
||||
|
||||
var warnings
|
||||
= !template.HasWarnings(t)
|
||||
? ""
|
||||
: "Warnings:\r\n" +
|
||||
template
|
||||
.GetWarnings(t)
|
||||
.Select(err => $"- {err}")
|
||||
.Aggregate((a, b) => $"{a}\r\n{b}");
|
||||
|
||||
|
||||
var books = config.Books;
|
||||
var folderTemplate = template == Templates.Folder ? templateTb.Text : config.FolderTemplate;
|
||||
var folderTemplate = template == Templates.Folder ? t : config.FolderTemplate;
|
||||
folderTemplate = folderTemplate.Trim().Trim(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Trim();
|
||||
var fileTemplate = template == Templates.Folder ? config.FileTemplate : templateTb.Text;
|
||||
var fileTemplate = template == Templates.Folder ? config.FileTemplate : t;
|
||||
fileTemplate = fileTemplate.Trim();
|
||||
var ext = config.DecryptToLossy ? "mp3" : "m4b";
|
||||
|
||||
@ -56,13 +72,23 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
// this logic should be external
|
||||
path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
|
||||
var sing = $"{Path.DirectorySeparatorChar}";
|
||||
var dbl = $"{Path.DirectorySeparatorChar}{Path.DirectorySeparatorChar}";
|
||||
while (path.Contains(dbl))
|
||||
path = path.Replace(dbl, $"{Path.DirectorySeparatorChar}");
|
||||
path = path.Replace(dbl, sing);
|
||||
|
||||
// once path is finalized
|
||||
const char ZERO_WIDTH_SPACE = '\u200B';
|
||||
path = path.Replace(sing, $"{ZERO_WIDTH_SPACE}{sing}");
|
||||
// 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
|
||||
|
||||
var book = new DataLayer.Book(
|
||||
new DataLayer.AudibleProductId("123456789"),
|
||||
"A Study in Scarlet",
|
||||
"A Study in Scarlet: A Sherlock Holmes Novel",
|
||||
"Fake description",
|
||||
1234,
|
||||
DataLayer.ContentType.Product,
|
||||
@ -78,6 +104,9 @@ namespace LibationWinForms.Dialogs
|
||||
var libraryBook = new DataLayer.LibraryBook(book, DateTime.Now, "my account");
|
||||
|
||||
outputTb.Text = @$"
|
||||
|
||||
Example:
|
||||
|
||||
{books}
|
||||
{folderTemplate}
|
||||
{fileTemplate}
|
||||
@ -89,14 +118,21 @@ namespace LibationWinForms.Dialogs
|
||||
{book.AuthorNames}
|
||||
{book.NarratorNames}
|
||||
series: {"Sherlock Holmes"}
|
||||
";
|
||||
|
||||
{warnings}
|
||||
|
||||
".Trim();
|
||||
}
|
||||
|
||||
private void saveBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (!template.IsValid(templateTb.Text))
|
||||
{
|
||||
MessageBox.Show("This template text is not valid.", "Invalid", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
var errors = template
|
||||
.GetErrors(templateTb.Text)
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
@ -104,5 +104,13 @@ namespace FileTemplateTests
|
||||
|
||||
return fileTemplate.GetFilePath();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void remove_slashes()
|
||||
{
|
||||
var fileTemplate = new FileTemplate(@"\foo\<title>.txt");
|
||||
fileTemplate.AddParameterReplacement("title", @"s\l/a\s/h\e/s");
|
||||
fileTemplate.GetFilePath().Should().Be(@"\foo\slashes.txt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user