From 2eda69610c5c5966e5d79f426912bd1d1200e2fa Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:38:31 -0500 Subject: [PATCH 1/4] Fix Fix custom parameters not working when more than one custom parameter is used --- TwitchDownloaderCore/Tools/FilenameService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TwitchDownloaderCore/Tools/FilenameService.cs b/TwitchDownloaderCore/Tools/FilenameService.cs index 408116a1..7d03242d 100644 --- a/TwitchDownloaderCore/Tools/FilenameService.cs +++ b/TwitchDownloaderCore/Tools/FilenameService.cs @@ -40,25 +40,25 @@ public static string GetFilename(string template, string title, string id, DateT if (template.Contains("{date_custom=")) { - var dateRegex = new Regex("{date_custom=\"(.*)\"}"); + var dateRegex = new Regex("{date_custom=\"(.*?)\"}"); ReplaceCustomWithFormattable(stringBuilder, dateRegex, date); } if (template.Contains("{crop_start_custom=")) { - var cropStartRegex = new Regex("{crop_start_custom=\"(.*)\"}"); + var cropStartRegex = new Regex("{crop_start_custom=\"(.*?)\"}"); ReplaceCustomWithFormattable(stringBuilder, cropStartRegex, cropStart); } if (template.Contains("{crop_end_custom=")) { - var cropEndRegex = new Regex("{crop_end_custom=\"(.*)\"}"); + var cropEndRegex = new Regex("{crop_end_custom=\"(.*?)\"}"); ReplaceCustomWithFormattable(stringBuilder, cropEndRegex, cropEnd); } if (template.Contains("{length_custom=")) { - var lengthRegex = new Regex("{length_custom=\"(.*)\"}"); + var lengthRegex = new Regex("{length_custom=\"(.*?)\"}"); ReplaceCustomWithFormattable(stringBuilder, lengthRegex, videoLength); } From d402929b216e7ce1e861ae05f812913bd5210ab1 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Sun, 26 Nov 2023 00:39:23 -0500 Subject: [PATCH 2/4] Change date parameter because it sucks --- TwitchDownloaderCore/Tools/FilenameService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TwitchDownloaderCore/Tools/FilenameService.cs b/TwitchDownloaderCore/Tools/FilenameService.cs index 7d03242d..f770d3f0 100644 --- a/TwitchDownloaderCore/Tools/FilenameService.cs +++ b/TwitchDownloaderCore/Tools/FilenameService.cs @@ -30,7 +30,7 @@ public static string GetFilename(string template, string title, string id, DateT .Replace("{title}", RemoveInvalidFilenameChars(title)) .Replace("{id}", id) .Replace("{channel}", RemoveInvalidFilenameChars(channel)) - .Replace("{date}", date.ToString("Mdyy")) + .Replace("{date}", date.ToString("M-d-yy")) .Replace("{random_string}", Path.GetRandomFileName().Replace(".", "")) .Replace("{crop_start}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", cropStart)) .Replace("{crop_end}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", cropEnd)) From cbda4495770e5925f8c4552a7ef2e1a466c8b6fb Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Sun, 26 Nov 2023 01:05:50 -0500 Subject: [PATCH 3/4] Minor refactor --- TwitchDownloaderCore/Tools/FilenameService.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/TwitchDownloaderCore/Tools/FilenameService.cs b/TwitchDownloaderCore/Tools/FilenameService.cs index f770d3f0..bc89e6a4 100644 --- a/TwitchDownloaderCore/Tools/FilenameService.cs +++ b/TwitchDownloaderCore/Tools/FilenameService.cs @@ -8,20 +8,6 @@ namespace TwitchDownloaderCore.Tools { public static class FilenameService { - private static string[] GetTemplateSubfolders(ref string fullPath) - { - var returnString = fullPath.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); - fullPath = returnString[^1]; - Array.Resize(ref returnString, returnString.Length - 1); - - for (var i = 0; i < returnString.Length; i++) - { - returnString[i] = RemoveInvalidFilenameChars(returnString[i]); - } - - return returnString; - } - public static string GetFilename(string template, string title, string id, DateTime date, string channel, TimeSpan cropStart, TimeSpan cropEnd, string viewCount, string game) { var videoLength = cropEnd - cropStart; @@ -31,7 +17,7 @@ public static string GetFilename(string template, string title, string id, DateT .Replace("{id}", id) .Replace("{channel}", RemoveInvalidFilenameChars(channel)) .Replace("{date}", date.ToString("M-d-yy")) - .Replace("{random_string}", Path.GetRandomFileName().Replace(".", "")) + .Replace("{random_string}", Path.GetRandomFileName().Remove(8)) // Remove the period .Replace("{crop_start}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", cropStart)) .Replace("{crop_end}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", cropEnd)) .Replace("{length}", TimeSpanHFormat.ReusableInstance.Format(@"HH\-mm\-ss", videoLength)) @@ -83,6 +69,20 @@ private static void ReplaceCustomWithFormattable(StringBuilder sb, Regex regex, } while (true); } + private static string[] GetTemplateSubfolders(ref string fullPath) + { + var returnString = fullPath.Split(new[] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); + fullPath = returnString[^1]; + Array.Resize(ref returnString, returnString.Length - 1); + + for (var i = 0; i < returnString.Length; i++) + { + returnString[i] = RemoveInvalidFilenameChars(returnString[i]); + } + + return returnString; + } + private static readonly char[] FilenameInvalidChars = Path.GetInvalidFileNameChars(); private static string RemoveInvalidFilenameChars(string filename) => filename.ReplaceAny(FilenameInvalidChars, '_'); From 0fc556d6da73f797dd5d165f87eddc5cce2ff7d0 Mon Sep 17 00:00:00 2001 From: ScrubN <72096833+ScrubN@users.noreply.github.com> Date: Sun, 26 Nov 2023 02:29:15 -0500 Subject: [PATCH 4/4] Add basic unit tests --- TwitchDownloaderTests/FilenameServiceTests.cs | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 TwitchDownloaderTests/FilenameServiceTests.cs diff --git a/TwitchDownloaderTests/FilenameServiceTests.cs b/TwitchDownloaderTests/FilenameServiceTests.cs new file mode 100644 index 00000000..874f34ff --- /dev/null +++ b/TwitchDownloaderTests/FilenameServiceTests.cs @@ -0,0 +1,155 @@ +using TwitchDownloaderCore.Extensions; +using TwitchDownloaderCore.Tools; + +namespace TwitchDownloaderTests +{ + public class FilenameServiceTests + { + private static (string title, string id, DateTime date, string channel, TimeSpan cropStart, TimeSpan cropEnd, string viewCount, string game) GetExampleInfo() => + ("A Title", "abc123", new DateTime(1984, 11, 1, 9, 43, 21), "streamer8", new TimeSpan(0, 1, 2, 3, 4), new TimeSpan(0, 5, 6, 7, 8), "123456789", "A Game"); + + [Theory] + [InlineData("{title}", "A Title")] + [InlineData("{id}", "abc123")] + [InlineData("{channel}", "streamer8")] + [InlineData("{date}", "11-1-84")] + [InlineData("{crop_start}", "01-02-03")] + [InlineData("{crop_end}", "05-06-07")] + [InlineData("{length}", "04-04-04")] + [InlineData("{views}", "123456789")] + [InlineData("{game}", "A Game")] + [InlineData("{date_custom=\"s\"}", "1984-11-01T09_43_21")] + [InlineData("{crop_start_custom=\"hh\\-mm\\-ss\"}", "01-02-03")] + [InlineData("{crop_end_custom=\"hh\\-mm\\-ss\"}", "05-06-07")] + [InlineData("{length_custom=\"hh\\-mm\\-ss\"}", "04-04-04")] + public void CorrectlyGeneratesIndividualTemplates(string template, string expected) + { + var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo(); + + var result = FilenameService.GetFilename(template, title, id, date, channel, cropStart, cropEnd, viewCount, game); + + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("[{date_custom=\"M-dd-yy\"}] {channel} - {title}", "[11-01-84] streamer8 - A Title")] + [InlineData("[{channel}] [{date_custom=\"M-dd-yy\"}] [{game}] {title} ({id}) - {views} views", "[streamer8] [11-01-84] [A Game] A Title (abc123) - 123456789 views")] + [InlineData("{title} by {channel} playing {game} on {date_custom=\"M dd, yyyy\"} for {length_custom=\"h'h 'm'm 's's'\"} with {views} views", "A Title by streamer8 playing A Game on 11 01, 1984 for 4h 4m 4s with 123456789 views")] + public void CorrectlyGeneratesLargeTemplates(string template, string expected) + { + var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo(); + + var result = FilenameService.GetFilename(template, title, id, date, channel, cropStart, cropEnd, viewCount, game); + + Assert.Equal(expected, result); + } + + [Fact] + public void CorrectlyInterpretsMultipleCustomParameters() + { + const string TEMPLATE = "{date_custom=\"yyyy\"} {date_custom=\"MM\"} {date_custom=\"dd\"} {crop_start_custom=\"hh\\-mm\\-ss\"} {crop_end_custom=\"hh\\-mm\\-ss\"} {length_custom=\"hh\\-mm\\-ss\"}"; + const string EXPECTED = "1984 11 01 01-02-03 05-06-07 04-04-04"; + var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo(); + + var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game); + + Assert.Equal(EXPECTED, result); + } + + [Fact] + public void CorrectlyGeneratesSubFolders_WithForwardSlash() + { + const string TEMPLATE = "{channel}/{date_custom=\"yyyy\"}/{date_custom=\"MM\"}/{date_custom=\"dd\"}/{title}.mp4"; + var expected = Path.Combine("streamer8", "1984", "11", "01", "A Title.mp4"); + var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo(); + + var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game); + + Assert.Equal(expected, result); + } + + [Fact] + public void CorrectlyGeneratesSubFolders_WithBackSlash() + { + const string TEMPLATE = "{channel}\\{date_custom=\"yyyy\"}\\{date_custom=\"MM\"}\\{date_custom=\"dd\"}\\{title}"; + var expected = Path.Combine("streamer8", "1984", "11", "01", "A Title"); + var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo(); + + var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game); + + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("{title}")] + [InlineData("{id}")] + [InlineData("{channel}")] + [InlineData("{views}")] + [InlineData("{game}")] + public void CorrectlyReplacesInvalidCharactersForNonCustomTemplates(string template) + { + const char EXPECTED = '_'; + var invalidChars = new string(Path.GetInvalidFileNameChars()); + + var result = FilenameService.GetFilename(template, invalidChars, invalidChars, default, invalidChars, default, default, invalidChars, invalidChars); + + Assert.All(result, c => Assert.Equal(EXPECTED, c)); + } + + [Theory] + [InlineData("{date_custom=\"'")] + [InlineData("{crop_start_custom=\"'")] + [InlineData("{crop_end_custom=\"'")] + [InlineData("{length_custom=\"'")] + public void CorrectlyReplacesInvalidCharactersForCustomTemplates(string templateStart) + { + const char EXPECTED = '_'; + var invalidChars = new string(Path.GetInvalidFileNameChars()); + var template = string.Concat( + templateStart, + invalidChars.ReplaceAny("\r\n", EXPECTED), // newline chars are not supported by the custom parameters. This will not change. + "'\"}"); + + var result = FilenameService.GetFilename(template, invalidChars, invalidChars, default, invalidChars, default, default, invalidChars, invalidChars); + + Assert.All(result, c => Assert.Equal(EXPECTED, c)); + } + + [Fact] + public void CorrectlyReplacesInvalidCharactersForSubFolders() + { + var invalidChars = new string(Path.GetInvalidPathChars()); + var template = invalidChars + "\\{title}"; + var expected = Path.Combine(new string('_', invalidChars.Length), "A Title"); + var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo(); + + var result = FilenameService.GetFilename(template, title, id, date, channel, cropStart, cropEnd, viewCount, game); + + Assert.Equal(expected, result); + } + + [Fact] + public void RandomStringIsRandom() + { + const string TEMPLATE = "{random_string}"; + var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo(); + + var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game); + var result2 = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game); + + Assert.NotEqual(result, result2); + } + + [Fact] + public void DoesNotInterpretBogusTemplateParameter() + { + const string TEMPLATE = "{foobar}"; + const string EXPECTED = "{foobar}"; + var (title, id, date, channel, cropStart, cropEnd, viewCount, game) = GetExampleInfo(); + + var result = FilenameService.GetFilename(TEMPLATE, title, id, date, channel, cropStart, cropEnd, viewCount, game); + + Assert.Equal(EXPECTED, result); + } + } +} \ No newline at end of file