Make filename character replacement more xplat and allow replacing any char, not just illegal.

This commit is contained in:
Michael Bucari-Tovo 2022-12-15 15:50:48 -07:00
parent 80bcf60b5b
commit a0dd2ccad6
9 changed files with 1141 additions and 1133 deletions

View File

@ -5,7 +5,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AudibleApi" Version="7.0.0.5" /> <PackageReference Include="AudibleApi" Version="7.1.0.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -105,7 +105,7 @@ namespace FileManager
// Other illegal characters will be taken care of later. Must take care of slashes now so params can't introduce new folders. // 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. // Esp important for file templates.
return replacements.ReplaceInvalidFilenameChars(value.ToString()); return replacements.ReplaceFilenameChars(value.ToString());
} }
} }
} }

View File

@ -84,7 +84,7 @@ namespace FileManager
var pathNoPrefix = path.PathWithoutPrefix; var pathNoPrefix = path.PathWithoutPrefix;
pathNoPrefix = replacements.ReplaceInvalidPathChars(pathNoPrefix); pathNoPrefix = replacements.ReplacePathChars(pathNoPrefix);
pathNoPrefix = removeDoubleSlashes(pathNoPrefix); pathNoPrefix = removeDoubleSlashes(pathNoPrefix);
return pathNoPrefix; return pathNoPrefix;

View File

@ -69,7 +69,7 @@ namespace FileManager
= IsWindows = IsWindows
? new() ? new()
{ {
Replacements = new List<Replacement>() Replacements = new Replacement[]
{ {
Replacement.OtherInvalid("_"), Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash(""), Replacement.FilenameForwardSlash(""),
@ -87,7 +87,7 @@ namespace FileManager
} }
: new() : new()
{ {
Replacements = new List<Replacement>() Replacements = new Replacement[]
{ {
Replacement.OtherInvalid("_"), Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash(""), Replacement.FilenameForwardSlash(""),
@ -102,7 +102,7 @@ namespace FileManager
= IsWindows = IsWindows
? new() ? new()
{ {
Replacements = new List<Replacement>() Replacements = new Replacement[]
{ {
Replacement.OtherInvalid("_"), Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash("_"), Replacement.FilenameForwardSlash("_"),
@ -121,7 +121,7 @@ namespace FileManager
= IsWindows = IsWindows
? new () ? new ()
{ {
Replacements = new List<Replacement>() Replacements = new Replacement[]
{ {
Replacement.OtherInvalid("_"), Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash("_"), Replacement.FilenameForwardSlash("_"),
@ -133,7 +133,7 @@ namespace FileManager
} }
: new () : new ()
{ {
Replacements = new List<Replacement>() Replacements = new Replacement[]
{ {
Replacement.OtherInvalid("_"), Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash("_"), Replacement.FilenameForwardSlash("_"),
@ -147,11 +147,11 @@ namespace FileManager
private static bool IsWindows => Environment.OSVersion.Platform is PlatformID.Win32NT; private static bool IsWindows => Environment.OSVersion.Platform is PlatformID.Win32NT;
private static readonly char[] invalidPathChars = Path.GetInvalidFileNameChars().Except(new[] { private static readonly char[] invalidPathChars = Path.GetInvalidFileNameChars().Except(new[] {
'\\', '/' Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
}).ToArray(); }).ToArray();
private static readonly char[] invalidSlashes = Path.GetInvalidFileNameChars().Intersect(new[] { private static readonly char[] invalidSlashes = Path.GetInvalidFileNameChars().Intersect(new[] {
'\\', '/' Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
}).ToArray(); }).ToArray();
public IReadOnlyList<Replacement> Replacements { get; init; } public IReadOnlyList<Replacement> Replacements { get; init; }
@ -214,7 +214,7 @@ namespace FileManager
public static bool ContainsInvalidFilenameChar(string path) public static bool ContainsInvalidFilenameChar(string path)
=> ContainsInvalidPathChar(path) || path.Any(c => invalidSlashes.Contains(c)); => ContainsInvalidPathChar(path) || path.Any(c => invalidSlashes.Contains(c));
public string ReplaceInvalidFilenameChars(string fileName) public string ReplaceFilenameChars(string fileName)
{ {
if (string.IsNullOrEmpty(fileName)) return string.Empty; if (string.IsNullOrEmpty(fileName)) return string.Empty;
var builder = new System.Text.StringBuilder(); var builder = new System.Text.StringBuilder();
@ -222,7 +222,9 @@ namespace FileManager
{ {
var c = fileName[i]; var c = fileName[i];
if (invalidPathChars.Contains(c) || invalidSlashes.Contains(c)) if (invalidPathChars.Contains(c)
|| invalidSlashes.Contains(c)
|| Replacements.Any(r => r.CharacterToReplace == c) /* Replace any other legal characters that they user wants. */ )
{ {
char preceding = i > 0 ? fileName[i - 1] : default; char preceding = i > 0 ? fileName[i - 1] : default;
char succeeding = i < fileName.Length - 1 ? fileName[i + 1] : default; char succeeding = i < fileName.Length - 1 ? fileName[i + 1] : default;
@ -230,29 +232,42 @@ namespace FileManager
} }
else else
builder.Append(c); builder.Append(c);
} }
return builder.ToString(); return builder.ToString();
} }
public string ReplaceInvalidPathChars(string pathStr) public string ReplacePathChars(string pathStr)
{ {
if (string.IsNullOrEmpty(pathStr)) return string.Empty; if (string.IsNullOrEmpty(pathStr)) return string.Empty;
// replace all colons except within the first 2 chars
var builder = new System.Text.StringBuilder(); var builder = new System.Text.StringBuilder();
for (var i = 0; i < pathStr.Length; i++) for (var i = 0; i < pathStr.Length; i++)
{ {
var c = pathStr[i]; var c = pathStr[i];
if (!invalidPathChars.Contains(c) || (c == ':' && i == 1 && Path.IsPathRooted(pathStr))) if (
builder.Append(c); (
else invalidPathChars.Contains(c)
|| ( // Replace any other legal characters that they user wants.
c != Path.DirectorySeparatorChar
&& c != Path.AltDirectorySeparatorChar
&& Replacements.Any(r => r.CharacterToReplace == c)
)
)
&& !( // replace all colons except drive letter designator on Windows
c == ':'
&& i == 1
&& Path.IsPathRooted(pathStr)
&& IsWindows
)
)
{ {
char preceding = i > 0 ? pathStr[i - 1] : default; char preceding = i > 0 ? pathStr[i - 1] : default;
char succeeding = i < pathStr.Length - 1 ? pathStr[i + 1] : default; char succeeding = i < pathStr.Length - 1 ? pathStr[i + 1] : default;
builder.Append(GetPathCharReplacement(c, preceding, succeeding)); builder.Append(GetPathCharReplacement(c, preceding, succeeding));
} }
else
builder.Append(c);
} }
return builder.ToString(); return builder.ToString();
} }
@ -274,28 +289,19 @@ namespace FileManager
//Ensure that the first 6 replacements are for the expected chars and that all replacement strings are valid. //Ensure that the first 6 replacements are for the expected chars and that all replacement strings are valid.
//If not, reset to default. //If not, reset to default.
var default0 = Replacement.OtherInvalid(""); for (int i = 0; i < Replacement.FIXED_COUNT; i++)
var default1 = Replacement.FilenameForwardSlash(""); {
var default2 = Replacement.FilenameBackSlash(""); if (dict.Count < Replacement.FIXED_COUNT
var default3 = Replacement.OpenQuote(""); || dict[i].CharacterToReplace != ReplacementCharacters.Barebones.Replacements[i].CharacterToReplace
var default4 = Replacement.CloseQuote(""); || dict[i].Description != ReplacementCharacters.Barebones.Replacements[i].Description)
var default5 = Replacement.OtherQuote("");
if (dict.Count < Replacement.FIXED_COUNT ||
dict[0].CharacterToReplace != default0.CharacterToReplace || dict[0].Description != default0.Description ||
dict[1].CharacterToReplace != default1.CharacterToReplace || dict[1].Description != default1.Description ||
dict[2].CharacterToReplace != default2.CharacterToReplace || dict[2].Description != default2.Description ||
dict[3].CharacterToReplace != default3.CharacterToReplace || dict[3].Description != default3.Description ||
dict[4].CharacterToReplace != default4.CharacterToReplace || dict[4].Description != default4.Description ||
dict[5].CharacterToReplace != default5.CharacterToReplace || dict[5].Description != default5.Description ||
dict.Any(r => ReplacementCharacters.ContainsInvalidFilenameChar(r.ReplacementString))
)
{ {
dict = ReplacementCharacters.Default.Replacements; dict = ReplacementCharacters.Default.Replacements;
break;
} }
//First FIXED_COUNT are mandatory //First FIXED_COUNT are mandatory
for (int i = 0; i < Replacement.FIXED_COUNT; i++)
dict[i].Mandatory = true; dict[i].Mandatory = true;
}
return new ReplacementCharacters { Replacements = dict }; return new ReplacementCharacters { Replacements = dict };
} }
@ -305,7 +311,7 @@ namespace FileManager
ReplacementCharacters replacements = (ReplacementCharacters)value; ReplacementCharacters replacements = (ReplacementCharacters)value;
var propertyNames = replacements.Replacements var propertyNames = replacements.Replacements
.Select(c => JObject.FromObject(c)).ToList(); .Select(JObject.FromObject).ToList();
var prop = new JProperty(nameof(Replacement), new JArray(propertyNames)); var prop = new JProperty(nameof(Replacement), new JArray(propertyNames));

View File

@ -158,7 +158,7 @@ namespace LibationAvalonia.Dialogs
set set
{ {
if (value?.Length != 1 || !ReplacementCharacters.ContainsInvalidFilenameChar(value)) if (value?.Length != 1)
this.RaisePropertyChanged(nameof(CharacterToReplace)); this.RaisePropertyChanged(nameof(CharacterToReplace));
else else
{ {

View File

@ -4,6 +4,7 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using Dinah.Core; using Dinah.Core;
using Dinah.Core.Logging; using Dinah.Core.Logging;
using FileManager;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Serilog; using Serilog;
using Serilog.Events; using Serilog.Events;
@ -21,6 +22,7 @@ namespace LibationFileManager
.Build(); .Build();
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration) .ReadFrom.Configuration(configuration)
.Destructure.ByTransforming<LongPath>(lp => lp.Path)
.CreateLogger(); .CreateLogger();
} }

View File

@ -276,7 +276,7 @@ namespace LibationFileManager
#region templates: custom file naming #region templates: custom file naming
[Description("Edit how illegal filename characters are replaced")] [Description("Edit how filename characters are replaced")]
public ReplacementCharacters ReplacementCharacters public ReplacementCharacters ReplacementCharacters
{ {
get => persistentDictionary.GetNonString<ReplacementCharacters>(nameof(ReplacementCharacters)); get => persistentDictionary.GetNonString<ReplacementCharacters>(nameof(ReplacementCharacters));

View File

@ -535,7 +535,7 @@
this.editCharreplacementBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.editCharreplacementBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.editCharreplacementBtn.Location = new System.Drawing.Point(5, 158); this.editCharreplacementBtn.Location = new System.Drawing.Point(5, 158);
this.editCharreplacementBtn.Name = "editCharreplacementBtn"; this.editCharreplacementBtn.Name = "editCharreplacementBtn";
this.editCharreplacementBtn.Size = new System.Drawing.Size(387, 23); this.editCharreplacementBtn.Size = new System.Drawing.Size(281, 23);
this.editCharreplacementBtn.TabIndex = 8; this.editCharreplacementBtn.TabIndex = 8;
this.editCharreplacementBtn.Text = "[edit char replacement desc]"; this.editCharreplacementBtn.Text = "[edit char replacement desc]";
this.editCharreplacementBtn.UseVisualStyleBackColor = true; this.editCharreplacementBtn.UseVisualStyleBackColor = true;

View File

@ -83,17 +83,17 @@ namespace FileUtilityTests
[TestMethod] [TestMethod]
// empty replacement // empty replacement
[DataRow("http://test.com/a/b/c", "httptest.comabc")] [DataRow("http://test.com/a/b/c", "httptest.comabc")]
public void DefaultReplacementTest(string inStr, string outStr) => Default.ReplaceInvalidFilenameChars(inStr).Should().Be(outStr); public void DefaultReplacementTest(string inStr, string outStr) => Default.ReplaceFilenameChars(inStr).Should().Be(outStr);
[TestMethod] [TestMethod]
// empty replacement // empty replacement
[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 LoFiDefaultReplacementTest(string inStr, string outStr) => LoFiDefault.ReplaceInvalidFilenameChars(inStr).Should().Be(outStr); public void LoFiDefaultReplacementTest(string inStr, string outStr) => LoFiDefault.ReplaceFilenameChars(inStr).Should().Be(outStr);
[TestMethod] [TestMethod]
// empty replacement // empty replacement
[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 BarebonesDefaultReplacementTest(string inStr, string outStr) => Barebones.ReplaceInvalidFilenameChars(inStr).Should().Be(outStr); public void BarebonesDefaultReplacementTest(string inStr, string outStr) => Barebones.ReplaceFilenameChars(inStr).Should().Be(outStr);
} }
[TestClass] [TestClass]