Provide NTFS default characters for non-windows users (#1258)

This commit is contained in:
Michael Bucari-Tovo 2025-07-29 15:20:52 -06:00
parent 663f70b8bf
commit 7024bbf823
10 changed files with 98 additions and 96 deletions

View File

@ -56,7 +56,7 @@ namespace FileManager
{
ArgumentValidator.EnsureNotNull(name, nameof(name));
name = ReplacementCharacters.Barebones.ReplaceFilenameChars(name);
name = ReplacementCharacters.Barebones(true).ReplaceFilenameChars(name);
return Task.Run(() => AddFileInternal(name, contents.Span, comment));
}

View File

@ -74,12 +74,14 @@ namespace FileManager
}
public override int GetHashCode() => Replacements.GetHashCode();
public static readonly ReplacementCharacters Default
= IsWindows
? new()
{
Replacements = new Replacement[]
{
public static ReplacementCharacters Default(bool ntfs) => ntfs ? HiFi_NTFS : HiFi_Other;
public static ReplacementCharacters LoFiDefault(bool ntfs) => ntfs ? LoFi_NTFS : LoFi_Other;
public static ReplacementCharacters Barebones(bool ntfs) => ntfs ? BareBones_NTFS : BareBones_Other;
#region Defaults
private static readonly ReplacementCharacters HiFi_NTFS = new()
{
Replacements = [
Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash(""),
Replacement.FilenameBackSlash(""),
@ -91,28 +93,23 @@ namespace FileManager
Replacement.Colon("_"),
Replacement.Asterisk("✱"),
Replacement.QuestionMark(""),
Replacement.Pipe("⏐"),
}
}
: new()
{
Replacements = new Replacement[]
{
Replacement.Pipe("⏐")]
};
private static readonly ReplacementCharacters HiFi_Other = new()
{
Replacements = [
Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash(""),
Replacement.FilenameBackSlash("\\"),
Replacement.OpenQuote("“"),
Replacement.CloseQuote("”"),
Replacement.OtherQuote("\"")
}
};
Replacement.OtherQuote("\"")]
};
public static readonly ReplacementCharacters LoFiDefault
= IsWindows
? new()
{
Replacements = new Replacement[]
{
private static readonly ReplacementCharacters LoFi_NTFS = new()
{
Replacements = [
Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash("_"),
Replacement.FilenameBackSlash("_"),
@ -121,50 +118,44 @@ namespace FileManager
Replacement.OtherQuote("'"),
Replacement.OpenAngleBracket("{"),
Replacement.CloseAngleBracket("}"),
Replacement.Colon("-"),
}
}
: new ()
{
Replacements = new Replacement[]
{
Replacement.Colon("-")]
};
private static readonly ReplacementCharacters LoFi_Other = new()
{
Replacements = [
Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash("_"),
Replacement.FilenameBackSlash("\\"),
Replacement.OpenQuote("\""),
Replacement.CloseQuote("\""),
Replacement.OtherQuote("\"")
}
};
Replacement.OtherQuote("\"")]
};
public static readonly ReplacementCharacters Barebones
= IsWindows
? new ()
{
Replacements = new Replacement[]
{
private static readonly ReplacementCharacters BareBones_NTFS = new()
{
Replacements = [
Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash("_"),
Replacement.FilenameBackSlash("_"),
Replacement.OpenQuote("_"),
Replacement.CloseQuote("_"),
Replacement.OtherQuote("_")
}
}
: new ()
{
Replacements = new Replacement[]
{
Replacement.OtherQuote("_")]
};
private static readonly ReplacementCharacters BareBones_Other = new()
{
Replacements = [
Replacement.OtherInvalid("_"),
Replacement.FilenameForwardSlash("_"),
Replacement.FilenameBackSlash("\\"),
Replacement.OpenQuote("\""),
Replacement.CloseQuote("\""),
Replacement.OtherQuote("\"")
}
};
Replacement.OtherQuote("\"")]
};
#endregion
private static bool IsWindows => Environment.OSVersion.Platform is PlatformID.Win32NT;
internal static bool IsWindows => Environment.OSVersion.Platform is PlatformID.Win32NT;
private static readonly char[] invalidPathChars = Path.GetInvalidFileNameChars().Except(new[] {
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
@ -301,23 +292,21 @@ namespace FileManager
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
var defaults = ReplacementCharacters.Default(ReplacementCharacters.IsWindows).Replacements;
var jObj = JObject.Load(reader);
var replaceArr = jObj[nameof(Replacement)];
var dict
= replaceArr?.ToObject<Replacement[]>()?.ToList()
?? ReplacementCharacters.Default.Replacements;
var dict = replaceArr?.ToObject<Replacement[]>()?.ToList() ?? defaults;
//Ensure that the first 6 replacements are for the expected chars and that all replacement strings are valid.
//If not, reset to default.
for (int i = 0; i < Replacement.FIXED_COUNT; i++)
{
if (dict.Count < Replacement.FIXED_COUNT
|| dict[i].CharacterToReplace != ReplacementCharacters.Barebones.Replacements[i].CharacterToReplace
|| dict[i].Description != ReplacementCharacters.Barebones.Replacements[i].Description)
|| dict[i].CharacterToReplace != defaults[i].CharacterToReplace
|| dict[i].Description != defaults[i].Description)
{
dict = ReplacementCharacters.Default.Replacements;
dict = defaults;
break;
}

View File

@ -6,6 +6,8 @@
MinWidth="500" MinHeight="450"
Width="500" Height="450"
x:Class="LibationAvalonia.Dialogs.EditReplacementChars"
xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs"
x:DataType="dialogs:EditReplacementChars"
Title="Illegal Character Replacement">
<Grid
@ -23,31 +25,30 @@
BeginningEdit="ReplacementGrid_BeginningEdit"
CellEditEnding="ReplacementGrid_CellEditEnding"
KeyDown="ReplacementGrid_KeyDown"
ItemsSource="{Binding replacements}">
ItemsSource="{CompiledBinding replacements}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Char to&#xa;Replace">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox IsReadOnly="{Binding Mandatory}" Text="{Binding CharacterToReplace, Mode=TwoWay}" />
<DataTemplate x:DataType="dialogs:EditReplacementChars+ReplacementsExt">
<TextBox IsReadOnly="{CompiledBinding Mandatory}" Text="{CompiledBinding CharacterToReplace, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Replacement&#xa;Text">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding ReplacementText, Mode=TwoWay}" />
<DataTemplate x:DataType="dialogs:EditReplacementChars+ReplacementsExt">
<TextBox Text="{CompiledBinding ReplacementText, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="Description">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox IsReadOnly="{Binding Mandatory}" Text="{Binding Description, Mode=TwoWay}" />
<DataTemplate x:DataType="dialogs:EditReplacementChars+ReplacementsExt">
<TextBox IsReadOnly="{CompiledBinding Mandatory}" Text="{CompiledBinding Description, Mode=TwoWay}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
@ -55,21 +56,31 @@
</DataGrid.Columns>
</DataGrid>
<StackPanel
<Grid
Grid.Row="1"
Grid.Column="0"
RowDefinitions="Auto,Auto"
Margin="5"
Orientation="Horizontal">
<Button Margin="0,0,10,0" Command="{Binding Defaults}" Content="Defaults" />
<Button Margin="0,0,10,0" Command="{Binding LoFiDefaults}" Content="LoFi Defaults" />
<Button Command="{Binding Barebones}" Content="Barebones" />
</StackPanel>
ColumnDefinitions="Auto,Auto,Auto,Auto">
<TextBlock IsVisible="{CompiledBinding !EnvironmentIsWindows}" Text="This System:" Margin="0,0,10,0" VerticalAlignment="Center" />
<TextBlock IsVisible="{CompiledBinding !EnvironmentIsWindows}" Grid.Row="1" Text="NTFS:" Margin="0,0,10,0" VerticalAlignment="Center" />
<Button Grid.Column="1" Margin="0,0,10,0" Command="{CompiledBinding Defaults}" CommandParameter="{CompiledBinding EnvironmentIsWindows}" Content="Defaults" />
<Button Grid.Column="2" Margin="0,0,10,0" Command="{CompiledBinding LoFiDefaults}" CommandParameter="{CompiledBinding EnvironmentIsWindows}" Content="LoFi Defaults" />
<Button Grid.Column="3" Command="{CompiledBinding Barebones}" CommandParameter="{CompiledBinding EnvironmentIsWindows}" Content="Barebones" />
<Button IsVisible="{CompiledBinding !EnvironmentIsWindows}" Grid.Row="1" Grid.Column="1" Margin="0,10,10,0" Command="{CompiledBinding Defaults}" CommandParameter="True" Content="Defaults" />
<Button IsVisible="{CompiledBinding !EnvironmentIsWindows}" Grid.Row="1" Grid.Column="2" Margin="0,10,10,0" Command="{CompiledBinding LoFiDefaults}" CommandParameter="True" Content="LoFi Defaults" />
<Button IsVisible="{CompiledBinding !EnvironmentIsWindows}" Grid.Row="1" Grid.Column="3" Margin="0,10,0,0" Command="{CompiledBinding Barebones}" CommandParameter="True" Content="Barebones" />
</Grid>
<StackPanel
Grid.Row="1"
Grid.Column="1"
Margin="5"
VerticalAlignment="Bottom"
Orientation="Horizontal">
<Button Margin="0,0,10,0" Command="{Binding Close}" Content="Cancel" />

View File

@ -13,6 +13,8 @@ namespace LibationAvalonia.Dialogs
{
Configuration config;
public bool EnvironmentIsWindows => Configuration.IsWindows;
private readonly List<ReplacementsExt> SOURCE = new();
public DataGridCollectionView replacements { get; }
public EditReplacementChars()
@ -23,7 +25,7 @@ namespace LibationAvalonia.Dialogs
if (Design.IsDesignMode)
{
LoadTable(ReplacementCharacters.Default.Replacements);
LoadTable(ReplacementCharacters.Default(true).Replacements);
}
DataContext = this;
@ -35,12 +37,12 @@ namespace LibationAvalonia.Dialogs
LoadTable(config.ReplacementCharacters.Replacements);
}
public void Defaults()
=> LoadTable(ReplacementCharacters.Default.Replacements);
public void LoFiDefaults()
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
public void Barebones()
=> LoadTable(ReplacementCharacters.Barebones.Replacements);
public void Defaults(bool isNtfs)
=> LoadTable(ReplacementCharacters.Default(isNtfs).Replacements);
public void LoFiDefaults(bool isNtfs)
=> LoadTable(ReplacementCharacters.LoFiDefault(isNtfs).Replacements);
public void Barebones(bool isNtfs)
=> LoadTable(ReplacementCharacters.Barebones(isNtfs).Replacements);
protected override void SaveAndClose()
{

View File

@ -54,7 +54,7 @@ namespace LibationAvalonia.Views
FileUtility.SaferMoveToValidPath(
e.SettingsFilePath,
e.SettingsFilePath,
ReplacementCharacters.Barebones,
Configuration.Instance.ReplacementCharacters,
"bak");
AudibleApiStorage.EnsureAccountsSettingsFileExists();
e.Handled = true;

View File

@ -319,7 +319,7 @@ namespace LibationFileManager
#region templates: custom file naming
[Description("Edit how filename characters are replaced")]
public ReplacementCharacters ReplacementCharacters { get => GetNonString(defaultValue: ReplacementCharacters.Default); set => SetNonString(value); }
public ReplacementCharacters ReplacementCharacters { get => GetNonString(defaultValue: ReplacementCharacters.Default(IsWindows)); set => SetNonString(value); }
[Description("How to format the folders in which files will be saved")]
public string FolderTemplate

View File

@ -50,13 +50,13 @@ namespace LibationWinForms.Dialogs
}
private void loFiDefaultsBtn_Click(object sender, EventArgs e)
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
=> LoadTable(ReplacementCharacters.LoFiDefault(ntfs: true).Replacements);
private void defaultsBtn_Click(object sender, EventArgs e)
=> LoadTable(ReplacementCharacters.Default.Replacements);
=> LoadTable(ReplacementCharacters.Default(ntfs: true).Replacements);
private void minDefaultBtn_Click(object sender, EventArgs e)
=> LoadTable(ReplacementCharacters.Barebones.Replacements);
=> LoadTable(ReplacementCharacters.Barebones(ntfs: true).Replacements);
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)

View File

@ -47,7 +47,7 @@ namespace LibationWinForms
FileUtility.SaferMoveToValidPath(
e.SettingsFilePath,
e.SettingsFilePath,
ReplacementCharacters.Barebones,
Configuration.Instance.ReplacementCharacters,
"bak");
AudibleApiStorage.EnsureAccountsSettingsFileExists();

View File

@ -12,9 +12,9 @@ namespace FileUtilityTests
[TestClass]
public class GetSafePath
{
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault;
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones;
static readonly ReplacementCharacters Default = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault(Environment.OSVersion.Platform == PlatformID.Win32NT);
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones(Environment.OSVersion.Platform == PlatformID.Win32NT);
[TestMethod]
public void null_path_throws() => Assert.ThrowsException<ArgumentNullException>(() => FileUtility.GetSafePath(null, Default));
@ -98,9 +98,9 @@ namespace FileUtilityTests
[TestClass]
public class GetSafeFileName
{
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault;
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones;
static readonly ReplacementCharacters Default = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault(Environment.OSVersion.Platform == PlatformID.Win32NT);
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones(Environment.OSVersion.Platform == PlatformID.Win32NT);
// needs separate method. middle null param not running correctly in TestExplorer when used in DataRow()
[TestMethod]
@ -193,7 +193,7 @@ namespace FileUtilityTests
[TestClass]
public class GetValidFilename
{
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
static ReplacementCharacters Replacements = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
[TestMethod]
// dot-files

View File

@ -66,7 +66,7 @@ namespace TemplatesTests
[TestClass]
public class getFileNamingTemplate
{
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
static ReplacementCharacters Replacements = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
[TestMethod]
[DataRow(null)]
@ -453,7 +453,7 @@ namespace Templates_Other
[TestClass]
public class GetFilePath
{
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
static ReplacementCharacters Replacements = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
[TestMethod]
[DataRow(@"C:\foo\bar", @"\\Folder\<title>\[<id>]\\", @"C:\foo\bar\Folder\my_ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\[ID123456].txt", PlatformID.Win32NT)]
@ -889,7 +889,7 @@ namespace Templates_ChapterFile_Tests
[TestClass]
public class GetFilename
{
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
static readonly ReplacementCharacters Default = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
[TestMethod]
[DataRow("[<id>] <ch# 0> of <ch count> - <ch title>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\[asin] 06 of 10 - chap.txt", PlatformID.Win32NT)]