Custom illegal character replacement
This commit is contained in:
parent
184ba84600
commit
2ab466c570
@ -141,7 +141,7 @@ That naming may not be desirable for everyone, but it's an easy change to instea
|
|||||||
private FileStream createOutputFileStream(MultiConvertFileProperties multiConvertFileProperties)
|
private FileStream createOutputFileStream(MultiConvertFileProperties multiConvertFileProperties)
|
||||||
{
|
{
|
||||||
var fileName = DownloadOptions.GetMultipartFileName(multiConvertFileProperties);
|
var fileName = DownloadOptions.GetMultipartFileName(multiConvertFileProperties);
|
||||||
fileName = FileUtility.GetValidFilename(fileName);
|
fileName = FileUtility.GetValidFilename(fileName, DownloadOptions.ReplacementCharacters);
|
||||||
|
|
||||||
multiPartFilePaths.Add(fileName);
|
multiPartFilePaths.Add(fileName);
|
||||||
|
|
||||||
|
|||||||
@ -103,7 +103,7 @@ namespace AaxDecrypter
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var path = Path.ChangeExtension(OutputFileName, ".cue");
|
var path = Path.ChangeExtension(OutputFileName, ".cue");
|
||||||
path = FileUtility.GetValidFilename(path);
|
path = FileUtility.GetValidFilename(path, DownloadOptions.ReplacementCharacters);
|
||||||
File.WriteAllText(path, Cue.CreateContents(Path.GetFileName(OutputFileName), DownloadOptions.ChapterInfo));
|
File.WriteAllText(path, Cue.CreateContents(Path.GetFileName(OutputFileName), DownloadOptions.ChapterInfo));
|
||||||
OnFileCreated(path);
|
OnFileCreated(path);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
public interface IDownloadOptions
|
public interface IDownloadOptions
|
||||||
{
|
{
|
||||||
|
FileManager.ReplacementCharacters ReplacementCharacters { get; }
|
||||||
string DownloadUrl { get; }
|
string DownloadUrl { get; }
|
||||||
string UserAgent { get; }
|
string UserAgent { get; }
|
||||||
string AudibleKey { get; }
|
string AudibleKey { get; }
|
||||||
|
|||||||
@ -11,15 +11,5 @@ namespace AaxDecrypter
|
|||||||
public int PartsTotal { get; set; }
|
public int PartsTotal { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
||||||
public static string DefaultMultipartFilename(MultiConvertFileProperties multiConvertFileProperties)
|
|
||||||
{
|
|
||||||
var template = Path.ChangeExtension(multiConvertFileProperties.OutputFileName, null) + " - <ch# 0> - <title>" + Path.GetExtension(multiConvertFileProperties.OutputFileName);
|
|
||||||
|
|
||||||
var fileNamingTemplate = new FileNamingTemplate(template) { IllegalCharacterReplacements = " " };
|
|
||||||
fileNamingTemplate.AddParameterReplacement("ch# 0", FileUtility.GetSequenceFormatted(multiConvertFileProperties.PartsPosition, multiConvertFileProperties.PartsTotal));
|
|
||||||
fileNamingTemplate.AddParameterReplacement("title", multiConvertFileProperties.Title ?? "");
|
|
||||||
|
|
||||||
return fileNamingTemplate.GetFilePath();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,7 +68,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
CloseInputFileStream();
|
CloseInputFileStream();
|
||||||
|
|
||||||
var realOutputFileName = FileUtility.SaferMoveToValidPath(InputFileStream.SaveFilePath, OutputFileName);
|
var realOutputFileName = FileUtility.SaferMoveToValidPath(InputFileStream.SaveFilePath, OutputFileName, DownloadOptions.ReplacementCharacters);
|
||||||
SetOutputFileName(realOutputFileName);
|
SetOutputFileName(realOutputFileName);
|
||||||
OnFileCreated(realOutputFileName);
|
OnFileCreated(realOutputFileName);
|
||||||
|
|
||||||
|
|||||||
@ -117,6 +117,9 @@ namespace AppScaffolding
|
|||||||
if (!config.Exists(nameof(config.DownloadEpisodes)))
|
if (!config.Exists(nameof(config.DownloadEpisodes)))
|
||||||
config.DownloadEpisodes = true;
|
config.DownloadEpisodes = true;
|
||||||
|
|
||||||
|
if (!config.Exists(nameof(config.ReplacementCharacters)))
|
||||||
|
config.ReplacementCharacters = FileManager.ReplacementCharacters.Default;
|
||||||
|
|
||||||
if (!config.Exists(nameof(config.FolderTemplate)))
|
if (!config.Exists(nameof(config.FolderTemplate)))
|
||||||
config.FolderTemplate = Templates.Folder.DefaultTemplate;
|
config.FolderTemplate = Templates.Folder.DefaultTemplate;
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,7 @@ namespace FileLiberator
|
|||||||
return new StatusHandler { "Cancelled" };
|
return new StatusHandler { "Cancelled" };
|
||||||
}
|
}
|
||||||
|
|
||||||
var realMp3Path = FileUtility.SaferMoveToValidPath(mp3File.Name, proposedMp3Path);
|
var realMp3Path = FileUtility.SaferMoveToValidPath(mp3File.Name, proposedMp3Path, Configuration.Instance.ReplacementCharacters);
|
||||||
OnFileCreated(libraryBook, realMp3Path);
|
OnFileCreated(libraryBook, realMp3Path);
|
||||||
}
|
}
|
||||||
return new StatusHandler();
|
return new StatusHandler();
|
||||||
|
|||||||
@ -258,7 +258,7 @@ namespace FileLiberator
|
|||||||
{
|
{
|
||||||
var entry = entries[i];
|
var entry = entries[i];
|
||||||
|
|
||||||
var realDest = FileUtility.SaferMoveToValidPath(entry.Path, Path.Combine(destinationDir, Path.GetFileName(entry.Path)));
|
var realDest = FileUtility.SaferMoveToValidPath(entry.Path, Path.Combine(destinationDir, Path.GetFileName(entry.Path)), Configuration.Instance.ReplacementCharacters);
|
||||||
FilePathCache.Insert(libraryBook.Book.AudibleProductId, realDest);
|
FilePathCache.Insert(libraryBook.Book.AudibleProductId, realDest);
|
||||||
|
|
||||||
// propagate corrected path. Must update cache with corrected path. Also want updated path for cue file (after this for-loop)
|
// propagate corrected path. Must update cache with corrected path. Also want updated path for cue file (after this for-loop)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using AAXClean;
|
|||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using FileManager;
|
||||||
|
|
||||||
namespace FileLiberator
|
namespace FileLiberator
|
||||||
{
|
{
|
||||||
@ -23,6 +24,7 @@ namespace FileLiberator
|
|||||||
public NAudio.Lame.LameConfig LameConfig { get; set; }
|
public NAudio.Lame.LameConfig LameConfig { get; set; }
|
||||||
public bool Downsample { get; set; }
|
public bool Downsample { get; set; }
|
||||||
public bool MatchSourceBitrate { get; set; }
|
public bool MatchSourceBitrate { get; set; }
|
||||||
|
public ReplacementCharacters ReplacementCharacters => Configuration.Instance.ReplacementCharacters;
|
||||||
|
|
||||||
public string GetMultipartFileName(MultiConvertFileProperties props)
|
public string GetMultipartFileName(MultiConvertFileProperties props)
|
||||||
=> Templates.ChapterFile.GetFilename(LibraryBookDto, props);
|
=> Templates.ChapterFile.GetFilename(LibraryBookDto, props);
|
||||||
|
|||||||
@ -79,8 +79,13 @@ namespace FileManager
|
|||||||
//Stop raising events
|
//Stop raising events
|
||||||
fileSystemWatcher?.Dispose();
|
fileSystemWatcher?.Dispose();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
//Calling CompleteAdding() will cause background scanner to terminate.
|
//Calling CompleteAdding() will cause background scanner to terminate.
|
||||||
directoryChangesEvents?.CompleteAdding();
|
directoryChangesEvents?.CompleteAdding();
|
||||||
|
}
|
||||||
|
// if directoryChangesEvents is non-null and isDisposed, this exception is thrown. there's no other way to check >:(
|
||||||
|
catch (ObjectDisposedException) { }
|
||||||
|
|
||||||
//Wait for background scanner to terminate before reinitializing.
|
//Wait for background scanner to terminate before reinitializing.
|
||||||
backgroundScanner?.Wait();
|
backgroundScanner?.Wait();
|
||||||
|
|||||||
@ -12,17 +12,13 @@ namespace FileManager
|
|||||||
/// <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) : base(template) { }
|
||||||
|
|
||||||
/// <summary>Optional step 2: Replace all illegal characters with this. Default=<see cref="string.Empty"/></summary>
|
|
||||||
public string IllegalCharacterReplacements { get; set; }
|
|
||||||
|
|
||||||
/// <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(bool returnFirstExisting = false)
|
public LongPath GetFilePath(ReplacementCharacters replacements, bool returnFirstExisting = false)
|
||||||
{
|
{
|
||||||
|
|
||||||
string fileName = Template.EndsWith(Path.DirectorySeparatorChar) ? Template[..^1] : Template;
|
string fileName = Template.EndsWith(Path.DirectorySeparatorChar) ? Template[..^1] : Template;
|
||||||
List<string> pathParts = new();
|
List<string> pathParts = new();
|
||||||
|
|
||||||
var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value));
|
var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value, replacements));
|
||||||
|
|
||||||
while (!string.IsNullOrEmpty(fileName))
|
while (!string.IsNullOrEmpty(fileName))
|
||||||
{
|
{
|
||||||
@ -43,7 +39,7 @@ namespace FileManager
|
|||||||
|
|
||||||
pathParts.Reverse();
|
pathParts.Reverse();
|
||||||
|
|
||||||
return FileUtility.GetValidFilename(Path.Join(pathParts.ToArray()), IllegalCharacterReplacements, returnFirstExisting);
|
return FileUtility.GetValidFilename(Path.Join(pathParts.ToArray()), replacements, returnFirstExisting);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string replaceFileName(string filename, Dictionary<string,string> paramReplacements)
|
private string replaceFileName(string filename, Dictionary<string,string> paramReplacements)
|
||||||
@ -92,17 +88,14 @@ namespace FileManager
|
|||||||
return string.Join("", filenameParts);
|
return string.Join("", filenameParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string formatValue(object value)
|
private string formatValue(object value, ReplacementCharacters replacements)
|
||||||
{
|
{
|
||||||
if (value is null)
|
if (value is null)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
// 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 value
|
return replacements.ReplaceInvalidFilenameChars(value.ToString());
|
||||||
.ToString()
|
|
||||||
.Replace($"{System.IO.Path.DirectorySeparatorChar}", IllegalCharacterReplacements)
|
|
||||||
.Replace($"{System.IO.Path.AltDirectorySeparatorChar}", IllegalCharacterReplacements);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,12 +46,12 @@ namespace FileManager
|
|||||||
/// <br/>- ensure uniqueness
|
/// <br/>- ensure uniqueness
|
||||||
/// <br/>- enforce max file length
|
/// <br/>- enforce max file length
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LongPath GetValidFilename(LongPath path, string illegalCharacterReplacements = "", bool returnFirstExisting = false)
|
public static LongPath GetValidFilename(LongPath path, ReplacementCharacters replacements, bool returnFirstExisting = false)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
||||||
|
|
||||||
// remove invalid chars
|
// remove invalid chars
|
||||||
path = GetSafePath(path, illegalCharacterReplacements);
|
path = GetSafePath(path, replacements);
|
||||||
|
|
||||||
// ensure uniqueness and check lengths
|
// ensure uniqueness and check lengths
|
||||||
var dir = Path.GetDirectoryName(path);
|
var dir = Path.GetDirectoryName(path);
|
||||||
@ -77,36 +77,19 @@ namespace FileManager
|
|||||||
return fullfilename;
|
return fullfilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInvalidFileNameChars contains everything in GetInvalidPathChars plus ':', '*', '?', '\\', '/'
|
|
||||||
|
|
||||||
/// <summary>Use with file name, not full path. Valid path characters which are invalid file name characters will be replaced: ':', '\\', '/'</summary>
|
|
||||||
public static string GetSafeFileName(string str, string illegalCharacterReplacements = "")
|
|
||||||
=> string.Join(illegalCharacterReplacements ?? "", str.Split(Path.GetInvalidFileNameChars()));
|
|
||||||
|
|
||||||
/// <summary>Use with full path, not file name. Valid path characters which are invalid file name characters will be retained: '\\', '/'</summary>
|
/// <summary>Use with full path, not file name. Valid path characters which are invalid file name characters will be retained: '\\', '/'</summary>
|
||||||
public static LongPath GetSafePath(LongPath path, string illegalCharacterReplacements = "")
|
public static LongPath GetSafePath(LongPath path, ReplacementCharacters replacements)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
||||||
|
|
||||||
var pathNoPrefix = path.PathWithoutPrefix;
|
var pathNoPrefix = path.PathWithoutPrefix;
|
||||||
|
|
||||||
pathNoPrefix = replaceColons(pathNoPrefix, "꞉");
|
pathNoPrefix = replacements.ReplaceInvalidChars(pathNoPrefix);
|
||||||
pathNoPrefix = replaceIllegalWithUnicodeAnalog(pathNoPrefix);
|
|
||||||
pathNoPrefix = replaceInvalidChars(pathNoPrefix, illegalCharacterReplacements);
|
|
||||||
pathNoPrefix = removeDoubleSlashes(pathNoPrefix);
|
pathNoPrefix = removeDoubleSlashes(pathNoPrefix);
|
||||||
|
|
||||||
return pathNoPrefix;
|
return pathNoPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static char[] invalidChars { get; } = Path.GetInvalidPathChars().Union(new[] {
|
|
||||||
'*', '?',
|
|
||||||
// these are weird. If you run Path.GetInvalidPathChars() in Visual Studio's "C# Interactive", then these characters are included.
|
|
||||||
// In live code, Path.GetInvalidPathChars() does not include them
|
|
||||||
'"', '<', '>'
|
|
||||||
}).ToArray();
|
|
||||||
private static string replaceInvalidChars(string path, string illegalCharacterReplacements)
|
|
||||||
=> string.Join(illegalCharacterReplacements ?? "", path.Split(invalidChars));
|
|
||||||
|
|
||||||
private static string removeDoubleSlashes(string path)
|
private static string removeDoubleSlashes(string path)
|
||||||
{
|
{
|
||||||
if (path.Length < 2)
|
if (path.Length < 2)
|
||||||
@ -122,60 +105,6 @@ namespace FileManager
|
|||||||
return path[0] + remainder;
|
return path[0] + remainder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string replaceIllegalWithUnicodeAnalog(string path)
|
|
||||||
{
|
|
||||||
char[] replaced = path.ToCharArray();
|
|
||||||
|
|
||||||
char GetQuote(int position)
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
position == 0
|
|
||||||
|| (position > 0
|
|
||||||
&& position < replaced.Length
|
|
||||||
&& !char.IsLetter(replaced[position - 1])
|
|
||||||
&& !char.IsNumber(replaced[position - 1])
|
|
||||||
)
|
|
||||||
) return '“';
|
|
||||||
else if (
|
|
||||||
position == replaced.Length - 1
|
|
||||||
|| (position >= 0
|
|
||||||
&& position < replaced.Length - 1
|
|
||||||
&& !char.IsLetter(replaced[position + 1])
|
|
||||||
&& !char.IsNumber(replaced[position + 1])
|
|
||||||
)
|
|
||||||
) return '”';
|
|
||||||
else return '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < replaced.Length; i++)
|
|
||||||
{
|
|
||||||
replaced[i] = replaced[i] switch
|
|
||||||
{
|
|
||||||
'?' => '?',
|
|
||||||
'*' => '✱',
|
|
||||||
'<' => '<',
|
|
||||||
'>' => '>',
|
|
||||||
'"' => GetQuote(i),
|
|
||||||
_ => replaced[i]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return new string(replaced);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string replaceColons(string path, string illegalCharacterReplacements)
|
|
||||||
{
|
|
||||||
// replace all colons except within the first 2 chars
|
|
||||||
var builder = new System.Text.StringBuilder();
|
|
||||||
for (var i = 0; i < path.Length; i++)
|
|
||||||
{
|
|
||||||
var c = path[i];
|
|
||||||
if (i >= 2 && c == ':')
|
|
||||||
builder.Append(illegalCharacterReplacements);
|
|
||||||
else
|
|
||||||
builder.Append(c);
|
|
||||||
}
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
|
||||||
private static string removeInvalidWhitespace_pattern { get; } = $@"[\s\.]*\{Path.DirectorySeparatorChar}\s*";
|
private static string removeInvalidWhitespace_pattern { get; } = $@"[\s\.]*\{Path.DirectorySeparatorChar}\s*";
|
||||||
private static Regex removeInvalidWhitespace_regex { get; } = new(removeInvalidWhitespace_pattern, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
|
private static Regex removeInvalidWhitespace_regex { get; } = new(removeInvalidWhitespace_pattern, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace);
|
||||||
|
|
||||||
@ -206,9 +135,9 @@ namespace FileManager
|
|||||||
/// <br/>- Perform <see cref="SaferMove"/>
|
/// <br/>- Perform <see cref="SaferMove"/>
|
||||||
/// <br/>- Return valid path
|
/// <br/>- Return valid path
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string SaferMoveToValidPath(LongPath source, LongPath destination)
|
public static string SaferMoveToValidPath(LongPath source, LongPath destination, ReplacementCharacters replacements)
|
||||||
{
|
{
|
||||||
destination = GetValidFilename(destination);
|
destination = GetValidFilename(destination, replacements);
|
||||||
SaferMove(source, destination);
|
SaferMove(source, destination);
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|||||||
269
Source/FileManager/ReplacementCharacters.cs
Normal file
269
Source/FileManager/ReplacementCharacters.cs
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace FileManager
|
||||||
|
{
|
||||||
|
public class Replacement
|
||||||
|
{
|
||||||
|
public const int FIXED_COUNT = 6;
|
||||||
|
|
||||||
|
internal const char QUOTE_MARK = '"';
|
||||||
|
[JsonIgnore] public bool Mandatory { get; internal set; }
|
||||||
|
[JsonProperty] public char CharacterToReplace { get; private set; }
|
||||||
|
[JsonProperty] public string ReplacementString { get; private set; }
|
||||||
|
[JsonProperty] public string Description { get; private set; }
|
||||||
|
public override string ToString() => $"{CharacterToReplace} → {ReplacementString} ({Description})";
|
||||||
|
|
||||||
|
public Replacement(char charToReplace, string replacementString, string description)
|
||||||
|
{
|
||||||
|
CharacterToReplace = charToReplace;
|
||||||
|
ReplacementString = replacementString;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
private Replacement(char charToReplace, string replacementString, string description, bool mandatory)
|
||||||
|
: this(charToReplace, replacementString, description)
|
||||||
|
{
|
||||||
|
Mandatory = mandatory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(char charToReplace, string replacementString, string description)
|
||||||
|
{
|
||||||
|
ReplacementString = replacementString;
|
||||||
|
|
||||||
|
if (!Mandatory)
|
||||||
|
{
|
||||||
|
CharacterToReplace = charToReplace;
|
||||||
|
Description = description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Replacement OtherInvalid(string replacement) => new(default, replacement, "All other invalid characters", true);
|
||||||
|
public static Replacement FilenameForwardSlash(string replacement) => new('/', replacement, "Forward Slash (Filename Only)", true);
|
||||||
|
public static Replacement FilenameBackSlash(string replacement) => new('\\', replacement, "Back Slash (Filename Only)", true);
|
||||||
|
public static Replacement OpenQuote(string replacement) => new('"', replacement, "Open Quote", true);
|
||||||
|
public static Replacement CloseQuote(string replacement) => new('"', replacement, "Close Quote", true);
|
||||||
|
public static Replacement OtherQuote(string replacement) => new('"', replacement, "Other Quote", true);
|
||||||
|
public static Replacement Colon(string replacement) => new(':', replacement, "Colon");
|
||||||
|
public static Replacement Asterisk(string replacement) => new('*', replacement, "Asterisk");
|
||||||
|
public static Replacement QuestionMark(string replacement) => new('?', replacement, "Question Mark");
|
||||||
|
public static Replacement OpenAngleBracket(string replacement) => new('<', replacement, "Open Angle Bracket");
|
||||||
|
public static Replacement CloseAngleBracket(string replacement) => new('>', replacement, "Close Angle Bracket");
|
||||||
|
public static Replacement Pipe(string replacement) => new('|', replacement, "Vertical Line");
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(ReplacementCharactersConverter))]
|
||||||
|
public class ReplacementCharacters
|
||||||
|
{
|
||||||
|
public static readonly ReplacementCharacters Default = new()
|
||||||
|
{
|
||||||
|
Replacements = new List<Replacement>()
|
||||||
|
{
|
||||||
|
Replacement.OtherInvalid("_"),
|
||||||
|
Replacement.FilenameForwardSlash("∕"),
|
||||||
|
Replacement.FilenameBackSlash(""),
|
||||||
|
Replacement.OpenQuote("“"),
|
||||||
|
Replacement.CloseQuote("”"),
|
||||||
|
Replacement.OtherQuote("""),
|
||||||
|
Replacement.OpenAngleBracket("<"),
|
||||||
|
Replacement.CloseAngleBracket(">"),
|
||||||
|
Replacement.Colon("꞉"),
|
||||||
|
Replacement.Asterisk("✱"),
|
||||||
|
Replacement.QuestionMark("?"),
|
||||||
|
Replacement.Pipe("⏐"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly ReplacementCharacters LoFiDefault = new()
|
||||||
|
{
|
||||||
|
Replacements = new List<Replacement>()
|
||||||
|
{
|
||||||
|
Replacement.OtherInvalid("_"),
|
||||||
|
Replacement.FilenameForwardSlash("_"),
|
||||||
|
Replacement.FilenameBackSlash("_"),
|
||||||
|
Replacement.OpenQuote("'"),
|
||||||
|
Replacement.CloseQuote("'"),
|
||||||
|
Replacement.OtherQuote("'"),
|
||||||
|
Replacement.OpenAngleBracket("{"),
|
||||||
|
Replacement.CloseAngleBracket("}"),
|
||||||
|
Replacement.Colon("-"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static readonly ReplacementCharacters Minimum = new()
|
||||||
|
{
|
||||||
|
Replacements = new List<Replacement>()
|
||||||
|
{
|
||||||
|
Replacement.OtherInvalid("_"),
|
||||||
|
Replacement.FilenameForwardSlash("_"),
|
||||||
|
Replacement.FilenameBackSlash("_"),
|
||||||
|
Replacement.OpenQuote("_"),
|
||||||
|
Replacement.CloseQuote("_"),
|
||||||
|
Replacement.OtherQuote("_"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly char[] invalidChars = Path.GetInvalidPathChars().Union(new[] {
|
||||||
|
'*', '?', ':',
|
||||||
|
// these are weird. If you run Path.GetInvalidPathChars() in Visual Studio's "C# Interactive", then these characters are included.
|
||||||
|
// In live code, Path.GetInvalidPathChars() does not include them
|
||||||
|
'"', '<', '>'
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
public IReadOnlyList<Replacement> Replacements { get; init; }
|
||||||
|
private string DefaultReplacement => Replacements[0].ReplacementString;
|
||||||
|
private Replacement ForwardSlash => Replacements[1];
|
||||||
|
private Replacement BackSlash => Replacements[2];
|
||||||
|
private string OpenQuote => Replacements[3].ReplacementString;
|
||||||
|
private string CloseQuote => Replacements[4].ReplacementString;
|
||||||
|
private string OtherQuote => Replacements[5].ReplacementString;
|
||||||
|
|
||||||
|
private string GetFilenameCharReplacement(char toReplace, char preceding, char succeding)
|
||||||
|
{
|
||||||
|
if (toReplace == ForwardSlash.CharacterToReplace)
|
||||||
|
return ForwardSlash.ReplacementString;
|
||||||
|
else if (toReplace == BackSlash.CharacterToReplace)
|
||||||
|
return BackSlash.ReplacementString;
|
||||||
|
else return GetPathCharReplacement(toReplace, preceding, succeding);
|
||||||
|
}
|
||||||
|
private string GetPathCharReplacement(char toReplace, char preceding, char succeding)
|
||||||
|
{
|
||||||
|
if (toReplace == Replacement.QUOTE_MARK)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
preceding != default
|
||||||
|
&& !char.IsLetter(preceding)
|
||||||
|
&& !char.IsNumber(preceding)
|
||||||
|
&& (char.IsLetter(succeding) || char.IsNumber(succeding))
|
||||||
|
)
|
||||||
|
return OpenQuote;
|
||||||
|
else if (
|
||||||
|
succeding != default
|
||||||
|
&& !char.IsLetter(succeding)
|
||||||
|
&& !char.IsNumber(succeding)
|
||||||
|
&& (char.IsLetter(preceding) || char.IsNumber(preceding))
|
||||||
|
)
|
||||||
|
return CloseQuote;
|
||||||
|
else
|
||||||
|
return OtherQuote;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = Replacement.FIXED_COUNT; i < Replacements.Count; i++)
|
||||||
|
{
|
||||||
|
var r = Replacements[i];
|
||||||
|
if (r.CharacterToReplace == toReplace)
|
||||||
|
return r.ReplacementString;
|
||||||
|
}
|
||||||
|
return DefaultReplacement;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static bool ContainsInvalid(string path)
|
||||||
|
=> path.Any(c => invalidChars.Contains(c));
|
||||||
|
|
||||||
|
public string ReplaceInvalidFilenameChars(string fileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fileName)) return string.Empty;
|
||||||
|
var builder = new System.Text.StringBuilder();
|
||||||
|
for (var i = 0; i < fileName.Length; i++)
|
||||||
|
{
|
||||||
|
var c = fileName[i];
|
||||||
|
|
||||||
|
if (invalidChars.Contains(c) || c == ForwardSlash.CharacterToReplace || c == BackSlash.CharacterToReplace)
|
||||||
|
{
|
||||||
|
char preceding = i > 0 ? fileName[i - 1] : default;
|
||||||
|
char succeeding = i < fileName.Length - 1 ? fileName[i + 1] : default;
|
||||||
|
builder.Append(GetFilenameCharReplacement(c, preceding, succeeding));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
builder.Append(c);
|
||||||
|
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReplaceInvalidChars(string pathStr)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(pathStr)) return string.Empty;
|
||||||
|
|
||||||
|
// replace all colons except within the first 2 chars
|
||||||
|
var builder = new System.Text.StringBuilder();
|
||||||
|
for (var i = 0; i < pathStr.Length; i++)
|
||||||
|
{
|
||||||
|
var c = pathStr[i];
|
||||||
|
|
||||||
|
if (!invalidChars.Contains(c) || (i <= 2 && Path.IsPathRooted(pathStr)))
|
||||||
|
builder.Append(c);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
char preceding = i > 0 ? pathStr[i - 1] : default;
|
||||||
|
char succeeding = i < pathStr.Length - 1 ? pathStr[i + 1] : default;
|
||||||
|
builder.Append(GetPathCharReplacement(c, preceding, succeeding));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region JSON Converter
|
||||||
|
internal class ReplacementCharactersConverter : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
=> objectType == typeof(ReplacementCharacters);
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
var jObj = JObject.Load(reader);
|
||||||
|
var replaceArr = jObj[nameof(Replacement)];
|
||||||
|
IReadOnlyList<Replacement> dict = replaceArr
|
||||||
|
.ToObject<Replacement[]>().ToList();
|
||||||
|
|
||||||
|
//Ensure that the first 6 replacements are for the expected chars and that all replacement strings are valid.
|
||||||
|
//If not, reset to default.
|
||||||
|
|
||||||
|
var default0 = Replacement.OtherInvalid("");
|
||||||
|
var default1 = Replacement.FilenameForwardSlash("");
|
||||||
|
var default2 = Replacement.FilenameBackSlash("");
|
||||||
|
var default3 = Replacement.OpenQuote("");
|
||||||
|
var default4 = Replacement.CloseQuote("");
|
||||||
|
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.ContainsInvalid(r.ReplacementString))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
dict = ReplacementCharacters.Default.Replacements;
|
||||||
|
}
|
||||||
|
//First FIXED_COUNT are mandatory
|
||||||
|
for (int i = 0; i < Replacement.FIXED_COUNT; i++)
|
||||||
|
dict[i].Mandatory = true;
|
||||||
|
|
||||||
|
return new ReplacementCharacters { Replacements = dict };
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
ReplacementCharacters replacements = (ReplacementCharacters)value;
|
||||||
|
|
||||||
|
var propertyNames = replacements.Replacements
|
||||||
|
.Select(c => JObject.FromObject(c)).ToList();
|
||||||
|
|
||||||
|
var prop = new JProperty(nameof(Replacement), new JArray(propertyNames));
|
||||||
|
|
||||||
|
var obj = new JObject();
|
||||||
|
obj.AddFirst(prop);
|
||||||
|
obj.WriteTo(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@ -284,6 +284,13 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
#region templates: custom file naming
|
#region templates: custom file naming
|
||||||
|
|
||||||
|
[Description("Edit how illegal filename characters are replaced")]
|
||||||
|
public ReplacementCharacters ReplacementCharacters
|
||||||
|
{
|
||||||
|
get => persistentDictionary.GetNonString<ReplacementCharacters>(nameof(ReplacementCharacters));
|
||||||
|
set => persistentDictionary.SetNonString(nameof(ReplacementCharacters), value);
|
||||||
|
}
|
||||||
|
|
||||||
[Description("How to format the folders in which files will be saved")]
|
[Description("How to format the folders in which files will be saved")]
|
||||||
public string FolderTemplate
|
public string FolderTemplate
|
||||||
{
|
{
|
||||||
|
|||||||
@ -106,7 +106,7 @@ namespace LibationFileManager
|
|||||||
=> string.IsNullOrWhiteSpace(template)
|
=> string.IsNullOrWhiteSpace(template)
|
||||||
? ""
|
? ""
|
||||||
: getFileNamingTemplate(libraryBookDto, template, null, null)
|
: getFileNamingTemplate(libraryBookDto, template, null, null)
|
||||||
.GetFilePath().PathWithoutPrefix;
|
.GetFilePath(Configuration.Instance.ReplacementCharacters).PathWithoutPrefix;
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ namespace LibationFileManager
|
|||||||
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) { IllegalCharacterReplacements = "_" };
|
var fileNamingTemplate = new FileNamingTemplate(fullfilename);
|
||||||
|
|
||||||
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(':'));
|
||||||
@ -210,7 +210,7 @@ namespace LibationFileManager
|
|||||||
/// <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)
|
||||||
.GetFilePath();
|
.GetFilePath(Configuration.Instance.ReplacementCharacters);
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,7 +233,7 @@ namespace LibationFileManager
|
|||||||
/// <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)
|
||||||
.GetFilePath(returnFirstExisting);
|
.GetFilePath(Configuration.Instance.ReplacementCharacters, returnFirstExisting);
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,8 +268,9 @@ namespace LibationFileManager
|
|||||||
public string GetFilename(LibraryBookDto libraryBookDto, AaxDecrypter.MultiConvertFileProperties props)
|
public string GetFilename(LibraryBookDto libraryBookDto, AaxDecrypter.MultiConvertFileProperties props)
|
||||||
=> GetPortionFilename(libraryBookDto, Configuration.Instance.ChapterFileTemplate, props, AudibleFileStorage.DecryptInProgressDirectory);
|
=> GetPortionFilename(libraryBookDto, Configuration.Instance.ChapterFileTemplate, props, AudibleFileStorage.DecryptInProgressDirectory);
|
||||||
|
|
||||||
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath)
|
public string GetPortionFilename(LibraryBookDto libraryBookDto, string template, AaxDecrypter.MultiConvertFileProperties props, string fullDirPath, ReplacementCharacters replacements = null)
|
||||||
{
|
{
|
||||||
|
replacements ??= Configuration.Instance.ReplacementCharacters;
|
||||||
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName));
|
var fileNamingTemplate = getFileNamingTemplate(libraryBookDto, template, fullDirPath, Path.GetExtension(props.OutputFileName));
|
||||||
|
|
||||||
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChCount, props.PartsTotal);
|
fileNamingTemplate.AddParameterReplacement(TemplateTags.ChCount, props.PartsTotal);
|
||||||
@ -277,7 +278,7 @@ namespace LibationFileManager
|
|||||||
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().PathWithoutPrefix;
|
return fileNamingTemplate.GetFilePath(replacements).PathWithoutPrefix;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
171
Source/LibationWinForms/Dialogs/EditReplacementChars.Designer.cs
generated
Normal file
171
Source/LibationWinForms/Dialogs/EditReplacementChars.Designer.cs
generated
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
namespace LibationWinForms.Dialogs
|
||||||
|
{
|
||||||
|
partial class EditReplacementChars
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Required designer variable.
|
||||||
|
/// </summary>
|
||||||
|
private System.ComponentModel.IContainer components = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clean up any resources being used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing && (components != null))
|
||||||
|
{
|
||||||
|
components.Dispose();
|
||||||
|
}
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Windows Form Designer generated code
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required method for Designer support - do not modify
|
||||||
|
/// the contents of this method with the code editor.
|
||||||
|
/// </summary>
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
this.dataGridView1 = new System.Windows.Forms.DataGridView();
|
||||||
|
this.defaultsBtn = new System.Windows.Forms.Button();
|
||||||
|
this.loFiDefaultsBtn = new System.Windows.Forms.Button();
|
||||||
|
this.saveBtn = new System.Windows.Forms.Button();
|
||||||
|
this.cancelBtn = new System.Windows.Forms.Button();
|
||||||
|
this.minDefaultBtn = new System.Windows.Forms.Button();
|
||||||
|
this.charToReplaceCol = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
|
this.replacementStringCol = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
|
this.descriptionCol = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
|
||||||
|
this.SuspendLayout();
|
||||||
|
//
|
||||||
|
// dataGridView1
|
||||||
|
//
|
||||||
|
this.dataGridView1.AllowUserToResizeColumns = false;
|
||||||
|
this.dataGridView1.AllowUserToResizeRows = false;
|
||||||
|
this.dataGridView1.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.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||||
|
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||||
|
this.charToReplaceCol,
|
||||||
|
this.replacementStringCol,
|
||||||
|
this.descriptionCol});
|
||||||
|
this.dataGridView1.Location = new System.Drawing.Point(12, 12);
|
||||||
|
this.dataGridView1.Name = "dataGridView1";
|
||||||
|
this.dataGridView1.RowTemplate.Height = 25;
|
||||||
|
this.dataGridView1.Size = new System.Drawing.Size(498, 393);
|
||||||
|
this.dataGridView1.TabIndex = 0;
|
||||||
|
this.dataGridView1.CellEndEdit += new System.Windows.Forms.DataGridViewCellEventHandler(this.dataGridView1_CellEndEdit);
|
||||||
|
this.dataGridView1.UserDeletingRow += new System.Windows.Forms.DataGridViewRowCancelEventHandler(this.dataGridView1_UserDeletingRow);
|
||||||
|
this.dataGridView1.Resize += new System.EventHandler(this.dataGridView1_Resize);
|
||||||
|
//
|
||||||
|
// defaultsBtn
|
||||||
|
//
|
||||||
|
this.defaultsBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.defaultsBtn.Location = new System.Drawing.Point(12, 430);
|
||||||
|
this.defaultsBtn.Name = "defaultsBtn";
|
||||||
|
this.defaultsBtn.Size = new System.Drawing.Size(64, 25);
|
||||||
|
this.defaultsBtn.TabIndex = 1;
|
||||||
|
this.defaultsBtn.Text = "Defaults";
|
||||||
|
this.defaultsBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.defaultsBtn.Click += new System.EventHandler(this.defaultsBtn_Click);
|
||||||
|
//
|
||||||
|
// loFiDefaultsBtn
|
||||||
|
//
|
||||||
|
this.loFiDefaultsBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.loFiDefaultsBtn.Location = new System.Drawing.Point(82, 430);
|
||||||
|
this.loFiDefaultsBtn.Name = "loFiDefaultsBtn";
|
||||||
|
this.loFiDefaultsBtn.Size = new System.Drawing.Size(84, 25);
|
||||||
|
this.loFiDefaultsBtn.TabIndex = 1;
|
||||||
|
this.loFiDefaultsBtn.Text = "LoFi Defaults";
|
||||||
|
this.loFiDefaultsBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.loFiDefaultsBtn.Click += new System.EventHandler(this.loFiDefaultsBtn_Click);
|
||||||
|
//
|
||||||
|
// saveBtn
|
||||||
|
//
|
||||||
|
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.saveBtn.Location = new System.Drawing.Point(428, 430);
|
||||||
|
this.saveBtn.Name = "saveBtn";
|
||||||
|
this.saveBtn.Size = new System.Drawing.Size(82, 25);
|
||||||
|
this.saveBtn.TabIndex = 1;
|
||||||
|
this.saveBtn.Text = "Save";
|
||||||
|
this.saveBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
||||||
|
//
|
||||||
|
// cancelBtn
|
||||||
|
//
|
||||||
|
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.cancelBtn.Location = new System.Drawing.Point(340, 430);
|
||||||
|
this.cancelBtn.Name = "cancelBtn";
|
||||||
|
this.cancelBtn.Size = new System.Drawing.Size(82, 25);
|
||||||
|
this.cancelBtn.TabIndex = 1;
|
||||||
|
this.cancelBtn.Text = "Cancel";
|
||||||
|
this.cancelBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
||||||
|
//
|
||||||
|
// minDefaultBtn
|
||||||
|
//
|
||||||
|
this.minDefaultBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||||
|
this.minDefaultBtn.Location = new System.Drawing.Point(172, 430);
|
||||||
|
this.minDefaultBtn.Name = "minDefaultBtn";
|
||||||
|
this.minDefaultBtn.Size = new System.Drawing.Size(80, 25);
|
||||||
|
this.minDefaultBtn.TabIndex = 1;
|
||||||
|
this.minDefaultBtn.Text = "Barebones";
|
||||||
|
this.minDefaultBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.minDefaultBtn.Click += new System.EventHandler(this.minDefaultBtn_Click);
|
||||||
|
//
|
||||||
|
// charToReplaceCol
|
||||||
|
//
|
||||||
|
this.charToReplaceCol.HeaderText = "Char to Replace";
|
||||||
|
this.charToReplaceCol.MinimumWidth = 70;
|
||||||
|
this.charToReplaceCol.Name = "charToReplaceCol";
|
||||||
|
this.charToReplaceCol.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
||||||
|
this.charToReplaceCol.Width = 70;
|
||||||
|
//
|
||||||
|
// replacementStringCol
|
||||||
|
//
|
||||||
|
this.replacementStringCol.HeaderText = "Replacement Text";
|
||||||
|
this.replacementStringCol.MinimumWidth = 85;
|
||||||
|
this.replacementStringCol.Name = "replacementStringCol";
|
||||||
|
this.replacementStringCol.Width = 85;
|
||||||
|
//
|
||||||
|
// descriptionCol
|
||||||
|
//
|
||||||
|
this.descriptionCol.HeaderText = "Description";
|
||||||
|
this.descriptionCol.MinimumWidth = 100;
|
||||||
|
this.descriptionCol.Name = "descriptionCol";
|
||||||
|
this.descriptionCol.Width = 200;
|
||||||
|
//
|
||||||
|
// EditReplacementChars
|
||||||
|
//
|
||||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
|
this.ClientSize = new System.Drawing.Size(522, 467);
|
||||||
|
this.Controls.Add(this.minDefaultBtn);
|
||||||
|
this.Controls.Add(this.loFiDefaultsBtn);
|
||||||
|
this.Controls.Add(this.cancelBtn);
|
||||||
|
this.Controls.Add(this.saveBtn);
|
||||||
|
this.Controls.Add(this.defaultsBtn);
|
||||||
|
this.Controls.Add(this.dataGridView1);
|
||||||
|
this.Name = "EditReplacementChars";
|
||||||
|
this.Text = "Character Replacements";
|
||||||
|
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
|
||||||
|
this.ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private System.Windows.Forms.DataGridView dataGridView1;
|
||||||
|
private System.Windows.Forms.Button defaultsBtn;
|
||||||
|
private System.Windows.Forms.Button loFiDefaultsBtn;
|
||||||
|
private System.Windows.Forms.Button saveBtn;
|
||||||
|
private System.Windows.Forms.Button cancelBtn;
|
||||||
|
private System.Windows.Forms.Button minDefaultBtn;
|
||||||
|
private System.Windows.Forms.DataGridViewTextBoxColumn charToReplaceCol;
|
||||||
|
private System.Windows.Forms.DataGridViewTextBoxColumn replacementStringCol;
|
||||||
|
private System.Windows.Forms.DataGridViewTextBoxColumn descriptionCol;
|
||||||
|
}
|
||||||
|
}
|
||||||
144
Source/LibationWinForms/Dialogs/EditReplacementChars.cs
Normal file
144
Source/LibationWinForms/Dialogs/EditReplacementChars.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
using FileManager;
|
||||||
|
using LibationFileManager;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace LibationWinForms.Dialogs
|
||||||
|
{
|
||||||
|
public partial class EditReplacementChars : Form
|
||||||
|
{
|
||||||
|
Configuration config;
|
||||||
|
public EditReplacementChars()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
dataGridView1_Resize(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditReplacementChars(Configuration config) : this()
|
||||||
|
{
|
||||||
|
this.config = config;
|
||||||
|
LoadTable(config.ReplacementCharacters.Replacements);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadTable(IReadOnlyList<Replacement> replacements)
|
||||||
|
{
|
||||||
|
dataGridView1.Rows.Clear();
|
||||||
|
for (int i = 0; i < replacements.Count; i++)
|
||||||
|
{
|
||||||
|
var r = replacements[i];
|
||||||
|
|
||||||
|
int row = dataGridView1.Rows.Add(r.CharacterToReplace, r.ReplacementString, r.Description);
|
||||||
|
dataGridView1.Rows[row].Tag = r;
|
||||||
|
|
||||||
|
|
||||||
|
if (r.Mandatory)
|
||||||
|
{
|
||||||
|
dataGridView1.Rows[row].Cells[charToReplaceCol.Index].ReadOnly = true;
|
||||||
|
dataGridView1.Rows[row].Cells[descriptionCol.Index].ReadOnly = true;
|
||||||
|
dataGridView1.Rows[row].Cells[charToReplaceCol.Index].Style.BackColor = System.Drawing.Color.LightGray;
|
||||||
|
dataGridView1.Rows[row].Cells[descriptionCol.Index].Style.BackColor = System.Drawing.Color.LightGray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dataGridView1_UserDeletingRow(object sender, DataGridViewRowCancelEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Row?.Tag is Replacement r && r.Mandatory)
|
||||||
|
e.Cancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loFiDefaultsBtn_Click(object sender, EventArgs e)
|
||||||
|
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
|
||||||
|
|
||||||
|
private void defaultsBtn_Click(object sender, EventArgs e)
|
||||||
|
=> LoadTable(ReplacementCharacters.Default.Replacements);
|
||||||
|
|
||||||
|
private void minDefaultBtn_Click(object sender, EventArgs e)
|
||||||
|
=> LoadTable(ReplacementCharacters.Minimum.Replacements);
|
||||||
|
|
||||||
|
|
||||||
|
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.RowIndex < 0) return;
|
||||||
|
|
||||||
|
dataGridView1.Rows[e.RowIndex].ErrorText = string.Empty;
|
||||||
|
|
||||||
|
var charToReplaceStr = dataGridView1.Rows[e.RowIndex].Cells[charToReplaceCol.Index].Value?.ToString();
|
||||||
|
var replacement = dataGridView1.Rows[e.RowIndex].Cells[replacementStringCol.Index].Value?.ToString() ?? string.Empty;
|
||||||
|
var description = dataGridView1.Rows[e.RowIndex].Cells[descriptionCol.Index].Value?.ToString() ?? string.Empty;
|
||||||
|
|
||||||
|
//Validate the whole row. If it passes all validation, add or update the row's tag.
|
||||||
|
if (string.IsNullOrEmpty(charToReplaceStr) && replacement == string.Empty && description == string.Empty)
|
||||||
|
{
|
||||||
|
//Invalid entry, so delete row
|
||||||
|
var row = dataGridView1.Rows[e.RowIndex];
|
||||||
|
if (!row.IsNewRow)
|
||||||
|
{
|
||||||
|
BeginInvoke(new MethodInvoker(delegate
|
||||||
|
{
|
||||||
|
dataGridView1.Rows.Remove(row);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(charToReplaceStr))
|
||||||
|
{
|
||||||
|
dataGridView1.Rows[e.RowIndex].ErrorText = $"You must choose a character to replace";
|
||||||
|
}
|
||||||
|
else if (charToReplaceStr.Length > 1)
|
||||||
|
{
|
||||||
|
dataGridView1.Rows[e.RowIndex].ErrorText = $"Only 1 {charToReplaceCol.HeaderText} per entry";
|
||||||
|
}
|
||||||
|
else if (e.RowIndex >= Replacement.FIXED_COUNT &&
|
||||||
|
dataGridView1.Rows
|
||||||
|
.Cast<DataGridViewRow>()
|
||||||
|
.Where(r => r.Index != e.RowIndex)
|
||||||
|
.Select(r => r.Tag)
|
||||||
|
.OfType<Replacement>()
|
||||||
|
.Any(r => r.CharacterToReplace == charToReplaceStr[0])
|
||||||
|
)
|
||||||
|
{
|
||||||
|
dataGridView1.Rows[e.RowIndex].ErrorText = $"The {charToReplaceStr[0]} character is already being replaced";
|
||||||
|
}
|
||||||
|
else if (ReplacementCharacters.ContainsInvalid(replacement))
|
||||||
|
{
|
||||||
|
dataGridView1.Rows[e.RowIndex].ErrorText = $"Your {replacementStringCol.HeaderText} contains illegal characters";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//valid entry. Add or update Replacement in row's Tag
|
||||||
|
var charToReplace = charToReplaceStr[0];
|
||||||
|
|
||||||
|
if (dataGridView1.Rows[e.RowIndex].Tag is Replacement existing)
|
||||||
|
existing.Update(charToReplace, replacement, description);
|
||||||
|
else
|
||||||
|
dataGridView1.Rows[e.RowIndex].Tag = new Replacement(charToReplace, replacement, description);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveBtn_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var replacements = dataGridView1.Rows
|
||||||
|
.Cast<DataGridViewRow>()
|
||||||
|
.Select(r => r.Tag)
|
||||||
|
.OfType<Replacement>()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
config.ReplacementCharacters = new ReplacementCharacters { Replacements = replacements };
|
||||||
|
DialogResult = DialogResult.OK;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cancelBtn_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = DialogResult.Cancel;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dataGridView1_Resize(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
dataGridView1.Columns[^1].Width = dataGridView1.Width - dataGridView1.Columns.Cast<DataGridViewColumn>().Sum(c => c == dataGridView1.Columns[^1] ? 0 : c.Width) - dataGridView1.RowHeadersWidth - 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
Source/LibationWinForms/Dialogs/EditReplacementChars.resx
Normal file
69
Source/LibationWinForms/Dialogs/EditReplacementChars.resx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<root>
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<metadata name="charToReplaceCol.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
|
<value>True</value>
|
||||||
|
</metadata>
|
||||||
|
<metadata name="replacementStringCol.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
|
<value>True</value>
|
||||||
|
</metadata>
|
||||||
|
<metadata name="descriptionCol.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
|
<value>True</value>
|
||||||
|
</metadata>
|
||||||
|
</root>
|
||||||
@ -60,6 +60,7 @@
|
|||||||
this.tab3DownloadDecrypt = new System.Windows.Forms.TabPage();
|
this.tab3DownloadDecrypt = new System.Windows.Forms.TabPage();
|
||||||
this.inProgressFilesGb = new System.Windows.Forms.GroupBox();
|
this.inProgressFilesGb = new System.Windows.Forms.GroupBox();
|
||||||
this.customFileNamingGb = new System.Windows.Forms.GroupBox();
|
this.customFileNamingGb = new System.Windows.Forms.GroupBox();
|
||||||
|
this.editCharreplacementBtn = new System.Windows.Forms.Button();
|
||||||
this.chapterFileTemplateBtn = new System.Windows.Forms.Button();
|
this.chapterFileTemplateBtn = new System.Windows.Forms.Button();
|
||||||
this.chapterFileTemplateTb = new System.Windows.Forms.TextBox();
|
this.chapterFileTemplateTb = new System.Windows.Forms.TextBox();
|
||||||
this.chapterFileTemplateLbl = new System.Windows.Forms.Label();
|
this.chapterFileTemplateLbl = new System.Windows.Forms.Label();
|
||||||
@ -148,7 +149,7 @@
|
|||||||
// saveBtn
|
// saveBtn
|
||||||
//
|
//
|
||||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.saveBtn.Location = new System.Drawing.Point(667, 441);
|
this.saveBtn.Location = new System.Drawing.Point(667, 461);
|
||||||
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.saveBtn.Name = "saveBtn";
|
this.saveBtn.Name = "saveBtn";
|
||||||
this.saveBtn.Size = new System.Drawing.Size(88, 27);
|
this.saveBtn.Size = new System.Drawing.Size(88, 27);
|
||||||
@ -161,7 +162,7 @@
|
|||||||
//
|
//
|
||||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||||
this.cancelBtn.Location = new System.Drawing.Point(785, 441);
|
this.cancelBtn.Location = new System.Drawing.Point(785, 461);
|
||||||
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.cancelBtn.Name = "cancelBtn";
|
this.cancelBtn.Name = "cancelBtn";
|
||||||
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
|
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
|
||||||
@ -365,7 +366,7 @@
|
|||||||
this.tabControl.Location = new System.Drawing.Point(12, 12);
|
this.tabControl.Location = new System.Drawing.Point(12, 12);
|
||||||
this.tabControl.Name = "tabControl";
|
this.tabControl.Name = "tabControl";
|
||||||
this.tabControl.SelectedIndex = 0;
|
this.tabControl.SelectedIndex = 0;
|
||||||
this.tabControl.Size = new System.Drawing.Size(862, 423);
|
this.tabControl.Size = new System.Drawing.Size(862, 443);
|
||||||
this.tabControl.TabIndex = 100;
|
this.tabControl.TabIndex = 100;
|
||||||
//
|
//
|
||||||
// tab1ImportantSettings
|
// tab1ImportantSettings
|
||||||
@ -377,7 +378,7 @@
|
|||||||
this.tab1ImportantSettings.Location = new System.Drawing.Point(4, 24);
|
this.tab1ImportantSettings.Location = new System.Drawing.Point(4, 24);
|
||||||
this.tab1ImportantSettings.Name = "tab1ImportantSettings";
|
this.tab1ImportantSettings.Name = "tab1ImportantSettings";
|
||||||
this.tab1ImportantSettings.Padding = new System.Windows.Forms.Padding(3);
|
this.tab1ImportantSettings.Padding = new System.Windows.Forms.Padding(3);
|
||||||
this.tab1ImportantSettings.Size = new System.Drawing.Size(854, 395);
|
this.tab1ImportantSettings.Size = new System.Drawing.Size(854, 415);
|
||||||
this.tab1ImportantSettings.TabIndex = 0;
|
this.tab1ImportantSettings.TabIndex = 0;
|
||||||
this.tab1ImportantSettings.Text = "Important settings";
|
this.tab1ImportantSettings.Text = "Important settings";
|
||||||
this.tab1ImportantSettings.UseVisualStyleBackColor = true;
|
this.tab1ImportantSettings.UseVisualStyleBackColor = true;
|
||||||
@ -416,7 +417,7 @@
|
|||||||
this.tab2ImportLibrary.Location = new System.Drawing.Point(4, 24);
|
this.tab2ImportLibrary.Location = new System.Drawing.Point(4, 24);
|
||||||
this.tab2ImportLibrary.Name = "tab2ImportLibrary";
|
this.tab2ImportLibrary.Name = "tab2ImportLibrary";
|
||||||
this.tab2ImportLibrary.Padding = new System.Windows.Forms.Padding(3);
|
this.tab2ImportLibrary.Padding = new System.Windows.Forms.Padding(3);
|
||||||
this.tab2ImportLibrary.Size = new System.Drawing.Size(854, 395);
|
this.tab2ImportLibrary.Size = new System.Drawing.Size(854, 415);
|
||||||
this.tab2ImportLibrary.TabIndex = 1;
|
this.tab2ImportLibrary.TabIndex = 1;
|
||||||
this.tab2ImportLibrary.Text = "Import library";
|
this.tab2ImportLibrary.Text = "Import library";
|
||||||
this.tab2ImportLibrary.UseVisualStyleBackColor = true;
|
this.tab2ImportLibrary.UseVisualStyleBackColor = true;
|
||||||
@ -459,7 +460,7 @@
|
|||||||
this.tab3DownloadDecrypt.Location = new System.Drawing.Point(4, 24);
|
this.tab3DownloadDecrypt.Location = new System.Drawing.Point(4, 24);
|
||||||
this.tab3DownloadDecrypt.Name = "tab3DownloadDecrypt";
|
this.tab3DownloadDecrypt.Name = "tab3DownloadDecrypt";
|
||||||
this.tab3DownloadDecrypt.Padding = new System.Windows.Forms.Padding(3);
|
this.tab3DownloadDecrypt.Padding = new System.Windows.Forms.Padding(3);
|
||||||
this.tab3DownloadDecrypt.Size = new System.Drawing.Size(854, 395);
|
this.tab3DownloadDecrypt.Size = new System.Drawing.Size(854, 415);
|
||||||
this.tab3DownloadDecrypt.TabIndex = 2;
|
this.tab3DownloadDecrypt.TabIndex = 2;
|
||||||
this.tab3DownloadDecrypt.Text = "Download/Decrypt";
|
this.tab3DownloadDecrypt.Text = "Download/Decrypt";
|
||||||
this.tab3DownloadDecrypt.UseVisualStyleBackColor = true;
|
this.tab3DownloadDecrypt.UseVisualStyleBackColor = true;
|
||||||
@ -470,7 +471,7 @@
|
|||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
this.inProgressFilesGb.Controls.Add(this.inProgressDescLbl);
|
this.inProgressFilesGb.Controls.Add(this.inProgressDescLbl);
|
||||||
this.inProgressFilesGb.Controls.Add(this.inProgressSelectControl);
|
this.inProgressFilesGb.Controls.Add(this.inProgressSelectControl);
|
||||||
this.inProgressFilesGb.Location = new System.Drawing.Point(7, 251);
|
this.inProgressFilesGb.Location = new System.Drawing.Point(6, 281);
|
||||||
this.inProgressFilesGb.Name = "inProgressFilesGb";
|
this.inProgressFilesGb.Name = "inProgressFilesGb";
|
||||||
this.inProgressFilesGb.Size = new System.Drawing.Size(841, 128);
|
this.inProgressFilesGb.Size = new System.Drawing.Size(841, 128);
|
||||||
this.inProgressFilesGb.TabIndex = 21;
|
this.inProgressFilesGb.TabIndex = 21;
|
||||||
@ -481,6 +482,7 @@
|
|||||||
//
|
//
|
||||||
this.customFileNamingGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
this.customFileNamingGb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
| System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.customFileNamingGb.Controls.Add(this.editCharreplacementBtn);
|
||||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateBtn);
|
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateBtn);
|
||||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateTb);
|
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateTb);
|
||||||
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateLbl);
|
this.customFileNamingGb.Controls.Add(this.chapterFileTemplateLbl);
|
||||||
@ -492,11 +494,22 @@
|
|||||||
this.customFileNamingGb.Controls.Add(this.folderTemplateLbl);
|
this.customFileNamingGb.Controls.Add(this.folderTemplateLbl);
|
||||||
this.customFileNamingGb.Location = new System.Drawing.Point(7, 88);
|
this.customFileNamingGb.Location = new System.Drawing.Point(7, 88);
|
||||||
this.customFileNamingGb.Name = "customFileNamingGb";
|
this.customFileNamingGb.Name = "customFileNamingGb";
|
||||||
this.customFileNamingGb.Size = new System.Drawing.Size(841, 157);
|
this.customFileNamingGb.Size = new System.Drawing.Size(841, 187);
|
||||||
this.customFileNamingGb.TabIndex = 20;
|
this.customFileNamingGb.TabIndex = 20;
|
||||||
this.customFileNamingGb.TabStop = false;
|
this.customFileNamingGb.TabStop = false;
|
||||||
this.customFileNamingGb.Text = "Custom file naming";
|
this.customFileNamingGb.Text = "Custom file naming";
|
||||||
//
|
//
|
||||||
|
// editCharreplacementBtn
|
||||||
|
//
|
||||||
|
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.Name = "editCharreplacementBtn";
|
||||||
|
this.editCharreplacementBtn.Size = new System.Drawing.Size(387, 23);
|
||||||
|
this.editCharreplacementBtn.TabIndex = 8;
|
||||||
|
this.editCharreplacementBtn.Text = "[edit char replacement desc]";
|
||||||
|
this.editCharreplacementBtn.UseVisualStyleBackColor = true;
|
||||||
|
this.editCharreplacementBtn.Click += new System.EventHandler(this.editCharreplacementBtn_Click);
|
||||||
|
//
|
||||||
// chapterFileTemplateBtn
|
// chapterFileTemplateBtn
|
||||||
//
|
//
|
||||||
this.chapterFileTemplateBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
this.chapterFileTemplateBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
@ -603,7 +616,7 @@
|
|||||||
this.tab4AudioFileOptions.Location = new System.Drawing.Point(4, 24);
|
this.tab4AudioFileOptions.Location = new System.Drawing.Point(4, 24);
|
||||||
this.tab4AudioFileOptions.Name = "tab4AudioFileOptions";
|
this.tab4AudioFileOptions.Name = "tab4AudioFileOptions";
|
||||||
this.tab4AudioFileOptions.Padding = new System.Windows.Forms.Padding(3);
|
this.tab4AudioFileOptions.Padding = new System.Windows.Forms.Padding(3);
|
||||||
this.tab4AudioFileOptions.Size = new System.Drawing.Size(854, 395);
|
this.tab4AudioFileOptions.Size = new System.Drawing.Size(854, 415);
|
||||||
this.tab4AudioFileOptions.TabIndex = 3;
|
this.tab4AudioFileOptions.TabIndex = 3;
|
||||||
this.tab4AudioFileOptions.Text = "Audio File Options";
|
this.tab4AudioFileOptions.Text = "Audio File Options";
|
||||||
this.tab4AudioFileOptions.UseVisualStyleBackColor = true;
|
this.tab4AudioFileOptions.UseVisualStyleBackColor = true;
|
||||||
@ -1017,7 +1030,7 @@
|
|||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||||
this.CancelButton = this.cancelBtn;
|
this.CancelButton = this.cancelBtn;
|
||||||
this.ClientSize = new System.Drawing.Size(886, 484);
|
this.ClientSize = new System.Drawing.Size(886, 504);
|
||||||
this.Controls.Add(this.tabControl);
|
this.Controls.Add(this.tabControl);
|
||||||
this.Controls.Add(this.cancelBtn);
|
this.Controls.Add(this.cancelBtn);
|
||||||
this.Controls.Add(this.saveBtn);
|
this.Controls.Add(this.saveBtn);
|
||||||
@ -1141,5 +1154,6 @@
|
|||||||
private System.Windows.Forms.GroupBox chapterTitleTemplateGb;
|
private System.Windows.Forms.GroupBox chapterTitleTemplateGb;
|
||||||
private System.Windows.Forms.Button chapterTitleTemplateBtn;
|
private System.Windows.Forms.Button chapterTitleTemplateBtn;
|
||||||
private System.Windows.Forms.TextBox chapterTitleTemplateTb;
|
private System.Windows.Forms.TextBox chapterTitleTemplateTb;
|
||||||
|
private System.Windows.Forms.Button editCharreplacementBtn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -11,9 +11,19 @@ namespace LibationWinForms.Dialogs
|
|||||||
private void fileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.File, fileTemplateTb);
|
private void fileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.File, fileTemplateTb);
|
||||||
private void chapterFileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.ChapterFile, chapterFileTemplateTb);
|
private void chapterFileTemplateBtn_Click(object sender, EventArgs e) => editTemplate(Templates.ChapterFile, chapterFileTemplateTb);
|
||||||
|
|
||||||
|
|
||||||
|
private void editCharreplacementBtn_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var form = new EditReplacementChars(config);
|
||||||
|
form.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
|
form.ShowDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
private void Load_DownloadDecrypt(Configuration config)
|
private void Load_DownloadDecrypt(Configuration config)
|
||||||
{
|
{
|
||||||
inProgressDescLbl.Text = desc(nameof(config.InProgress));
|
inProgressDescLbl.Text = desc(nameof(config.InProgress));
|
||||||
|
editCharreplacementBtn.Text = desc(nameof(config.ReplacementCharacters));
|
||||||
|
|
||||||
badBookGb.Text = desc(nameof(config.BadBook));
|
badBookGb.Text = desc(nameof(config.BadBook));
|
||||||
badBookAskRb.Text = Configuration.BadBookAction.Ask.GetDescription();
|
badBookAskRb.Text = Configuration.BadBookAction.Ask.GetDescription();
|
||||||
badBookAbortRb.Text = Configuration.BadBookAction.Abort.GetDescription();
|
badBookAbortRb.Text = Configuration.BadBookAction.Abort.GetDescription();
|
||||||
|
|||||||
@ -33,7 +33,18 @@ namespace LibationWinForms
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
// in autoScan, new books SHALL NOT show dialog
|
// in autoScan, new books SHALL NOT show dialog
|
||||||
await Invoke(async () => await LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts));
|
try
|
||||||
|
{
|
||||||
|
Task importAsync() => LibraryCommands.ImportAccountAsync(Login.WinformLoginChoiceEager.ApiExtendedFunc, accounts);
|
||||||
|
if (InvokeRequired)
|
||||||
|
await Invoke(importAsync);
|
||||||
|
else
|
||||||
|
await importAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Error(ex, "Error invoking auto-scan");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// load init state to menu checkbox
|
// load init state to menu checkbox
|
||||||
|
|||||||
@ -12,6 +12,8 @@ namespace FileNamingTemplateTests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetFilePath
|
public class GetFilePath
|
||||||
{
|
{
|
||||||
|
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void equiv_GetValidFilename()
|
public void equiv_GetValidFilename()
|
||||||
{
|
{
|
||||||
@ -19,81 +21,33 @@ namespace FileNamingTemplateTests
|
|||||||
sb.Append('0', 300);
|
sb.Append('0', 300);
|
||||||
var longText = sb.ToString();
|
var longText = sb.ToString();
|
||||||
|
|
||||||
var expectedOld = "C:\\foo\\bar\\my_ book 00000000000000000000000000000000000000000 [ID123456].txt";
|
|
||||||
var expectedNew = "C:\\foo\\bar\\my꞉ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [ID123456].txt";
|
var expectedNew = "C:\\foo\\bar\\my꞉ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [ID123456].txt";
|
||||||
var f1 = OLD_GetValidFilename(@"C:\foo\bar", "my: book " + longText, "txt", "ID123456");
|
|
||||||
var f2 = NEW_GetValidFilename_FileNamingTemplate(@"C:\foo\bar", "my: book " + longText, "txt", "ID123456");
|
var f2 = NEW_GetValidFilename_FileNamingTemplate(@"C:\foo\bar", "my: book " + longText, "txt", "ID123456");
|
||||||
|
|
||||||
f1.Should().Be(expectedOld);
|
|
||||||
f2.Should().Be(expectedNew);
|
f2.Should().Be(expectedNew);
|
||||||
}
|
}
|
||||||
private static string OLD_GetValidFilename(string dirFullPath, string filename, string extension, string metadataSuffix)
|
|
||||||
{
|
|
||||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(dirFullPath, nameof(dirFullPath));
|
|
||||||
|
|
||||||
filename ??= "";
|
|
||||||
|
|
||||||
// sanitize. omit invalid characters. exception: colon => underscore
|
|
||||||
filename = filename.Replace(":", "_");
|
|
||||||
filename = FileUtility.GetSafeFileName(filename);
|
|
||||||
|
|
||||||
if (filename.Length > 50)
|
|
||||||
filename = filename.Substring(0, 50);
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(metadataSuffix))
|
|
||||||
filename += $" [{metadataSuffix}]";
|
|
||||||
|
|
||||||
// extension is null when this method is used for directory names
|
|
||||||
extension = FileUtility.GetStandardizedExtension(extension);
|
|
||||||
|
|
||||||
// ensure uniqueness
|
|
||||||
var fullfilename = Path.Combine(dirFullPath, filename + extension);
|
|
||||||
var i = 0;
|
|
||||||
while (File.Exists(fullfilename))
|
|
||||||
fullfilename = Path.Combine(dirFullPath, filename + $" ({++i})" + extension);
|
|
||||||
|
|
||||||
return fullfilename;
|
|
||||||
}
|
|
||||||
private static string NEW_GetValidFilename_FileNamingTemplate(string dirFullPath, string filename, string extension, string metadataSuffix)
|
private static string NEW_GetValidFilename_FileNamingTemplate(string dirFullPath, string filename, string extension, string metadataSuffix)
|
||||||
{
|
{
|
||||||
var template = $"<title> [<id>]";
|
var template = $"<title> [<id>]";
|
||||||
|
|
||||||
var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension));
|
var fullfilename = Path.Combine(dirFullPath, template + FileUtility.GetStandardizedExtension(extension));
|
||||||
|
|
||||||
var fileNamingTemplate = new FileNamingTemplate(fullfilename) { IllegalCharacterReplacements = "_" };
|
var fileNamingTemplate = new FileNamingTemplate(fullfilename);
|
||||||
fileNamingTemplate.AddParameterReplacement("title", filename);
|
fileNamingTemplate.AddParameterReplacement("title", filename);
|
||||||
fileNamingTemplate.AddParameterReplacement("id", metadataSuffix);
|
fileNamingTemplate.AddParameterReplacement("id", metadataSuffix);
|
||||||
return fileNamingTemplate.GetFilePath().PathWithoutPrefix;
|
return fileNamingTemplate.GetFilePath(Replacements).PathWithoutPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void equiv_GetMultipartFileName()
|
public void equiv_GetMultipartFileName()
|
||||||
{
|
{
|
||||||
var expected = @"C:\foo\bar\my file - 002 - title.txt";
|
var expected = @"C:\foo\bar\my file - 002 - title.txt";
|
||||||
var f1 = OLD_GetMultipartFileName(@"C:\foo\bar\my file.txt", 2, 100, "title");
|
|
||||||
var f2 = NEW_GetMultipartFileName_FileNamingTemplate(@"C:\foo\bar\my file.txt", 2, 100, "title");
|
var f2 = NEW_GetMultipartFileName_FileNamingTemplate(@"C:\foo\bar\my file.txt", 2, 100, "title");
|
||||||
|
|
||||||
f1.Should().Be(expected);
|
f2.Should().Be(expected);
|
||||||
f1.Should().Be(f2);
|
|
||||||
}
|
}
|
||||||
private static string OLD_GetMultipartFileName(string originalPath, int partsPosition, int partsTotal, string suffix)
|
|
||||||
{
|
|
||||||
// 1-9 => 1-9
|
|
||||||
// 10-99 => 01-99
|
|
||||||
// 100-999 => 001-999
|
|
||||||
var chapterCountLeadingZeros = partsPosition.ToString().PadLeft(partsTotal.ToString().Length, '0');
|
|
||||||
|
|
||||||
string extension = Path.GetExtension(originalPath);
|
|
||||||
|
|
||||||
var filenameBase = $"{Path.GetFileNameWithoutExtension(originalPath)} - {chapterCountLeadingZeros}";
|
|
||||||
if (!string.IsNullOrWhiteSpace(suffix))
|
|
||||||
filenameBase += $" - {suffix}";
|
|
||||||
|
|
||||||
// Replace illegal path characters with spaces
|
|
||||||
var fileName = FileUtility.GetSafeFileName(filenameBase, " ");
|
|
||||||
var path = Path.Combine(Path.GetDirectoryName(originalPath), fileName + extension);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
private static string NEW_GetMultipartFileName_FileNamingTemplate(string originalPath, int partsPosition, int partsTotal, string suffix)
|
private static string NEW_GetMultipartFileName_FileNamingTemplate(string originalPath, int partsPosition, int partsTotal, string suffix)
|
||||||
{
|
{
|
||||||
// 1-9 => 1-9
|
// 1-9 => 1-9
|
||||||
@ -103,10 +57,10 @@ namespace FileNamingTemplateTests
|
|||||||
|
|
||||||
var t = Path.ChangeExtension(originalPath, null) + " - <chapter> - <title>" + Path.GetExtension(originalPath);
|
var t = Path.ChangeExtension(originalPath, null) + " - <chapter> - <title>" + Path.GetExtension(originalPath);
|
||||||
|
|
||||||
var fileNamingTemplate = new FileNamingTemplate(t) { IllegalCharacterReplacements = " " };
|
var fileNamingTemplate = new FileNamingTemplate(t);
|
||||||
fileNamingTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros);
|
fileNamingTemplate.AddParameterReplacement("chapter", chapterCountLeadingZeros);
|
||||||
fileNamingTemplate.AddParameterReplacement("title", suffix);
|
fileNamingTemplate.AddParameterReplacement("title", suffix);
|
||||||
return fileNamingTemplate.GetFilePath().PathWithoutPrefix;
|
return fileNamingTemplate.GetFilePath(Replacements).PathWithoutPrefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@ -114,7 +68,7 @@ namespace FileNamingTemplateTests
|
|||||||
{
|
{
|
||||||
var fileNamingTemplate = new FileNamingTemplate(@"\foo\<title>.txt");
|
var fileNamingTemplate = new FileNamingTemplate(@"\foo\<title>.txt");
|
||||||
fileNamingTemplate.AddParameterReplacement("title", @"s\l/a\s/h\e/s");
|
fileNamingTemplate.AddParameterReplacement("title", @"s\l/a\s/h\e/s");
|
||||||
fileNamingTemplate.GetFilePath().PathWithoutPrefix.Should().Be(@"\foo\slashes.txt");
|
fileNamingTemplate.GetFilePath(Replacements).PathWithoutPrefix.Should().Be(@"\foo\sl∕as∕he∕s.txt");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,49 +12,88 @@ namespace FileUtilityTests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetSafePath
|
public class GetSafePath
|
||||||
{
|
{
|
||||||
[TestMethod]
|
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
|
||||||
public void null_path_throws() => Assert.ThrowsException<ArgumentNullException>(() => FileUtility.GetSafePath(null));
|
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault;
|
||||||
|
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Minimum;
|
||||||
|
|
||||||
// needs separate method. middle null param not running correctly in TestExplorer when used in DataRow()
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow("http://test.com/a/b/c", @"http꞉\test.com\a\b\c")]
|
public void null_path_throws() => Assert.ThrowsException<ArgumentNullException>(() => FileUtility.GetSafePath(null, Default));
|
||||||
public void null_replacement(string inStr, string outStr) => Tests(inStr, null, outStr);
|
|
||||||
|
[TestMethod]
|
||||||
|
// non-empty replacement
|
||||||
|
[DataRow("abc*abc.txt", "abc✱abc.txt")]
|
||||||
|
// standardize slashes
|
||||||
|
[DataRow(@"a/b\c/d", @"a\b\c\d")]
|
||||||
|
// remove illegal chars
|
||||||
|
[DataRow("a*?:z.txt", "a✱?꞉z.txt")]
|
||||||
|
// retain drive letter path colon
|
||||||
|
[DataRow(@"C:\az.txt", @"C:\az.txt")]
|
||||||
|
// replace all other colons
|
||||||
|
[DataRow(@"a\b:c\d.txt", @"a\b꞉c\d.txt")]
|
||||||
|
// remove empty directories
|
||||||
|
[DataRow(@"C:\a\\\b\c\\\d.txt", @"C:\a\b\c\d.txt")]
|
||||||
|
[DataRow(@"C:\""foo\<id>", @"C:\“foo\<id>")]
|
||||||
|
public void DefaultTests(string inStr, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, Default).PathWithoutPrefix);
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
// non-empty replacement
|
||||||
|
[DataRow("abc*abc.txt", "abc_abc.txt")]
|
||||||
|
// standardize slashes
|
||||||
|
[DataRow(@"a/b\c/d", @"a\b\c\d")]
|
||||||
|
// remove illegal chars
|
||||||
|
[DataRow("a*?:z.txt", "a__-z.txt")]
|
||||||
|
// retain drive letter path colon
|
||||||
|
[DataRow(@"C:\az.txt", @"C:\az.txt")]
|
||||||
|
// replace all other colons
|
||||||
|
[DataRow(@"a\b:c\d.txt", @"a\b-c\d.txt")]
|
||||||
|
// remove empty directories
|
||||||
|
[DataRow(@"C:\a\\\b\c\\\d.txt", @"C:\a\b\c\d.txt")]
|
||||||
|
[DataRow(@"C:\""foo\<id>", @"C:\'foo\{id}")]
|
||||||
|
public void LoFiDefaultTests(string inStr, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, LoFiDefault).PathWithoutPrefix);
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
// empty replacement
|
// empty replacement
|
||||||
[DataRow("abc*abc.txt", "", "abc✱abc.txt")]
|
[DataRow("abc*abc.txt", "abc_abc.txt")]
|
||||||
// non-empty replacement
|
|
||||||
[DataRow("abc*abc.txt", "ZZZ", "abc✱abc.txt")]
|
|
||||||
// standardize slashes
|
// standardize slashes
|
||||||
[DataRow(@"a/b\c/d", "Z", @"a\b\c\d")]
|
[DataRow(@"a/b\c/d", @"a\b\c\d")]
|
||||||
// remove illegal chars
|
// remove illegal chars
|
||||||
[DataRow("a*?:z.txt", "Z", "a✱?꞉z.txt")]
|
[DataRow("a*?:z.txt", "a___z.txt")]
|
||||||
// retain drive letter path colon
|
// retain drive letter path colon
|
||||||
[DataRow(@"C:\az.txt", "Z", @"C:\az.txt")]
|
[DataRow(@"C:\az.txt", @"C:\az.txt")]
|
||||||
// replace all other colons
|
// replace all other colons
|
||||||
[DataRow(@"a\b:c\d.txt", "ZZZ", @"a\b꞉c\d.txt")]
|
[DataRow(@"a\b:c\d.txt", @"a\b_c\d.txt")]
|
||||||
// remove empty directories
|
// remove empty directories
|
||||||
[DataRow(@"C:\a\\\b\c\\\d.txt", "ZZZ", @"C:\a\b\c\d.txt")]
|
[DataRow(@"C:\a\\\b\c\\\d.txt", @"C:\a\b\c\d.txt")]
|
||||||
[DataRow(@"C:\""foo\<id>", "ZZZ", @"C:\“foo\<id>")]
|
[DataRow(@"C:\""foo\<id>", @"C:\_foo\_id_")]
|
||||||
public void Tests(string inStr, string replacement, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, replacement).PathWithoutPrefix);
|
public void BarebonesDefaultTests(string inStr, string outStr) => Assert.AreEqual(outStr, FileUtility.GetSafePath(inStr, Barebones).PathWithoutPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetSafeFileName
|
public class GetSafeFileName
|
||||||
{
|
{
|
||||||
|
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
|
||||||
|
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault;
|
||||||
|
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Minimum;
|
||||||
|
|
||||||
// needs separate method. middle null param not running correctly in TestExplorer when used in DataRow()
|
// needs separate method. middle null param not running correctly in TestExplorer when used in DataRow()
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow("http://test.com/a/b/c", "httptest.comabc")]
|
[DataRow("http://test.com/a/b/c", "http꞉∕∕test.com∕a∕b∕c")]
|
||||||
public void url_null_replacement(string inStr, string outStr) => ReplacementTests(inStr, null, outStr);
|
public void url_null_replacement(string inStr, string outStr) => DefaultReplacementTest(inStr, outStr);
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
// empty replacement
|
// empty replacement
|
||||||
[DataRow("http://test.com/a/b/c", "", "httptest.comabc")]
|
[DataRow("http://test.com/a/b/c", "http꞉∕∕test.com∕a∕b∕c")]
|
||||||
// single char replace
|
public void DefaultReplacementTest(string inStr, string outStr) => Default.ReplaceInvalidFilenameChars(inStr).Should().Be(outStr);
|
||||||
[DataRow("http://test.com/a/b/c", "_", "http___test.com_a_b_c")]
|
|
||||||
// multi char replace
|
[TestMethod]
|
||||||
[DataRow("http://test.com/a/b/c", "!!!", "http!!!!!!!!!test.com!!!a!!!b!!!c")]
|
// empty replacement
|
||||||
public void ReplacementTests(string inStr, string replacement, string outStr) => FileUtility.GetSafeFileName(inStr, replacement).Should().Be(outStr);
|
[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);
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
// empty replacement
|
||||||
|
[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);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
@ -117,6 +156,8 @@ namespace FileUtilityTests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetValidFilename
|
public class GetValidFilename
|
||||||
{
|
{
|
||||||
|
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
// dot-files
|
// dot-files
|
||||||
[DataRow(@"C:\a bc\x y z\.f i l e.txt")]
|
[DataRow(@"C:\a bc\x y z\.f i l e.txt")]
|
||||||
@ -134,7 +175,7 @@ namespace FileUtilityTests
|
|||||||
// file end dots
|
// file end dots
|
||||||
[DataRow(@"C:\a bc\x y z\f i l e.txt . . .", @"C:\a bc\x y z\f i l e.txt")]
|
[DataRow(@"C:\a bc\x y z\f i l e.txt . . .", @"C:\a bc\x y z\f i l e.txt")]
|
||||||
public void Tests(string input, string expected)
|
public void Tests(string input, string expected)
|
||||||
=> FileUtility.GetValidFilename(input).PathWithoutPrefix.Should().Be(expected);
|
=> FileUtility.GetValidFilename(input, Replacements).PathWithoutPrefix.Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestClass]
|
[TestClass]
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
|
using FileManager;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
@ -51,6 +52,9 @@ namespace TemplatesTests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class getFileNamingTemplate
|
public class getFileNamingTemplate
|
||||||
{
|
{
|
||||||
|
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow(null, "asin", @"C:\", "ext")]
|
[DataRow(null, "asin", @"C:\", "ext")]
|
||||||
[ExpectedException(typeof(ArgumentNullException))]
|
[ExpectedException(typeof(ArgumentNullException))]
|
||||||
@ -73,28 +77,28 @@ namespace TemplatesTests
|
|||||||
[DataRow("<id>", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\asin.ext")]
|
[DataRow("<id>", "asin", @"C:\foo\bar", "ext", @"C:\foo\bar\asin.ext")]
|
||||||
public void Tests(string template, string asin, string dirFullPath, string extension, string expected)
|
public void Tests(string template, string asin, string dirFullPath, string extension, string expected)
|
||||||
=> Templates.getFileNamingTemplate(GetLibraryBook(asin), template, dirFullPath, extension)
|
=> Templates.getFileNamingTemplate(GetLibraryBook(asin), template, dirFullPath, extension)
|
||||||
.GetFilePath()
|
.GetFilePath(Replacements)
|
||||||
.PathWithoutPrefix
|
.PathWithoutPrefix
|
||||||
.Should().Be(expected);
|
.Should().Be(expected);
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void IfSeries_empty()
|
public void IfSeries_empty()
|
||||||
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", "Sherlock Holmes"), "foo<if series-><-if series>bar", @"C:\a\b", "ext")
|
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", "Sherlock Holmes"), "foo<if series-><-if series>bar", @"C:\a\b", "ext")
|
||||||
.GetFilePath()
|
.GetFilePath(Replacements)
|
||||||
.PathWithoutPrefix
|
.PathWithoutPrefix
|
||||||
.Should().Be(@"C:\a\b\foobar.ext");
|
.Should().Be(@"C:\a\b\foobar.ext");
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void IfSeries_no_series()
|
public void IfSeries_no_series()
|
||||||
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", ""), "foo<if series->-<series>-<id>-<-if series>bar", @"C:\a\b", "ext")
|
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", ""), "foo<if series->-<series>-<id>-<-if series>bar", @"C:\a\b", "ext")
|
||||||
.GetFilePath()
|
.GetFilePath(Replacements)
|
||||||
.PathWithoutPrefix
|
.PathWithoutPrefix
|
||||||
.Should().Be(@"C:\a\b\foobar.ext");
|
.Should().Be(@"C:\a\b\foobar.ext");
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void IfSeries_with_series()
|
public void IfSeries_with_series()
|
||||||
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", "Sherlock Holmes"), "foo<if series->-<series>-<id>-<-if series>bar", @"C:\a\b", "ext")
|
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", "Sherlock Holmes"), "foo<if series->-<series>-<id>-<-if series>bar", @"C:\a\b", "ext")
|
||||||
.GetFilePath()
|
.GetFilePath(Replacements)
|
||||||
.PathWithoutPrefix
|
.PathWithoutPrefix
|
||||||
.Should().Be(@"C:\a\b\foo-Sherlock Holmes-asin-bar.ext");
|
.Should().Be(@"C:\a\b\foo-Sherlock Holmes-asin-bar.ext");
|
||||||
}
|
}
|
||||||
@ -387,11 +391,13 @@ namespace Templates_ChapterFile_Tests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetPortionFilename
|
public class GetPortionFilename
|
||||||
{
|
{
|
||||||
|
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow("asin", "[<id>] <ch# 0> of <ch count> - <ch title>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\[asin] 06 of 10 - chap.txt")]
|
[DataRow("asin", "[<id>] <ch# 0> of <ch count> - <ch title>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\[asin] 06 of 10 - chap.txt")]
|
||||||
[DataRow("asin", "<ch#>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\6.txt")]
|
[DataRow("asin", "<ch#>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\6.txt")]
|
||||||
public void Tests(string asin, string template, string dir, string ext, int pos, int total, string chapter, string expected)
|
public void Tests(string asin, string template, string dir, string ext, int pos, int total, string chapter, string expected)
|
||||||
=> Templates.ChapterFile.GetPortionFilename(GetLibraryBook(asin), template, new() { OutputFileName = $"xyz.{ext}", PartsPosition = pos, PartsTotal = total, Title = chapter }, dir)
|
=> Templates.ChapterFile.GetPortionFilename(GetLibraryBook(asin), template, new() { OutputFileName = $"xyz.{ext}", PartsPosition = pos, PartsTotal = total, Title = chapter }, dir, Default)
|
||||||
.Should().Be(expected);
|
.Should().Be(expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user