Enable Nullable
This commit is contained in:
parent
e8c63e9a6e
commit
34033e7947
@ -5,6 +5,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -16,9 +17,9 @@ namespace FileManager
|
|||||||
public string SearchPattern { get; private set; }
|
public string SearchPattern { get; private set; }
|
||||||
public SearchOption SearchOption { get; private set; }
|
public SearchOption SearchOption { get; private set; }
|
||||||
|
|
||||||
private FileSystemWatcher fileSystemWatcher { get; set; }
|
private FileSystemWatcher? fileSystemWatcher { get; set; }
|
||||||
private BlockingCollection<FileSystemEventArgs> directoryChangesEvents { get; set; }
|
private BlockingCollection<FileSystemEventArgs>? directoryChangesEvents { get; set; }
|
||||||
private Task backgroundScanner { get; set; }
|
private Task? backgroundScanner { get; set; }
|
||||||
|
|
||||||
private object fsCacheLocker { get; } = new();
|
private object fsCacheLocker { get; } = new();
|
||||||
private List<LongPath> fsCache { get; } = new();
|
private List<LongPath> fsCache { get; } = new();
|
||||||
@ -32,7 +33,7 @@ namespace FileManager
|
|||||||
Init();
|
Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LongPath FindFile(System.Text.RegularExpressions.Regex regex)
|
public LongPath? FindFile(System.Text.RegularExpressions.Regex regex)
|
||||||
{
|
{
|
||||||
lock (fsCacheLocker)
|
lock (fsCacheLocker)
|
||||||
return fsCache.FirstOrDefault(s => regex.IsMatch(s));
|
return fsCache.FirstOrDefault(s => regex.IsMatch(s));
|
||||||
@ -105,13 +106,13 @@ namespace FileManager
|
|||||||
|
|
||||||
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
|
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
|
||||||
{
|
{
|
||||||
directoryChangesEvents.Add(e);
|
directoryChangesEvents?.Add(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Background Thread
|
#region Background Thread
|
||||||
private void BackgroundScanner()
|
private void BackgroundScanner()
|
||||||
{
|
{
|
||||||
while (directoryChangesEvents.TryTake(out FileSystemEventArgs change, -1))
|
while (directoryChangesEvents?.TryTake(out var change, -1) is true)
|
||||||
{
|
{
|
||||||
lock (fsCacheLocker)
|
lock (fsCacheLocker)
|
||||||
UpdateLocalCache(change);
|
UpdateLocalCache(change);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
@ -7,20 +8,20 @@ using Dinah.Core;
|
|||||||
using Polly;
|
using Polly;
|
||||||
using Polly.Retry;
|
using Polly.Retry;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
public static class FileUtility
|
public static class FileUtility
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "txt" => ".txt"
|
/// "txt" => ".txt"
|
||||||
/// <br />".txt" => ".txt"
|
/// <br />".txt" => ".txt"
|
||||||
/// <br />null or whitespace => ""
|
/// <br />null or whitespace => ""
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string GetStandardizedExtension(string extension)
|
[return: NotNull]
|
||||||
|
public static string GetStandardizedExtension(string? extension)
|
||||||
=> string.IsNullOrWhiteSpace(extension)
|
=> string.IsNullOrWhiteSpace(extension)
|
||||||
? (extension ?? "")?.Trim()
|
? string.Empty
|
||||||
: '.' + extension.Trim().Trim('.');
|
: '.' + extension.Trim().Trim('.');
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -48,18 +49,18 @@ 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, ReplacementCharacters replacements, string fileExtension, bool returnFirstExisting = false)
|
public static LongPath GetValidFilename(LongPath path, ReplacementCharacters replacements, string? fileExtension, bool returnFirstExisting = false)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
||||||
ArgumentValidator.EnsureNotNull(fileExtension, nameof(fileExtension));
|
ArgumentValidator.EnsureNotNull(replacements, nameof(replacements));
|
||||||
|
|
||||||
fileExtension = GetStandardizedExtension(fileExtension);
|
fileExtension = GetStandardizedExtension(fileExtension);
|
||||||
|
|
||||||
// remove invalid chars
|
// remove invalid chars
|
||||||
path = GetSafePath(path, replacements);
|
path = GetSafePath(path, replacements);
|
||||||
|
|
||||||
// ensure uniqueness and check lengths
|
// ensure uniqueness and check lengths
|
||||||
var dir = Path.GetDirectoryName(path);
|
var dir = Path.GetDirectoryName(path)?.TruncateFilename(LongPath.MaxDirectoryLength) ?? string.Empty;
|
||||||
dir = dir?.TruncateFilename(LongPath.MaxDirectoryLength) ?? string.Empty;
|
|
||||||
|
|
||||||
var fileName = Path.GetFileName(path);
|
var fileName = Path.GetFileName(path);
|
||||||
var extIndex = fileName.LastIndexOf(fileExtension, StringComparison.OrdinalIgnoreCase);
|
var extIndex = fileName.LastIndexOf(fileExtension, StringComparison.OrdinalIgnoreCase);
|
||||||
@ -84,6 +85,7 @@ namespace FileManager
|
|||||||
public static LongPath GetSafePath(LongPath path, ReplacementCharacters replacements)
|
public static LongPath GetSafePath(LongPath path, ReplacementCharacters replacements)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
ArgumentValidator.EnsureNotNull(path, nameof(path));
|
||||||
|
ArgumentValidator.EnsureNotNull(replacements, nameof(replacements));
|
||||||
|
|
||||||
var pathNoPrefix = path.PathWithoutPrefix;
|
var pathNoPrefix = path.PathWithoutPrefix;
|
||||||
|
|
||||||
@ -159,7 +161,7 @@ namespace FileManager
|
|||||||
LongPath source,
|
LongPath source,
|
||||||
LongPath destination,
|
LongPath destination,
|
||||||
ReplacementCharacters replacements,
|
ReplacementCharacters replacements,
|
||||||
string extension = null,
|
string? extension = null,
|
||||||
bool overwrite = false)
|
bool overwrite = false)
|
||||||
{
|
{
|
||||||
extension ??= Path.GetExtension(source);
|
extension ??= Path.GetExtension(source);
|
||||||
@ -213,6 +215,9 @@ namespace FileManager
|
|||||||
SaferDelete(destination);
|
SaferDelete(destination);
|
||||||
|
|
||||||
var dir = Path.GetDirectoryName(destination);
|
var dir = Path.GetDirectoryName(destination);
|
||||||
|
if (dir is null)
|
||||||
|
throw new DirectoryNotFoundException();
|
||||||
|
|
||||||
Serilog.Log.Logger.Debug("Attempt to create directory: {@DebugText}", new { dir });
|
Serilog.Log.Logger.Debug("Attempt to create directory: {@DebugText}", new { dir });
|
||||||
Directory.CreateDirectory(dir);
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,10 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
public sealed class LogArchiver : IAsyncDisposable
|
public sealed class LogArchiver : IAsyncDisposable, IDisposable
|
||||||
{
|
{
|
||||||
public Encoding Encoding { get; set; }
|
public Encoding Encoding { get; set; }
|
||||||
public string FileName { get; }
|
public string FileName { get; }
|
||||||
@ -39,28 +40,28 @@ namespace FileManager
|
|||||||
e.Delete();
|
e.Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddFileAsync(string name, JObject contents, string comment = null)
|
public async Task AddFileAsync(string name, JObject contents, string? comment = null)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(contents, nameof(contents));
|
ArgumentValidator.EnsureNotNull(contents, nameof(contents));
|
||||||
await AddFileAsync(name, Encoding.GetBytes(contents.ToString(Newtonsoft.Json.Formatting.Indented)), comment);
|
await AddFileAsync(name, Encoding.GetBytes(contents.ToString(Newtonsoft.Json.Formatting.Indented)), comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddFileAsync(string name, string contents, string comment = null)
|
public async Task AddFileAsync(string name, string contents, string? comment = null)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(contents, nameof(contents));
|
ArgumentValidator.EnsureNotNull(contents, nameof(contents));
|
||||||
await AddFileAsync(name, Encoding.GetBytes(contents), comment);
|
await AddFileAsync(name, Encoding.GetBytes(contents), comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task AddFileAsync(string name, ReadOnlyMemory<byte> contents, string comment = null)
|
public Task AddFileAsync(string name, ReadOnlyMemory<byte> contents, string? comment = null)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(name, nameof(name));
|
ArgumentValidator.EnsureNotNull(name, nameof(name));
|
||||||
|
|
||||||
name = ReplacementCharacters.Barebones.ReplaceFilenameChars(name);
|
name = ReplacementCharacters.Barebones.ReplaceFilenameChars(name);
|
||||||
return Task.Run(() => AddfileInternal(name, contents.Span, comment));
|
return Task.Run(() => AddFileInternal(name, contents.Span, comment));
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly object lockObj = new();
|
private readonly object lockObj = new();
|
||||||
private void AddfileInternal(string name, ReadOnlySpan<byte> contents, string comment)
|
private void AddFileInternal(string name, ReadOnlySpan<byte> contents, string? comment)
|
||||||
{
|
{
|
||||||
lock (lockObj)
|
lock (lockObj)
|
||||||
{
|
{
|
||||||
@ -73,5 +74,7 @@ namespace FileManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async ValueTask DisposeAsync() => await Task.Run(archive.Dispose);
|
public async ValueTask DisposeAsync() => await Task.Run(archive.Dispose);
|
||||||
|
|
||||||
|
public void Dispose() => archive.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
public class LongPath
|
public class LongPath
|
||||||
@ -15,9 +17,9 @@ namespace FileManager
|
|||||||
public static readonly int MaxPathLength;
|
public static readonly int MaxPathLength;
|
||||||
private const int WIN_MAX_PATH = 260;
|
private const int WIN_MAX_PATH = 260;
|
||||||
private const string WIN_LONG_PATH_PREFIX = @"\\?\";
|
private const string WIN_LONG_PATH_PREFIX = @"\\?\";
|
||||||
internal static readonly bool IsWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
internal static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
|
||||||
internal static readonly bool IsLinux = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
internal static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||||
internal static readonly bool IsOSX = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
internal static readonly bool IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
|
||||||
|
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
|
|
||||||
@ -60,7 +62,8 @@ namespace FileManager
|
|||||||
=> IsWindows ? filename.Length
|
=> IsWindows ? filename.Length
|
||||||
: Encoding.UTF8.GetByteCount(filename);
|
: Encoding.UTF8.GetByteCount(filename);
|
||||||
|
|
||||||
public static implicit operator LongPath(string path)
|
[return: NotNullIfNotNull(nameof(path))]
|
||||||
|
public static implicit operator LongPath?(string? path)
|
||||||
{
|
{
|
||||||
if (path is null) return null;
|
if (path is null) return null;
|
||||||
|
|
||||||
@ -93,7 +96,8 @@ namespace FileManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static implicit operator string(LongPath path) => path?.Path;
|
[return: NotNullIfNotNull(nameof(path))]
|
||||||
|
public static implicit operator string?(LongPath? path) => path?.Path;
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public string ShortPathName
|
public string ShortPathName
|
||||||
@ -127,8 +131,6 @@ namespace FileManager
|
|||||||
//for newly-created entries in ther file system. Existing entries made while
|
//for newly-created entries in ther file system. Existing entries made while
|
||||||
//8dot3 names were disabled will not be reachable by short paths.
|
//8dot3 names were disabled will not be reachable by short paths.
|
||||||
|
|
||||||
if (Path is null) return null;
|
|
||||||
|
|
||||||
StringBuilder shortPathBuffer = new(MaxPathLength);
|
StringBuilder shortPathBuffer = new(MaxPathLength);
|
||||||
GetShortPathName(Path, shortPathBuffer, MaxPathLength);
|
GetShortPathName(Path, shortPathBuffer, MaxPathLength);
|
||||||
return shortPathBuffer.ToString();
|
return shortPathBuffer.ToString();
|
||||||
@ -141,7 +143,6 @@ namespace FileManager
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!IsWindows) return Path;
|
if (!IsWindows) return Path;
|
||||||
if (Path is null) return null;
|
|
||||||
|
|
||||||
StringBuilder longPathBuffer = new(MaxPathLength);
|
StringBuilder longPathBuffer = new(MaxPathLength);
|
||||||
GetLongPathName(Path, longPathBuffer, MaxPathLength);
|
GetLongPathName(Path, longPathBuffer, MaxPathLength);
|
||||||
@ -156,7 +157,8 @@ namespace FileManager
|
|||||||
{
|
{
|
||||||
if (!IsWindows) return Path;
|
if (!IsWindows) return Path;
|
||||||
return
|
return
|
||||||
Path?.StartsWith(WIN_LONG_PATH_PREFIX) == true ? Path.Remove(0, WIN_LONG_PATH_PREFIX.Length)
|
Path.StartsWith(WIN_LONG_PATH_PREFIX)
|
||||||
|
? Path.Remove(0, WIN_LONG_PATH_PREFIX.Length)
|
||||||
: Path;
|
: Path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -164,9 +166,9 @@ namespace FileManager
|
|||||||
public override string ToString() => Path;
|
public override string ToString() => Path;
|
||||||
|
|
||||||
public override int GetHashCode() => Path.GetHashCode();
|
public override int GetHashCode() => Path.GetHashCode();
|
||||||
public override bool Equals(object obj) => obj is LongPath other && Path == other.Path;
|
public override bool Equals(object? obj) => obj is LongPath other && Path == other.Path;
|
||||||
public static bool operator ==(LongPath path1, LongPath path2) => path1.Equals(path2);
|
public static bool operator ==(LongPath? path1, LongPath? path2) => path1?.Equals(path2) is true;
|
||||||
public static bool operator !=(LongPath path1, LongPath path2) => !path1.Equals(path2);
|
public static bool operator !=(LongPath? path1, LongPath? path2) => path1 is null || path2 is null || !path1.Equals(path2);
|
||||||
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager.NamingTemplate;
|
namespace FileManager.NamingTemplate;
|
||||||
|
|
||||||
internal interface IClosingPropertyTag : IPropertyTag
|
internal interface IClosingPropertyTag : IPropertyTag
|
||||||
@ -17,7 +19,7 @@ internal interface IClosingPropertyTag : IPropertyTag
|
|||||||
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
|
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
|
||||||
/// <param name="propertyTag">The registered <see cref="IPropertyTag"/></param>
|
/// <param name="propertyTag">The registered <see cref="IPropertyTag"/></param>
|
||||||
/// <returns>True if the <paramref name="templateString"/> starts with this tag.</returns>
|
/// <returns>True if the <paramref name="templateString"/> starts with this tag.</returns>
|
||||||
bool StartsWithClosing(string templateString, out string exactName, out IClosingPropertyTag propertyTag);
|
bool StartsWithClosing(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IClosingPropertyTag? propertyTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConditionalTagCollection<TClass> : TagCollection
|
public class ConditionalTagCollection<TClass> : TagCollection
|
||||||
@ -37,6 +39,7 @@ public class ConditionalTagCollection<TClass> : TagCollection
|
|||||||
|
|
||||||
private class ConditionalTag : TagBase, IClosingPropertyTag
|
private class ConditionalTag : TagBase, IClosingPropertyTag
|
||||||
{
|
{
|
||||||
|
public override Regex NameMatcher { get; }
|
||||||
public Regex NameCloseMatcher { get; }
|
public Regex NameCloseMatcher { get; }
|
||||||
|
|
||||||
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, Expression conditionExpression)
|
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, Expression conditionExpression)
|
||||||
@ -46,7 +49,7 @@ public class ConditionalTagCollection<TClass> : TagCollection
|
|||||||
NameCloseMatcher = new Regex($"^<-{templateTag.TagName}>", options);
|
NameCloseMatcher = new Regex($"^<-{templateTag.TagName}>", options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool StartsWithClosing(string templateString, out string exactName, out IClosingPropertyTag propertyTag)
|
public bool StartsWithClosing(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IClosingPropertyTag? propertyTag)
|
||||||
{
|
{
|
||||||
var match = NameCloseMatcher.Match(templateString);
|
var match = NameCloseMatcher.Match(templateString);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
|
|||||||
@ -1,19 +1,21 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager.NamingTemplate;
|
namespace FileManager.NamingTemplate;
|
||||||
|
|
||||||
public class NamingTemplate
|
public class NamingTemplate
|
||||||
{
|
{
|
||||||
public string TemplateText { get; private set; }
|
public string TemplateText { get; private set; } = string.Empty;
|
||||||
public IEnumerable<ITemplateTag> TagsInUse => _tagsInUse;
|
public IEnumerable<ITemplateTag> TagsInUse => _tagsInUse;
|
||||||
public IEnumerable<ITemplateTag> TagsRegistered => TagCollections.SelectMany(t => t).DistinctBy(t => t.TagName);
|
public IEnumerable<ITemplateTag> TagsRegistered => TagCollections.SelectMany(t => t).DistinctBy(t => t.TagName);
|
||||||
public IEnumerable<string> Warnings => errors.Concat(warnings);
|
public IEnumerable<string> Warnings => errors.Concat(warnings);
|
||||||
public IEnumerable<string> Errors => errors;
|
public IEnumerable<string> Errors => errors;
|
||||||
|
|
||||||
private Delegate templateToString;
|
private Delegate? templateToString;
|
||||||
private readonly List<string> warnings = new();
|
private readonly List<string> warnings = new();
|
||||||
private readonly List<string> errors = new();
|
private readonly List<string> errors = new();
|
||||||
private readonly IEnumerable<TagCollection> TagCollections;
|
private readonly IEnumerable<TagCollection> TagCollections;
|
||||||
@ -30,6 +32,9 @@ public class NamingTemplate
|
|||||||
/// <param name="propertyClasses">Instances of the TClass used in <see cref="PropertyTagCollection{TClass}"/> and <see cref="ConditionalTagCollection{TClass}"/></param>
|
/// <param name="propertyClasses">Instances of the TClass used in <see cref="PropertyTagCollection{TClass}"/> and <see cref="ConditionalTagCollection{TClass}"/></param>
|
||||||
public TemplatePart Evaluate(params object[] propertyClasses)
|
public TemplatePart Evaluate(params object[] propertyClasses)
|
||||||
{
|
{
|
||||||
|
if (templateToString is null)
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
|
||||||
// Match propertyClasses to the arguments required by templateToString.DynamicInvoke().
|
// Match propertyClasses to the arguments required by templateToString.DynamicInvoke().
|
||||||
// First parameter is "this", so ignore it.
|
// First parameter is "this", so ignore it.
|
||||||
var delegateArgTypes = templateToString.Method.GetParameters().Skip(1);
|
var delegateArgTypes = templateToString.Method.GetParameters().Skip(1);
|
||||||
@ -39,7 +44,7 @@ public class NamingTemplate
|
|||||||
if (args.Length != delegateArgTypes.Count())
|
if (args.Length != delegateArgTypes.Count())
|
||||||
throw new ArgumentException($"This instance of {nameof(NamingTemplate)} requires the following arguments: {string.Join(", ", delegateArgTypes.Select(t => t.Name).Distinct())}");
|
throw new ArgumentException($"This instance of {nameof(NamingTemplate)} requires the following arguments: {string.Join(", ", delegateArgTypes.Select(t => t.Name).Distinct())}");
|
||||||
|
|
||||||
return ((TemplatePart)templateToString.DynamicInvoke(args)).FirstPart;
|
return (templateToString.DynamicInvoke(args) as TemplatePart)!.FirstPart;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Parse a template string to a <see cref="NamingTemplate"/></summary>
|
/// <summary>Parse a template string to a <see cref="NamingTemplate"/></summary>
|
||||||
@ -69,7 +74,7 @@ public class NamingTemplate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Builds an <see cref="Expression"/> tree that will evaluate to a <see cref="TemplatePart"/></summary>
|
/// <summary>Builds an <see cref="Expression"/> tree that will evaluate to a <see cref="TemplatePart"/></summary>
|
||||||
private static Expression GetExpressionTree(BinaryNode node)
|
private static Expression GetExpressionTree(BinaryNode? node)
|
||||||
{
|
{
|
||||||
if (node is null) return TemplatePart.Blank;
|
if (node is null) return TemplatePart.Blank;
|
||||||
else if (node.IsValue) return node.Expression;
|
else if (node.IsValue) return node.Expression;
|
||||||
@ -81,10 +86,10 @@ public class NamingTemplate
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Parse a template string into a <see cref="BinaryNode"/> tree</summary>
|
/// <summary>Parse a template string into a <see cref="BinaryNode"/> tree</summary>
|
||||||
private BinaryNode IntermediateParse(string templateString)
|
private BinaryNode IntermediateParse(string? templateString)
|
||||||
{
|
{
|
||||||
if (templateString is null)
|
if (templateString is null)
|
||||||
throw new NullReferenceException(ERROR_NULL_IS_INVALID);
|
throw new ArgumentException(ERROR_NULL_IS_INVALID);
|
||||||
else if (string.IsNullOrEmpty(templateString))
|
else if (string.IsNullOrEmpty(templateString))
|
||||||
warnings.Add(WARNING_EMPTY);
|
warnings.Add(WARNING_EMPTY);
|
||||||
else if (string.IsNullOrWhiteSpace(templateString))
|
else if (string.IsNullOrWhiteSpace(templateString))
|
||||||
@ -93,12 +98,12 @@ public class NamingTemplate
|
|||||||
TemplateText = templateString;
|
TemplateText = templateString;
|
||||||
|
|
||||||
BinaryNode topNode = BinaryNode.CreateRoot();
|
BinaryNode topNode = BinaryNode.CreateRoot();
|
||||||
BinaryNode currentNode = topNode;
|
BinaryNode? currentNode = topNode;
|
||||||
List<char> literalChars = new();
|
List<char> literalChars = new();
|
||||||
|
|
||||||
while (templateString.Length > 0)
|
while (templateString.Length > 0)
|
||||||
{
|
{
|
||||||
if (StartsWith(templateString, out string exactPropertyName, out var propertyTag, out var valueExpression))
|
if (StartsWith(templateString, out var exactPropertyName, out var propertyTag, out var valueExpression))
|
||||||
{
|
{
|
||||||
checkAndAddLiterals();
|
checkAndAddLiterals();
|
||||||
|
|
||||||
@ -116,7 +121,7 @@ public class NamingTemplate
|
|||||||
{
|
{
|
||||||
checkAndAddLiterals();
|
checkAndAddLiterals();
|
||||||
|
|
||||||
BinaryNode lastParenth = currentNode;
|
BinaryNode? lastParenth = currentNode;
|
||||||
|
|
||||||
while (lastParenth?.IsConditional is false)
|
while (lastParenth?.IsConditional is false)
|
||||||
lastParenth = lastParenth.Parent;
|
lastParenth = lastParenth.Parent;
|
||||||
@ -168,7 +173,7 @@ public class NamingTemplate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool StartsWith(string template, out string exactName, out IPropertyTag propertyTag, out Expression valueExpression)
|
private bool StartsWith(string template, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IPropertyTag? propertyTag, [NotNullWhen(true)] out Expression? valueExpression)
|
||||||
{
|
{
|
||||||
foreach (var pc in TagCollections)
|
foreach (var pc in TagCollections)
|
||||||
{
|
{
|
||||||
@ -182,7 +187,7 @@ public class NamingTemplate
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool StartsWithClosing(string template, out string exactName, out IClosingPropertyTag closingPropertyTag)
|
private bool StartsWithClosing(string template, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IClosingPropertyTag? closingPropertyTag)
|
||||||
{
|
{
|
||||||
foreach (var pc in TagCollections)
|
foreach (var pc in TagCollections)
|
||||||
{
|
{
|
||||||
@ -198,36 +203,36 @@ public class NamingTemplate
|
|||||||
private class BinaryNode
|
private class BinaryNode
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public BinaryNode Parent { get; private set; }
|
public BinaryNode? Parent { get; private set; }
|
||||||
public BinaryNode RightChild { get; private set; }
|
public BinaryNode? RightChild { get; private set; }
|
||||||
public BinaryNode LeftChild { get; private set; }
|
public BinaryNode? LeftChild { get; private set; }
|
||||||
public Expression Expression { get; private init; }
|
public Expression Expression { get; }
|
||||||
public bool IsConditional { get; private init; } = false;
|
public bool IsConditional { get; private init; } = false;
|
||||||
public bool IsValue { get; private init; } = false;
|
public bool IsValue { get; private init; } = false;
|
||||||
|
|
||||||
public static BinaryNode CreateRoot() => new("Root");
|
public static BinaryNode CreateRoot() => new("Root", Expression.Empty());
|
||||||
|
|
||||||
public static BinaryNode CreateValue(string literal) => new("Literal")
|
public static BinaryNode CreateValue(string literal)
|
||||||
|
=> new("Literal", TemplatePart.CreateLiteral(literal))
|
||||||
{
|
{
|
||||||
IsValue = true,
|
IsValue = true
|
||||||
Expression = TemplatePart.CreateLiteral(literal)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static BinaryNode CreateValue(ITemplateTag templateTag, Expression property) => new(templateTag.TagName)
|
public static BinaryNode CreateValue(ITemplateTag templateTag, Expression property)
|
||||||
|
=> new(templateTag.TagName, TemplatePart.CreateProperty(templateTag, property))
|
||||||
{
|
{
|
||||||
IsValue = true,
|
IsValue = true
|
||||||
Expression = TemplatePart.CreateProperty(templateTag, property)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public static BinaryNode CreateConditional(ITemplateTag templateTag, Expression property) => new(templateTag.TagName)
|
public static BinaryNode CreateConditional(ITemplateTag templateTag, Expression property)
|
||||||
|
=> new(templateTag.TagName, property)
|
||||||
{
|
{
|
||||||
IsConditional = true,
|
IsConditional = true
|
||||||
Expression = property
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static BinaryNode CreateConcatenation(BinaryNode left, BinaryNode right)
|
private static BinaryNode CreateConcatenation(BinaryNode left, BinaryNode right)
|
||||||
{
|
{
|
||||||
var newNode = new BinaryNode("Concatenation")
|
var newNode = new BinaryNode("Concatenation", Expression.Empty())
|
||||||
{
|
{
|
||||||
LeftChild = left,
|
LeftChild = left,
|
||||||
RightChild = right
|
RightChild = right
|
||||||
@ -237,7 +242,12 @@ public class NamingTemplate
|
|||||||
return newNode;
|
return newNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BinaryNode(string name) => Name = name;
|
private BinaryNode(string name, Expression expression)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Expression = expression;
|
||||||
|
}
|
||||||
|
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
|
|
||||||
public BinaryNode AddNewNode(BinaryNode newNode)
|
public BinaryNode AddNewNode(BinaryNode newNode)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager.NamingTemplate;
|
namespace FileManager.NamingTemplate;
|
||||||
|
|
||||||
public delegate string PropertyFormatter<T>(ITemplateTag templateTag, T value, string formatString);
|
public delegate string PropertyFormatter<T>(ITemplateTag templateTag, T value, string formatString);
|
||||||
@ -37,7 +38,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
|||||||
/// <param name="formatter">Optional formatting function that accepts the <typeparamref name="TProperty"/> property
|
/// <param name="formatter">Optional formatting function that accepts the <typeparamref name="TProperty"/> property
|
||||||
/// and a formatting string and returnes the value the formatted string. If <see cref="null"/>, use the default
|
/// and a formatting string and returnes the value the formatted string. If <see cref="null"/>, use the default
|
||||||
/// <typeparamref name="TProperty"/> formatter if present, or <see cref="object.ToString"/></param>
|
/// <typeparamref name="TProperty"/> formatter if present, or <see cref="object.ToString"/></param>
|
||||||
public void Add<TProperty>(ITemplateTag templateTag, Func<TClass, TProperty?> propertyGetter, PropertyFormatter<TProperty> formatter = null)
|
public void Add<TProperty>(ITemplateTag templateTag, Func<TClass, TProperty?> propertyGetter, PropertyFormatter<TProperty>? formatter = null)
|
||||||
where TProperty : struct
|
where TProperty : struct
|
||||||
=> RegisterWithFormatter(templateTag, propertyGetter, formatter);
|
=> RegisterWithFormatter(templateTag, propertyGetter, formatter);
|
||||||
|
|
||||||
@ -59,7 +60,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
|||||||
/// <param name="formatter">Optional formatting function that accepts the <typeparamref name="TProperty"/> property
|
/// <param name="formatter">Optional formatting function that accepts the <typeparamref name="TProperty"/> property
|
||||||
/// and a formatting string and returnes the value formatted to string. If <see cref="null"/>, use the default
|
/// and a formatting string and returnes the value formatted to string. If <see cref="null"/>, use the default
|
||||||
/// <typeparamref name="TProperty"/> formatter if present, or <see cref="object.ToString"/></param>
|
/// <typeparamref name="TProperty"/> formatter if present, or <see cref="object.ToString"/></param>
|
||||||
public void Add<TProperty>(ITemplateTag templateTag, Func<TClass, TProperty> propertyGetter, PropertyFormatter<TProperty> formatter = null)
|
public void Add<TProperty>(ITemplateTag templateTag, Func<TClass, TProperty> propertyGetter, PropertyFormatter<TProperty>? formatter = null)
|
||||||
=> RegisterWithFormatter(templateTag, propertyGetter, formatter);
|
=> RegisterWithFormatter(templateTag, propertyGetter, formatter);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -72,14 +73,15 @@ public class PropertyTagCollection<TClass> : TagCollection
|
|||||||
=> RegisterWithToString(templateTag, propertyGetter, toString);
|
=> RegisterWithToString(templateTag, propertyGetter, toString);
|
||||||
|
|
||||||
private void RegisterWithFormatter<TProperty, TPropertyValue>
|
private void RegisterWithFormatter<TProperty, TPropertyValue>
|
||||||
(ITemplateTag templateTag, Func<TClass, TProperty> propertyGetter, PropertyFormatter<TPropertyValue> formatter)
|
(ITemplateTag templateTag, Func<TClass, TProperty> propertyGetter, PropertyFormatter<TPropertyValue>? formatter)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(templateTag, nameof(templateTag));
|
ArgumentValidator.EnsureNotNull(templateTag, nameof(templateTag));
|
||||||
ArgumentValidator.EnsureNotNull(propertyGetter, nameof(propertyGetter));
|
ArgumentValidator.EnsureNotNull(propertyGetter, nameof(propertyGetter));
|
||||||
|
|
||||||
var expr = Expression.Call(Expression.Constant(propertyGetter.Target), propertyGetter.Method, Parameter);
|
var expr = Expression.Call(Expression.Constant(propertyGetter.Target), propertyGetter.Method, Parameter);
|
||||||
|
formatter ??= GetDefaultFormatter<TPropertyValue>();
|
||||||
|
|
||||||
if ((formatter ??= GetDefaultFormatter<TPropertyValue>()) is null)
|
if (formatter is null)
|
||||||
AddPropertyTag(new PropertyTag<TPropertyValue>(templateTag, Options, expr, ToStringFunc));
|
AddPropertyTag(new PropertyTag<TPropertyValue>(templateTag, Options, expr, ToStringFunc));
|
||||||
else
|
else
|
||||||
AddPropertyTag(new PropertyTag<TPropertyValue>(templateTag, Options, expr, formatter));
|
AddPropertyTag(new PropertyTag<TPropertyValue>(templateTag, Options, expr, formatter));
|
||||||
@ -97,7 +99,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
|||||||
|
|
||||||
private static string ToStringFunc<T>(T propertyValue) => propertyValue?.ToString() ?? "";
|
private static string ToStringFunc<T>(T propertyValue) => propertyValue?.ToString() ?? "";
|
||||||
|
|
||||||
private PropertyFormatter<T> GetDefaultFormatter<T>()
|
private PropertyFormatter<T>? GetDefaultFormatter<T>()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -109,6 +111,7 @@ public class PropertyTagCollection<TClass> : TagCollection
|
|||||||
|
|
||||||
private class PropertyTag<TPropertyValue> : TagBase
|
private class PropertyTag<TPropertyValue> : TagBase
|
||||||
{
|
{
|
||||||
|
public override Regex NameMatcher { get; }
|
||||||
private Func<Expression, string, Expression> CreateToStringExpression { get; }
|
private Func<Expression, string, Expression> CreateToStringExpression { get; }
|
||||||
|
|
||||||
public PropertyTag(ITemplateTag templateTag, RegexOptions options, Expression propertyGetter, PropertyFormatter<TPropertyValue> formatter)
|
public PropertyTag(ITemplateTag templateTag, RegexOptions options, Expression propertyGetter, PropertyFormatter<TPropertyValue> formatter)
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager.NamingTemplate;
|
namespace FileManager.NamingTemplate;
|
||||||
|
|
||||||
internal interface IPropertyTag
|
internal interface IPropertyTag
|
||||||
@ -22,13 +24,13 @@ internal interface IPropertyTag
|
|||||||
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
|
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
|
||||||
/// <param name="propertyValue">The <see cref="Expression"/> that returns the property's value</param>
|
/// <param name="propertyValue">The <see cref="Expression"/> that returns the property's value</param>
|
||||||
/// <returns>True if the <paramref name="templateString"/> starts with this tag.</returns>
|
/// <returns>True if the <paramref name="templateString"/> starts with this tag.</returns>
|
||||||
bool StartsWith(string templateString, out string exactName, out Expression propertyValue);
|
bool StartsWith(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out Expression? propertyValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal abstract class TagBase : IPropertyTag
|
internal abstract class TagBase : IPropertyTag
|
||||||
{
|
{
|
||||||
public ITemplateTag TemplateTag { get; }
|
public ITemplateTag TemplateTag { get; }
|
||||||
public Regex NameMatcher { get; protected init; }
|
public abstract Regex NameMatcher { get; }
|
||||||
public Type ReturnType => ValueExpression.Type;
|
public Type ReturnType => ValueExpression.Type;
|
||||||
protected Expression ValueExpression { get; }
|
protected Expression ValueExpression { get; }
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ internal abstract class TagBase : IPropertyTag
|
|||||||
/// <param name="formatter">The optional format string in the match inside the square brackets</param>
|
/// <param name="formatter">The optional format string in the match inside the square brackets</param>
|
||||||
protected abstract Expression GetTagExpression(string exactName, string formatter);
|
protected abstract Expression GetTagExpression(string exactName, string formatter);
|
||||||
|
|
||||||
public bool StartsWith(string templateString, out string exactName, out Expression propertyValue)
|
public bool StartsWith(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out Expression? propertyValue)
|
||||||
{
|
{
|
||||||
var match = NameMatcher.Match(templateString);
|
var match = NameMatcher.Match(templateString);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager.NamingTemplate;
|
namespace FileManager.NamingTemplate;
|
||||||
|
|
||||||
/// <summary>A collection of <see cref="IPropertyTag"/>s registered to a single <see cref="Type"/>.</summary>
|
/// <summary>A collection of <see cref="IPropertyTag"/>s registered to a single <see cref="Type"/>.</summary>
|
||||||
@ -32,7 +34,7 @@ public abstract class TagCollection : IEnumerable<ITemplateTag>
|
|||||||
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
|
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
|
||||||
/// <param name="propertyValue">The <see cref="Expression"/> that returns the <paramref name="propertyTag"/>'s value</param>
|
/// <param name="propertyValue">The <see cref="Expression"/> that returns the <paramref name="propertyTag"/>'s value</param>
|
||||||
/// <returns>True if the <paramref name="templateString"/> starts with a tag registered in this class.</returns>
|
/// <returns>True if the <paramref name="templateString"/> starts with a tag registered in this class.</returns>
|
||||||
internal bool StartsWith(string templateString, out string exactName, out IPropertyTag propertyTag, out Expression propertyValue)
|
internal bool StartsWith(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IPropertyTag? propertyTag, [NotNullWhen(true)] out Expression? propertyValue)
|
||||||
{
|
{
|
||||||
foreach (var p in PropertyTags)
|
foreach (var p in PropertyTags)
|
||||||
{
|
{
|
||||||
@ -57,7 +59,7 @@ public abstract class TagCollection : IEnumerable<ITemplateTag>
|
|||||||
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
|
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
|
||||||
/// <param name="closingPropertyTag">The registered <see cref="IClosingPropertyTag"/></param>
|
/// <param name="closingPropertyTag">The registered <see cref="IClosingPropertyTag"/></param>
|
||||||
/// <returns>True if the <paramref name="templateString"/> starts with this tag.</returns>
|
/// <returns>True if the <paramref name="templateString"/> starts with this tag.</returns>
|
||||||
internal bool StartsWithClosing(string templateString, out string exactName, out IClosingPropertyTag closingPropertyTag)
|
internal bool StartsWithClosing(string templateString, [NotNullWhen(true)] out string? exactName, [NotNullWhen(true)] out IClosingPropertyTag? closingPropertyTag)
|
||||||
{
|
{
|
||||||
foreach (var cg in PropertyTags.OfType<IClosingPropertyTag>())
|
foreach (var cg in PropertyTags.OfType<IClosingPropertyTag>())
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq.Expressions;
|
using System.Linq.Expressions;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager.NamingTemplate;
|
namespace FileManager.NamingTemplate;
|
||||||
|
|
||||||
/// <summary>Represents one part of an evaluated <see cref="NamingTemplate"/>.</summary>
|
/// <summary>Represents one part of an evaluated <see cref="NamingTemplate"/>.</summary>
|
||||||
@ -15,13 +16,13 @@ public class TemplatePart : IEnumerable<TemplatePart>
|
|||||||
|
|
||||||
/// <summary> The <see cref="IPropertyTag"/>'s <see cref="ITemplateTag"/> if <see cref="TemplatePart"/> is
|
/// <summary> The <see cref="IPropertyTag"/>'s <see cref="ITemplateTag"/> if <see cref="TemplatePart"/> is
|
||||||
/// a registered property, otherwise <see cref="null"/> for string literals. </summary>
|
/// a registered property, otherwise <see cref="null"/> for string literals. </summary>
|
||||||
public ITemplateTag TemplateTag { get; }
|
public ITemplateTag? TemplateTag { get; }
|
||||||
|
|
||||||
/// <summary>The evaluated string.</summary>
|
/// <summary>The evaluated string.</summary>
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
|
|
||||||
private TemplatePart previous;
|
private TemplatePart? previous;
|
||||||
private TemplatePart next;
|
private TemplatePart? next;
|
||||||
private TemplatePart(string name, string value)
|
private TemplatePart(string name, string value)
|
||||||
{
|
{
|
||||||
TagName = name;
|
TagName = name;
|
||||||
@ -53,14 +54,33 @@ public class TemplatePart : IEnumerable<TemplatePart>
|
|||||||
private static Expression CreateExpression(string name, Expression value)
|
private static Expression CreateExpression(string name, Expression value)
|
||||||
=> Expression.New(constructorInfo, Expression.Constant(name), value);
|
=> Expression.New(constructorInfo, Expression.Constant(name), value);
|
||||||
|
|
||||||
private static readonly ConstructorInfo constructorInfo
|
private static readonly ConstructorInfo constructorInfo;
|
||||||
= typeof(TemplatePart).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(string), typeof(string) });
|
private static readonly ConstructorInfo tagTemplateConstructorInfo;
|
||||||
|
private static readonly MethodInfo addMethodInfo;
|
||||||
|
static TemplatePart()
|
||||||
|
{
|
||||||
|
var type = typeof(TemplatePart);
|
||||||
|
|
||||||
private static readonly ConstructorInfo tagTemplateConstructorInfo
|
if (type.GetConstructor(
|
||||||
= typeof(TemplatePart).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(ITemplateTag), typeof(string) });
|
BindingFlags.NonPublic | BindingFlags.Instance,
|
||||||
|
new Type[] { typeof(string), typeof(string) }) is not ConstructorInfo c1)
|
||||||
|
throw new MissingMethodException(nameof(TemplatePart));
|
||||||
|
|
||||||
private static readonly MethodInfo addMethodInfo
|
if (type.GetConstructor(
|
||||||
= typeof(TemplatePart).GetMethod(nameof(Concatenate), BindingFlags.NonPublic | BindingFlags.Static, new Type[] { typeof(TemplatePart), typeof(TemplatePart) });
|
BindingFlags.NonPublic | BindingFlags.Instance,
|
||||||
|
new Type[] { typeof(ITemplateTag), typeof(string) }) is not ConstructorInfo c2)
|
||||||
|
throw new MissingMethodException(nameof(TemplatePart));
|
||||||
|
|
||||||
|
if (type.GetMethod(
|
||||||
|
nameof(Concatenate),
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Static,
|
||||||
|
new Type[] { typeof(TemplatePart), typeof(TemplatePart) }) is not MethodInfo m1)
|
||||||
|
throw new MissingMethodException(nameof(Concatenate));
|
||||||
|
|
||||||
|
constructorInfo = c1;
|
||||||
|
tagTemplateConstructorInfo = c2;
|
||||||
|
addMethodInfo = m1;
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<TemplatePart> GetEnumerator()
|
public IEnumerator<TemplatePart> GetEnumerator()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
public class PersistentDictionary
|
public class PersistentDictionary
|
||||||
@ -13,19 +15,19 @@ namespace FileManager
|
|||||||
public bool IsReadOnly { get; }
|
public bool IsReadOnly { get; }
|
||||||
|
|
||||||
// optimize for strings. expectation is most settings will be strings and a rare exception will be something else
|
// optimize for strings. expectation is most settings will be strings and a rare exception will be something else
|
||||||
private Dictionary<string, string> stringCache { get; } = new Dictionary<string, string>();
|
private Dictionary<string, string?> stringCache { get; } = new();
|
||||||
private Dictionary<string, object> objectCache { get; } = new Dictionary<string, object>();
|
private Dictionary<string, object?> objectCache { get; } = new();
|
||||||
|
|
||||||
public PersistentDictionary(string filepath, bool isReadOnly = false)
|
public PersistentDictionary(string filepath, bool isReadOnly = false)
|
||||||
{
|
{
|
||||||
Filepath = filepath;
|
Filepath = filepath;
|
||||||
IsReadOnly = isReadOnly;
|
IsReadOnly = isReadOnly;
|
||||||
|
|
||||||
if (File.Exists(Filepath))
|
if (File.Exists(Filepath) || Path.GetDirectoryName(Filepath) is not string dirName)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// will create any missing directories, incl subdirectories. if all already exist: no action
|
// will create any missing directories, incl subdirectories. if all already exist: no action
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(filepath));
|
Directory.CreateDirectory(dirName);
|
||||||
|
|
||||||
if (IsReadOnly)
|
if (IsReadOnly)
|
||||||
return;
|
return;
|
||||||
@ -33,13 +35,14 @@ namespace FileManager
|
|||||||
createNewFile();
|
createNewFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetString(string propertyName, string defaultValue = null)
|
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||||
|
public string? GetString(string propertyName, string? defaultValue = null)
|
||||||
{
|
{
|
||||||
if (!stringCache.ContainsKey(propertyName))
|
if (!stringCache.ContainsKey(propertyName))
|
||||||
{
|
{
|
||||||
var jObject = readFile();
|
var jObject = readFile();
|
||||||
if (jObject.ContainsKey(propertyName))
|
if (jObject.ContainsKey(propertyName))
|
||||||
stringCache[propertyName] = jObject[propertyName].Value<string>();
|
stringCache[propertyName] = jObject[propertyName]?.Value<string>();
|
||||||
else
|
else
|
||||||
stringCache[propertyName] = defaultValue;
|
stringCache[propertyName] = defaultValue;
|
||||||
}
|
}
|
||||||
@ -47,7 +50,8 @@ namespace FileManager
|
|||||||
return stringCache[propertyName];
|
return stringCache[propertyName];
|
||||||
}
|
}
|
||||||
|
|
||||||
public T GetNonString<T>(string propertyName, T defaultValue = default)
|
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||||
|
public T? GetNonString<T>(string propertyName, T? defaultValue = default)
|
||||||
{
|
{
|
||||||
var obj = GetObject(propertyName);
|
var obj = GetObject(propertyName);
|
||||||
|
|
||||||
@ -72,21 +76,21 @@ namespace FileManager
|
|||||||
throw new InvalidCastException($"{obj.GetType()} is not convertible to {typeof(T)}");
|
throw new InvalidCastException($"{obj.GetType()} is not convertible to {typeof(T)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public object GetObject(string propertyName)
|
public object? GetObject(string propertyName)
|
||||||
{
|
{
|
||||||
if (!objectCache.ContainsKey(propertyName))
|
if (!objectCache.ContainsKey(propertyName))
|
||||||
{
|
{
|
||||||
var jObject = readFile();
|
var jObject = readFile();
|
||||||
if (!jObject.ContainsKey(propertyName))
|
if (!jObject.ContainsKey(propertyName))
|
||||||
return null;
|
return null;
|
||||||
objectCache[propertyName] = jObject[propertyName].Value<object>();
|
objectCache[propertyName] = jObject[propertyName]?.Value<object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
return objectCache[propertyName];
|
return objectCache[propertyName];
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetStringFromJsonPath(string jsonPath, string propertyName) => GetStringFromJsonPath($"{jsonPath}.{propertyName}");
|
public string? GetStringFromJsonPath(string jsonPath, string propertyName) => GetStringFromJsonPath($"{jsonPath}.{propertyName}");
|
||||||
public string GetStringFromJsonPath(string jsonPath)
|
public string? GetStringFromJsonPath(string jsonPath)
|
||||||
{
|
{
|
||||||
if (!stringCache.ContainsKey(jsonPath))
|
if (!stringCache.ContainsKey(jsonPath))
|
||||||
{
|
{
|
||||||
@ -96,7 +100,7 @@ namespace FileManager
|
|||||||
var token = jObject.SelectToken(jsonPath);
|
var token = jObject.SelectToken(jsonPath);
|
||||||
if (token is null)
|
if (token is null)
|
||||||
return null;
|
return null;
|
||||||
stringCache[jsonPath] = (string)token;
|
stringCache[jsonPath] = token.Value<string>();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -110,7 +114,7 @@ namespace FileManager
|
|||||||
public bool Exists(string propertyName) => readFile().ContainsKey(propertyName);
|
public bool Exists(string propertyName) => readFile().ContainsKey(propertyName);
|
||||||
|
|
||||||
private object locker { get; } = new object();
|
private object locker { get; } = new object();
|
||||||
public void SetString(string propertyName, string newValue)
|
public void SetString(string propertyName, string? newValue)
|
||||||
{
|
{
|
||||||
// only do this check in string cache, NOT object cache
|
// only do this check in string cache, NOT object cache
|
||||||
if (stringCache.ContainsKey(propertyName) && stringCache[propertyName] == newValue)
|
if (stringCache.ContainsKey(propertyName) && stringCache[propertyName] == newValue)
|
||||||
@ -122,7 +126,7 @@ namespace FileManager
|
|||||||
writeFile(propertyName, newValue);
|
writeFile(propertyName, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetNonString(string propertyName, object newValue)
|
public void SetNonString(string propertyName, object? newValue)
|
||||||
{
|
{
|
||||||
// set cache
|
// set cache
|
||||||
objectCache[propertyName] = newValue;
|
objectCache[propertyName] = newValue;
|
||||||
@ -160,7 +164,7 @@ namespace FileManager
|
|||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeFile(string propertyName, JToken newValue)
|
private void writeFile(string propertyName, JToken? newValue)
|
||||||
{
|
{
|
||||||
if (IsReadOnly)
|
if (IsReadOnly)
|
||||||
return;
|
return;
|
||||||
@ -190,7 +194,7 @@ namespace FileManager
|
|||||||
|
|
||||||
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
||||||
/// <returns>Value was changed</returns>
|
/// <returns>Value was changed</returns>
|
||||||
public bool SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
public bool SetWithJsonPath(string jsonPath, string propertyName, string? newValue, bool suppressLogging = false)
|
||||||
{
|
{
|
||||||
if (IsReadOnly)
|
if (IsReadOnly)
|
||||||
return false;
|
return false;
|
||||||
@ -242,7 +246,7 @@ namespace FileManager
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string formatValueForLog(string value)
|
private static string formatValueForLog(string? value)
|
||||||
=> value is null ? "[null]"
|
=> value is null ? "[null]"
|
||||||
: string.IsNullOrEmpty(value) ? "[empty]"
|
: string.IsNullOrEmpty(value) ? "[empty]"
|
||||||
: string.IsNullOrWhiteSpace(value) ? $"[whitespace. Length={value.Length}]"
|
: string.IsNullOrWhiteSpace(value) ? $"[whitespace. Length={value.Length}]"
|
||||||
@ -283,7 +287,6 @@ namespace FileManager
|
|||||||
private void createNewFile()
|
private void createNewFile()
|
||||||
{
|
{
|
||||||
File.WriteAllText(Filepath, "{}");
|
File.WriteAllText(Filepath, "{}");
|
||||||
System.Threading.Thread.Sleep(100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileManager
|
namespace FileManager
|
||||||
{
|
{
|
||||||
public record Replacement
|
public record Replacement
|
||||||
@ -59,7 +60,7 @@ namespace FileManager
|
|||||||
[JsonConverter(typeof(ReplacementCharactersConverter))]
|
[JsonConverter(typeof(ReplacementCharactersConverter))]
|
||||||
public class ReplacementCharacters
|
public class ReplacementCharacters
|
||||||
{
|
{
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is ReplacementCharacters second && Replacements.Count == second.Replacements.Count)
|
if (obj is ReplacementCharacters second && Replacements.Count == second.Replacements.Count)
|
||||||
{
|
{
|
||||||
@ -173,7 +174,7 @@ namespace FileManager
|
|||||||
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
|
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
public IReadOnlyList<Replacement> Replacements { get; init; }
|
required public IReadOnlyList<Replacement> Replacements { get; init; }
|
||||||
private string DefaultReplacement => Replacements[0].ReplacementString;
|
private string DefaultReplacement => Replacements[0].ReplacementString;
|
||||||
private Replacement ForwardSlash => Replacements[1];
|
private Replacement ForwardSlash => Replacements[1];
|
||||||
private Replacement BackSlash => Replacements[2];
|
private Replacement BackSlash => Replacements[2];
|
||||||
@ -298,12 +299,14 @@ namespace FileManager
|
|||||||
public override bool CanConvert(Type objectType)
|
public override bool CanConvert(Type objectType)
|
||||||
=> objectType == typeof(ReplacementCharacters);
|
=> objectType == typeof(ReplacementCharacters);
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
var jObj = JObject.Load(reader);
|
var jObj = JObject.Load(reader);
|
||||||
var replaceArr = jObj[nameof(Replacement)];
|
var replaceArr = jObj[nameof(Replacement)];
|
||||||
IReadOnlyList<Replacement> dict = replaceArr
|
var dict
|
||||||
.ToObject<Replacement[]>().ToList();
|
= replaceArr?.ToObject<Replacement[]>()?.ToList()
|
||||||
|
?? ReplacementCharacters.Default.Replacements;
|
||||||
|
|
||||||
|
|
||||||
//Ensure that the first 6 replacements are for the expected chars and that all replacement strings are valid.
|
//Ensure that the first 6 replacements are for the expected chars and that all replacement strings are valid.
|
||||||
//If not, reset to default.
|
//If not, reset to default.
|
||||||
@ -325,9 +328,10 @@ namespace FileManager
|
|||||||
return new ReplacementCharacters { Replacements = dict };
|
return new ReplacementCharacters { Replacements = dict };
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||||
{
|
{
|
||||||
ReplacementCharacters replacements = (ReplacementCharacters)value;
|
if (value is not ReplacementCharacters replacements)
|
||||||
|
return;
|
||||||
|
|
||||||
var propertyNames = replacements.Replacements
|
var propertyNames = replacements.Replacements
|
||||||
.Select(JObject.FromObject).ToList();
|
.Select(JObject.FromObject).ToList();
|
||||||
|
|||||||
@ -9,11 +9,12 @@ using System.Threading.Tasks;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public abstract class AudibleFileStorage
|
public abstract class AudibleFileStorage
|
||||||
{
|
{
|
||||||
protected abstract LongPath GetFilePathCustom(string productId);
|
protected abstract LongPath? GetFilePathCustom(string productId);
|
||||||
protected abstract List<LongPath> GetFilePathsCustom(string productId);
|
protected abstract List<LongPath> GetFilePathsCustom(string productId);
|
||||||
|
|
||||||
#region static
|
#region static
|
||||||
@ -57,7 +58,7 @@ namespace LibationFileManager
|
|||||||
regexTemplate = $@"{{0}}.*?\.({extAggr})$";
|
regexTemplate = $@"{{0}}.*?\.({extAggr})$";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected LongPath GetFilePath(string productId)
|
protected LongPath? GetFilePath(string productId)
|
||||||
{
|
{
|
||||||
// primary lookup
|
// primary lookup
|
||||||
var cachedFile = FilePathCache.GetFirstPath(productId, FileType);
|
var cachedFile = FilePathCache.GetFirstPath(productId, FileType);
|
||||||
@ -87,7 +88,7 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
internal AaxcFileStorage() : base(FileType.AAXC) { }
|
internal AaxcFileStorage() : base(FileType.AAXC) { }
|
||||||
|
|
||||||
protected override LongPath GetFilePathCustom(string productId)
|
protected override LongPath? GetFilePathCustom(string productId)
|
||||||
=> GetFilePathsCustom(productId).FirstOrDefault();
|
=> GetFilePathsCustom(productId).FirstOrDefault();
|
||||||
|
|
||||||
protected override List<LongPath> GetFilePathsCustom(string productId)
|
protected override List<LongPath> GetFilePathsCustom(string productId)
|
||||||
@ -104,9 +105,9 @@ namespace LibationFileManager
|
|||||||
public class AudioFileStorage : AudibleFileStorage
|
public class AudioFileStorage : AudibleFileStorage
|
||||||
{
|
{
|
||||||
internal AudioFileStorage() : base(FileType.Audio)
|
internal AudioFileStorage() : base(FileType.Audio)
|
||||||
=> BookDirectoryFiles ??= new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
|
=> BookDirectoryFiles ??= newBookDirectoryFiles();
|
||||||
|
|
||||||
private static BackgroundFileSystem BookDirectoryFiles { get; set; }
|
private static BackgroundFileSystem? BookDirectoryFiles { get; set; }
|
||||||
private static object bookDirectoryFilesLocker { get; } = new();
|
private static object bookDirectoryFilesLocker { get; } = new();
|
||||||
private static EnumerationOptions enumerationOptions { get; } = new()
|
private static EnumerationOptions enumerationOptions { get; } = new()
|
||||||
{
|
{
|
||||||
@ -115,15 +116,18 @@ namespace LibationFileManager
|
|||||||
MatchCasing = MatchCasing.CaseInsensitive
|
MatchCasing = MatchCasing.CaseInsensitive
|
||||||
};
|
};
|
||||||
|
|
||||||
protected override LongPath GetFilePathCustom(string productId)
|
protected override LongPath? GetFilePathCustom(string productId)
|
||||||
=> GetFilePathsCustom(productId).FirstOrDefault();
|
=> GetFilePathsCustom(productId).FirstOrDefault();
|
||||||
|
|
||||||
|
private static BackgroundFileSystem newBookDirectoryFiles()
|
||||||
|
=> new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
|
||||||
|
|
||||||
protected override List<LongPath> GetFilePathsCustom(string productId)
|
protected override List<LongPath> GetFilePathsCustom(string productId)
|
||||||
{
|
{
|
||||||
// If user changed the BooksDirectory: reinitialize
|
// If user changed the BooksDirectory: reinitialize
|
||||||
lock (bookDirectoryFilesLocker)
|
lock (bookDirectoryFilesLocker)
|
||||||
if (BooksDirectory != BookDirectoryFiles.RootDirectory)
|
if (BooksDirectory != BookDirectoryFiles?.RootDirectory)
|
||||||
BookDirectoryFiles = new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
|
BookDirectoryFiles = newBookDirectoryFiles();
|
||||||
|
|
||||||
var regex = GetBookSearchRegex(productId);
|
var regex = GetBookSearchRegex(productId);
|
||||||
|
|
||||||
@ -138,9 +142,16 @@ namespace LibationFileManager
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Refresh() => BookDirectoryFiles.RefreshFiles();
|
public void Refresh()
|
||||||
|
{
|
||||||
|
if (BookDirectoryFiles is null)
|
||||||
|
lock (bookDirectoryFilesLocker)
|
||||||
|
BookDirectoryFiles = newBookDirectoryFiles();
|
||||||
|
else
|
||||||
|
BookDirectoryFiles?.RefreshFiles();
|
||||||
|
}
|
||||||
|
|
||||||
public LongPath GetPath(string productId) => GetFilePath(productId);
|
public LongPath? GetPath(string productId) => GetFilePath(productId);
|
||||||
|
|
||||||
public static async IAsyncEnumerable<FilePathCache.CacheEntry> FindAudiobooksAsync(LongPath searchDirectory, [EnumeratorCancellation] CancellationToken cancellationToken)
|
public static async IAsyncEnumerable<FilePathCache.CacheEntry> FindAudiobooksAsync(LongPath searchDirectory, [EnumeratorCancellation] CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -151,7 +162,7 @@ namespace LibationFileManager
|
|||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
yield break;
|
yield break;
|
||||||
|
|
||||||
FilePathCache.CacheEntry audioFile = default;
|
FilePathCache.CacheEntry? audioFile = default;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
[Flags]
|
[Flags]
|
||||||
@ -20,7 +21,7 @@ namespace LibationFileManager
|
|||||||
public static bool IsWindows { get; } = OperatingSystem.IsWindows();
|
public static bool IsWindows { get; } = OperatingSystem.IsWindows();
|
||||||
public static bool IsLinux { get; } = OperatingSystem.IsLinux();
|
public static bool IsLinux { get; } = OperatingSystem.IsLinux();
|
||||||
public static bool IsMacOs { get; } = OperatingSystem.IsMacOS();
|
public static bool IsMacOs { get; } = OperatingSystem.IsMacOS();
|
||||||
public static Version LibationVersion { get; private set; }
|
public static Version? LibationVersion { get; private set; }
|
||||||
public static void SetLibationVersion(Version version) => LibationVersion = version;
|
public static void SetLibationVersion(Version version) => LibationVersion = version;
|
||||||
|
|
||||||
public static OS OS { get; }
|
public static OS OS { get; }
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration
|
public partial class Configuration
|
||||||
@ -29,8 +30,7 @@ namespace LibationFileManager
|
|||||||
}
|
}
|
||||||
.AsReadOnly();
|
.AsReadOnly();
|
||||||
|
|
||||||
public static string GetHelpText(string settingName)
|
public static string? GetHelpText(string settingName)
|
||||||
=> HelpText.TryGetValue(settingName, out var value) ? value : null;
|
=> HelpText.TryGetValue(settingName, out var value) ? value : null;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,11 +5,12 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration
|
public partial class Configuration
|
||||||
{
|
{
|
||||||
public static string ProcessDirectory { get; } = Path.GetDirectoryName(Exe.FileLocationOnDisk);
|
public static string ProcessDirectory { get; } = Path.GetDirectoryName(Exe.FileLocationOnDisk)!;
|
||||||
public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}";
|
public static string AppDir_Relative => $@".{Path.PathSeparator}{LIBATION_FILES_KEY}";
|
||||||
public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(ProcessDirectory, LIBATION_FILES_KEY));
|
public static string AppDir_Absolute => Path.GetFullPath(Path.Combine(ProcessDirectory, LIBATION_FILES_KEY));
|
||||||
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
|
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
|
||||||
@ -36,7 +37,7 @@ namespace LibationFileManager
|
|||||||
LibationFiles = 5
|
LibationFiles = 5
|
||||||
}
|
}
|
||||||
// use func calls so we always get the latest value of LibationFiles
|
// use func calls so we always get the latest value of LibationFiles
|
||||||
private static List<(KnownDirectories directory, Func<string> getPathFunc)> directoryOptionsPaths { get; } = new()
|
private static List<(KnownDirectories directory, Func<string?> getPathFunc)> directoryOptionsPaths { get; } = new()
|
||||||
{
|
{
|
||||||
(KnownDirectories.None, () => null),
|
(KnownDirectories.None, () => null),
|
||||||
(KnownDirectories.UserProfile, () => UserProfile),
|
(KnownDirectories.UserProfile, () => UserProfile),
|
||||||
@ -47,7 +48,7 @@ namespace LibationFileManager
|
|||||||
// also, keep this at bottom of this list
|
// also, keep this at bottom of this list
|
||||||
(KnownDirectories.LibationFiles, () => libationFilesPathCache)
|
(KnownDirectories.LibationFiles, () => libationFilesPathCache)
|
||||||
};
|
};
|
||||||
public static string GetKnownDirectoryPath(KnownDirectories directory)
|
public static string? GetKnownDirectoryPath(KnownDirectories directory)
|
||||||
{
|
{
|
||||||
var dirFunc = directoryOptionsPaths.SingleOrDefault(dirFunc => dirFunc.directory == directory);
|
var dirFunc = directoryOptionsPaths.SingleOrDefault(dirFunc => dirFunc.directory == directory);
|
||||||
return dirFunc == default ? null : dirFunc.getPathFunc();
|
return dirFunc == default ? null : dirFunc.getPathFunc();
|
||||||
|
|||||||
@ -7,6 +7,7 @@ using Newtonsoft.Json;
|
|||||||
using Serilog;
|
using Serilog;
|
||||||
using Dinah.Core.Logging;
|
using Dinah.Core.Logging;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration
|
public partial class Configuration
|
||||||
@ -44,7 +45,7 @@ namespace LibationFileManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string libationFilesPathCache { get; set; }
|
private static string? libationFilesPathCache { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Try to find appsettings.json in the following locations:
|
/// Try to find appsettings.json in the following locations:
|
||||||
@ -124,7 +125,10 @@ namespace LibationFileManager
|
|||||||
// do not check whether directory exists. special/meta directory (eg: AppDir) is valid
|
// do not check whether directory exists. special/meta directory (eg: AppDir) is valid
|
||||||
// verify from live file. no try/catch. want failures to be visible
|
// verify from live file. no try/catch. want failures to be visible
|
||||||
var jObjFinal = JObject.Parse(File.ReadAllText(AppsettingsJsonFile));
|
var jObjFinal = JObject.Parse(File.ReadAllText(AppsettingsJsonFile));
|
||||||
var valueFinal = jObjFinal[LIBATION_FILES_KEY].Value<string>();
|
|
||||||
|
if (jObjFinal[LIBATION_FILES_KEY]?.Value<string>() is not string valueFinal)
|
||||||
|
throw new InvalidDataException($"{LIBATION_FILES_KEY} not found in {AppsettingsJsonFile}");
|
||||||
|
|
||||||
return valueFinal;
|
return valueFinal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dinah.Core;
|
|
||||||
using Dinah.Core.Logging;
|
using Dinah.Core.Logging;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration
|
public partial class Configuration
|
||||||
{
|
{
|
||||||
private IConfigurationRoot configuration;
|
private IConfigurationRoot? configuration;
|
||||||
|
|
||||||
public void ConfigureLogging()
|
public void ConfigureLogging()
|
||||||
{
|
{
|
||||||
@ -31,20 +30,20 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var logLevelStr = persistentDictionary.GetStringFromJsonPath("Serilog", "MinimumLevel");
|
var logLevelStr = Settings.GetStringFromJsonPath("Serilog", "MinimumLevel");
|
||||||
return Enum.TryParse<LogEventLevel>(logLevelStr, out var logLevelEnum) ? logLevelEnum : LogEventLevel.Information;
|
return Enum.TryParse<LogEventLevel>(logLevelStr, out var logLevelEnum) ? logLevelEnum : LogEventLevel.Information;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
OnPropertyChanging(nameof(LogLevel), LogLevel, value);
|
OnPropertyChanging(nameof(LogLevel), LogLevel, value);
|
||||||
var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
var valueWasChanged = Settings.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
|
||||||
if (!valueWasChanged)
|
if (!valueWasChanged)
|
||||||
{
|
{
|
||||||
Log.Logger.Debug("LogLevel.set attempt. No change");
|
Log.Logger.Debug("LogLevel.set attempt. No change");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
configuration.Reload();
|
configuration?.Reload();
|
||||||
|
|
||||||
OnPropertyChanged(nameof(LogLevel), value);
|
OnPropertyChanged(nameof(LogLevel), value);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@ -8,6 +9,7 @@ using FileManager;
|
|||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration
|
public partial class Configuration
|
||||||
@ -18,34 +20,52 @@ namespace LibationFileManager
|
|||||||
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
|
// config class is only responsible for path. not responsible for setting defaults, dir validation, or dir creation
|
||||||
// exceptions: appsettings.json, LibationFiles dir, Settings.json
|
// exceptions: appsettings.json, LibationFiles dir, Settings.json
|
||||||
|
|
||||||
private PersistentDictionary persistentDictionary;
|
private PersistentDictionary? persistentDictionary;
|
||||||
|
|
||||||
public bool RemoveProperty(string propertyName) => persistentDictionary.RemoveProperty(propertyName);
|
private PersistentDictionary Settings
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (persistentDictionary is null)
|
||||||
|
throw new InvalidOperationException($"{nameof(persistentDictionary)} must first be set by accessing {nameof(LibationFiles)} or calling {nameof(SettingsFileIsValid)}");
|
||||||
|
return persistentDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public T GetNonString<T>(T defaultValue, [CallerMemberName] string propertyName = "") => persistentDictionary.GetNonString(propertyName, defaultValue);
|
public bool RemoveProperty(string propertyName) => Settings.RemoveProperty(propertyName);
|
||||||
public object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
|
|
||||||
public string GetString(string defaultValue = null, [CallerMemberName] string propertyName = "") => persistentDictionary.GetString(propertyName, defaultValue);
|
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||||
public void SetNonString(object newValue, [CallerMemberName] string propertyName = "")
|
public T? GetNonString<T>(T defaultValue, [CallerMemberName] string propertyName = "")
|
||||||
|
=> Settings.GetNonString(propertyName, defaultValue);
|
||||||
|
|
||||||
|
|
||||||
|
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||||
|
public string? GetString(string? defaultValue = null, [CallerMemberName] string propertyName = "")
|
||||||
|
=> Settings.GetString(propertyName, defaultValue);
|
||||||
|
|
||||||
|
public object? GetObject([CallerMemberName] string propertyName = "") => Settings.GetObject(propertyName);
|
||||||
|
|
||||||
|
public void SetNonString(object? newValue, [CallerMemberName] string propertyName = "")
|
||||||
{
|
{
|
||||||
var existing = getExistingValue(propertyName);
|
var existing = getExistingValue(propertyName);
|
||||||
if (existing?.Equals(newValue) is true) return;
|
if (existing?.Equals(newValue) is true) return;
|
||||||
|
|
||||||
OnPropertyChanging(propertyName, existing, newValue);
|
OnPropertyChanging(propertyName, existing, newValue);
|
||||||
persistentDictionary.SetNonString(propertyName, newValue);
|
Settings.SetNonString(propertyName, newValue);
|
||||||
OnPropertyChanged(propertyName, newValue);
|
OnPropertyChanged(propertyName, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetString(string newValue, [CallerMemberName] string propertyName = "")
|
public void SetString(string? newValue, [CallerMemberName] string propertyName = "")
|
||||||
{
|
{
|
||||||
var existing = getExistingValue(propertyName);
|
var existing = getExistingValue(propertyName);
|
||||||
if (existing?.Equals(newValue) is true) return;
|
if (existing?.Equals(newValue) is true) return;
|
||||||
|
|
||||||
OnPropertyChanging(propertyName, existing, newValue);
|
OnPropertyChanging(propertyName, existing, newValue);
|
||||||
persistentDictionary.SetString(propertyName, newValue);
|
Settings.SetString(propertyName, newValue);
|
||||||
OnPropertyChanged(propertyName, newValue);
|
OnPropertyChanged(propertyName, newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private object getExistingValue(string propertyName)
|
private object? getExistingValue(string propertyName)
|
||||||
{
|
{
|
||||||
var property = GetType().GetProperty(propertyName);
|
var property = GetType().GetProperty(propertyName);
|
||||||
if (property is not null) return property.GetValue(this);
|
if (property is not null) return property.GetValue(this);
|
||||||
@ -53,16 +73,16 @@ namespace LibationFileManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
|
||||||
public void SetWithJsonPath(string jsonPath, string propertyName, string newValue, bool suppressLogging = false)
|
public void SetWithJsonPath(string jsonPath, string propertyName, string? newValue, bool suppressLogging = false)
|
||||||
{
|
{
|
||||||
var settingWasChanged = persistentDictionary.SetWithJsonPath(jsonPath, propertyName, newValue, suppressLogging);
|
var settingWasChanged = Settings.SetWithJsonPath(jsonPath, propertyName, newValue, suppressLogging);
|
||||||
if (settingWasChanged)
|
if (settingWasChanged)
|
||||||
configuration?.Reload();
|
configuration?.Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
|
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
|
||||||
|
|
||||||
public static string GetDescription(string propertyName)
|
public static string? GetDescription(string propertyName)
|
||||||
{
|
{
|
||||||
var attribute = typeof(Configuration)
|
var attribute = typeof(Configuration)
|
||||||
.GetProperty(propertyName)
|
.GetProperty(propertyName)
|
||||||
@ -73,7 +93,7 @@ namespace LibationFileManager
|
|||||||
return attribute?.Description;
|
return attribute?.Description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
|
public bool Exists(string propertyName) => Settings.Exists(propertyName);
|
||||||
|
|
||||||
[Description("Set cover art as the folder's icon.")]
|
[Description("Set cover art as the folder's icon.")]
|
||||||
public bool UseCoverAsFolderIcon { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
public bool UseCoverAsFolderIcon { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
||||||
@ -91,7 +111,7 @@ namespace LibationFileManager
|
|||||||
public bool BetaOptIn { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
public bool BetaOptIn { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
||||||
|
|
||||||
[Description("Location for book storage. Includes destination of newly liberated books")]
|
[Description("Location for book storage. Includes destination of newly liberated books")]
|
||||||
public LongPath Books { get => GetString(); set => SetString(value); }
|
public LongPath? Books { get => GetString(); set => SetString(value); }
|
||||||
|
|
||||||
[Description("Overwrite existing files if they already exist?")]
|
[Description("Overwrite existing files if they already exist?")]
|
||||||
public bool OverwriteExisting { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
public bool OverwriteExisting { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration
|
public partial class Configuration
|
||||||
@ -9,17 +10,17 @@ namespace LibationFileManager
|
|||||||
* and be sure to clone it before returning. This allows Configuration to
|
* and be sure to clone it before returning. This allows Configuration to
|
||||||
* accurately detect if any of the Dictionary's elements have changed.
|
* accurately detect if any of the Dictionary's elements have changed.
|
||||||
*/
|
*/
|
||||||
private class EquatableDictionary<TKey, TValue> : Dictionary<TKey, TValue>
|
private class EquatableDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TKey : notnull
|
||||||
{
|
{
|
||||||
public EquatableDictionary() { }
|
public EquatableDictionary() { }
|
||||||
public EquatableDictionary(IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs) : base(keyValuePairs) { }
|
public EquatableDictionary(IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs) : base(keyValuePairs) { }
|
||||||
public EquatableDictionary<TKey, TValue> Clone() => new(this);
|
public EquatableDictionary<TKey, TValue> Clone() => new(this);
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object? obj)
|
||||||
{
|
{
|
||||||
if (obj is Dictionary<TKey, TValue> dic && Count == dic.Count)
|
if (obj is Dictionary<TKey, TValue> dic && Count == dic.Count)
|
||||||
{
|
{
|
||||||
foreach (var pair in this)
|
foreach (var pair in this)
|
||||||
if (!dic.TryGetValue(pair.Key, out var value) || !pair.Value.Equals(value))
|
if (!dic.TryGetValue(pair.Key, out var value) || pair.Value?.Equals(value) is not true)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -4,7 +4,7 @@ using System.Linq;
|
|||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration : PropertyChangeFilter
|
public partial class Configuration : PropertyChangeFilter
|
||||||
@ -24,9 +24,12 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
if (!Directory.Exists(booksDir))
|
if (!Directory.Exists(booksDir))
|
||||||
{
|
{
|
||||||
|
if (Path.GetDirectoryName(settingsFile) is not string dir)
|
||||||
|
throw new DirectoryNotFoundException(settingsFile);
|
||||||
|
|
||||||
//"Books" is not null, so setup has already been run.
|
//"Books" is not null, so setup has already been run.
|
||||||
//Since Books can't be found, try to create it in Libation settings folder
|
//Since Books can't be found, try to create it in Libation settings folder
|
||||||
booksDir = Path.Combine(Path.GetDirectoryName(settingsFile), nameof(Books));
|
booksDir = Path.Combine(dir, nameof(Books));
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(booksDir);
|
Directory.CreateDirectory(booksDir);
|
||||||
|
|||||||
@ -6,6 +6,7 @@ using Dinah.Core.Collections.Immutable;
|
|||||||
using FileManager;
|
using FileManager;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public static class FilePathCache
|
public static class FilePathCache
|
||||||
@ -14,8 +15,8 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
private const string FILENAME = "FileLocations.json";
|
private const string FILENAME = "FileLocations.json";
|
||||||
|
|
||||||
public static event EventHandler<CacheEntry> Inserted;
|
public static event EventHandler<CacheEntry>? Inserted;
|
||||||
public static event EventHandler<CacheEntry> Removed;
|
public static event EventHandler<CacheEntry>? Removed;
|
||||||
|
|
||||||
private static Cache<CacheEntry> cache { get; } = new Cache<CacheEntry>();
|
private static Cache<CacheEntry> cache { get; } = new Cache<CacheEntry>();
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ namespace LibationFileManager
|
|||||||
.Select(entry => (entry.FileType, entry.Path))
|
.Select(entry => (entry.FileType, entry.Path))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
public static LongPath GetFirstPath(string id, FileType type)
|
public static LongPath? GetFirstPath(string id, FileType type)
|
||||||
=> getEntries(entry => entry.Id == id && entry.FileType == type)
|
=> getEntries(entry => entry.Id == id && entry.FileType == type)
|
||||||
?.FirstOrDefault()
|
?.FirstOrDefault()
|
||||||
?.Path;
|
?.Path;
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
#nullable enable
|
|
||||||
public interface IInteropFunctions
|
public interface IInteropFunctions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -5,11 +5,12 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public static class InteropFactory
|
public static class InteropFactory
|
||||||
{
|
{
|
||||||
public static Type InteropFunctionsType { get; }
|
public static Type? InteropFunctionsType { get; }
|
||||||
|
|
||||||
public static IInteropFunctions Create() => _create();
|
public static IInteropFunctions Create() => _create();
|
||||||
|
|
||||||
@ -17,13 +18,17 @@ namespace LibationFileManager
|
|||||||
//public static IInteropFunctions Create(string str, int i) => _create(str, i);
|
//public static IInteropFunctions Create(string str, int i) => _create(str, i);
|
||||||
//public static IInteropFunctions Create(params object[] values) => _create(values);
|
//public static IInteropFunctions Create(params object[] values) => _create(values);
|
||||||
|
|
||||||
private static IInteropFunctions instance { get; set; }
|
private static IInteropFunctions? instance { get; set; }
|
||||||
private static IInteropFunctions _create(params object[] values)
|
private static IInteropFunctions _create(params object[] values)
|
||||||
{
|
{
|
||||||
instance ??=
|
instance ??=
|
||||||
InteropFunctionsType is null
|
InteropFunctionsType is null
|
||||||
? new NullInteropFunctions()
|
? new NullInteropFunctions()
|
||||||
: Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions;
|
: Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions;
|
||||||
|
|
||||||
|
if (instance is null)
|
||||||
|
throw new TypeLoadException();
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +71,7 @@ namespace LibationFileManager
|
|||||||
.GetTypes()
|
.GetTypes()
|
||||||
.FirstOrDefault(type.IsAssignableFrom);
|
.FirstOrDefault(type.IsAssignableFrom);
|
||||||
}
|
}
|
||||||
private static string getOSConfigApp()
|
private static string? getOSConfigApp()
|
||||||
{
|
{
|
||||||
// find '*ConfigApp.dll' files
|
// find '*ConfigApp.dll' files
|
||||||
var appName =
|
var appName =
|
||||||
@ -76,8 +81,8 @@ namespace LibationFileManager
|
|||||||
return appName;
|
return appName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, Assembly> lowEffortCache { get; } = new();
|
private static Dictionary<string, Assembly?> lowEffortCache { get; } = new();
|
||||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
private static Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args)
|
||||||
{
|
{
|
||||||
var asmName = new AssemblyName(args.Name);
|
var asmName = new AssemblyName(args.Name);
|
||||||
var here = Configuration.ProcessDirectory;
|
var here = Configuration.ProcessDirectory;
|
||||||
@ -97,7 +102,7 @@ namespace LibationFileManager
|
|||||||
return assembly;
|
return assembly;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Assembly CurrentDomain_AssemblyResolve_internal(AssemblyName asmName, string here)
|
private static Assembly? CurrentDomain_AssemblyResolve_internal(AssemblyName asmName, string here)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Find the requested assembly in the program files directory.
|
* Find the requested assembly in the program files directory.
|
||||||
|
|||||||
@ -2,26 +2,27 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public class BookDto
|
public class BookDto
|
||||||
{
|
{
|
||||||
public string AudibleProductId { get; set; }
|
public string? AudibleProductId { get; set; }
|
||||||
public string Title { get; set; }
|
public string? Title { get; set; }
|
||||||
public string Subtitle { get; set; }
|
public string? Subtitle { get; set; }
|
||||||
public string TitleWithSubtitle { get; set; }
|
public string? TitleWithSubtitle { get; set; }
|
||||||
public string Locale { get; set; }
|
public string? Locale { get; set; }
|
||||||
public int? YearPublished { get; set; }
|
public int? YearPublished { get; set; }
|
||||||
|
|
||||||
public IEnumerable<string> Authors { get; set; }
|
public IEnumerable<string>? Authors { get; set; }
|
||||||
public string AuthorNames => string.Join(", ", Authors);
|
public string? AuthorNames => Authors is null ? null : string.Join(", ", Authors);
|
||||||
public string FirstAuthor => Authors.FirstOrDefault();
|
public string? FirstAuthor => Authors?.FirstOrDefault();
|
||||||
|
|
||||||
public IEnumerable<string> Narrators { get; set; }
|
public IEnumerable<string>? Narrators { get; set; }
|
||||||
public string NarratorNames => string.Join(", ", Narrators);
|
public string? NarratorNames => Narrators is null? null: string.Join(", ", Narrators);
|
||||||
public string FirstNarrator => Narrators.FirstOrDefault();
|
public string? FirstNarrator => Narrators?.FirstOrDefault();
|
||||||
|
|
||||||
public string SeriesName { get; set; }
|
public string? SeriesName { get; set; }
|
||||||
public float? SeriesNumber { get; set; }
|
public float? SeriesNumber { get; set; }
|
||||||
public bool IsSeries => !string.IsNullOrEmpty(SeriesName);
|
public bool IsSeries => !string.IsNullOrEmpty(SeriesName);
|
||||||
public bool IsPodcastParent { get; set; }
|
public bool IsPodcastParent { get; set; }
|
||||||
@ -32,13 +33,13 @@ namespace LibationFileManager
|
|||||||
public int Channels { get; set; }
|
public int Channels { get; set; }
|
||||||
public DateTime FileDate { get; set; } = DateTime.Now;
|
public DateTime FileDate { get; set; } = DateTime.Now;
|
||||||
public DateTime? DatePublished { get; set; }
|
public DateTime? DatePublished { get; set; }
|
||||||
public string Language { get; set; }
|
public string? Language { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LibraryBookDto : BookDto
|
public class LibraryBookDto : BookDto
|
||||||
{
|
{
|
||||||
public DateTime? DateAdded { get; set; }
|
public DateTime? DateAdded { get; set; }
|
||||||
public string Account { get; set; }
|
public string? Account { get; set; }
|
||||||
public string AccountNickname { get; set; }
|
public string? AccountNickname { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,12 +5,15 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
internal partial class NameListFormat
|
internal partial class NameListFormat
|
||||||
{
|
{
|
||||||
public static string Formatter(ITemplateTag _, IEnumerable<string> names, string formatString)
|
public static string Formatter(ITemplateTag _, IEnumerable<string>? names, string formatString)
|
||||||
{
|
{
|
||||||
|
if (names is null) return "";
|
||||||
|
|
||||||
var humanNames = names.Select(n => new HumanName(RemoveSuffix(n), Prefer.FirstOverPrefix));
|
var humanNames = names.Select(n => new HumanName(RemoveSuffix(n), Prefer.FirstOverPrefix));
|
||||||
|
|
||||||
var sortedNames = Sort(humanNames, formatString);
|
var sortedNames = Sort(humanNames, formatString);
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public class NullInteropFunctions : IInteropFunctions
|
public class NullInteropFunctions : IInteropFunctions
|
||||||
|
|||||||
@ -6,18 +6,25 @@ using System.Linq;
|
|||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public enum PictureSize { Native, _80x80 = 80, _300x300 = 300, _500x500 = 500 }
|
public enum PictureSize { Native, _80x80 = 80, _300x300 = 300, _500x500 = 500 }
|
||||||
public class PictureCachedEventArgs : EventArgs
|
public class PictureCachedEventArgs : EventArgs
|
||||||
{
|
{
|
||||||
public PictureDefinition Definition { get; internal set; }
|
public PictureDefinition Definition { get; }
|
||||||
public byte[] Picture { get; internal set; }
|
public byte[] Picture { get; }
|
||||||
|
|
||||||
|
internal PictureCachedEventArgs(PictureDefinition definition, byte[] picture)
|
||||||
|
{
|
||||||
|
Definition = definition;
|
||||||
|
Picture = picture;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public struct PictureDefinition : IEquatable<PictureDefinition>
|
public struct PictureDefinition : IEquatable<PictureDefinition>
|
||||||
{
|
{
|
||||||
public string PictureId { get; }
|
public string PictureId { get; init; }
|
||||||
public PictureSize Size { get; }
|
public PictureSize Size { get; init; }
|
||||||
|
|
||||||
public PictureDefinition(string pictureId, PictureSize pictureSize)
|
public PictureDefinition(string pictureId, PictureSize pictureSize)
|
||||||
{
|
{
|
||||||
@ -45,7 +52,7 @@ namespace LibationFileManager
|
|||||||
.Start();
|
.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static event EventHandler<PictureCachedEventArgs> PictureCached;
|
public static event EventHandler<PictureCachedEventArgs>? PictureCached;
|
||||||
|
|
||||||
private static BlockingCollection<PictureDefinition> DownloadQueue { get; } = new BlockingCollection<PictureDefinition>();
|
private static BlockingCollection<PictureDefinition> DownloadQueue { get; } = new BlockingCollection<PictureDefinition>();
|
||||||
private static object cacheLocker { get; } = new object();
|
private static object cacheLocker { get; } = new object();
|
||||||
@ -112,7 +119,7 @@ namespace LibationFileManager
|
|||||||
lock (cacheLocker)
|
lock (cacheLocker)
|
||||||
cache[def] = bytes;
|
cache[def] = bytes;
|
||||||
|
|
||||||
PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs { Definition = def, Picture = bytes });
|
PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs(def, bytes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,11 +5,14 @@ using System.Linq;
|
|||||||
using Dinah.Core.Collections.Generic;
|
using Dinah.Core.Collections.Generic;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public static class QuickFilters
|
public static class QuickFilters
|
||||||
{
|
{
|
||||||
public static event EventHandler Updated;
|
public static event EventHandler? Updated;
|
||||||
|
|
||||||
|
public static event EventHandler? UseDefaultChanged;
|
||||||
|
|
||||||
internal class FilterState
|
internal class FilterState
|
||||||
{
|
{
|
||||||
@ -17,18 +20,15 @@ namespace LibationFileManager
|
|||||||
public List<string> Filters { get; set; } = new List<string>();
|
public List<string> Filters { get; set; } = new List<string>();
|
||||||
}
|
}
|
||||||
|
|
||||||
static FilterState inMemoryState { get; } = new FilterState();
|
|
||||||
|
|
||||||
public static string JsonFile => Path.Combine(Configuration.Instance.LibationFiles, "QuickFilters.json");
|
public static string JsonFile => Path.Combine(Configuration.Instance.LibationFiles, "QuickFilters.json");
|
||||||
|
|
||||||
static QuickFilters()
|
|
||||||
{
|
|
||||||
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
|
|
||||||
if (File.Exists(JsonFile))
|
|
||||||
inMemoryState = JsonConvert.DeserializeObject<FilterState>(File.ReadAllText(JsonFile));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static event EventHandler UseDefaultChanged;
|
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
|
||||||
|
static FilterState inMemoryState { get; }
|
||||||
|
= File.Exists(JsonFile) && JsonConvert.DeserializeObject<FilterState>(File.ReadAllText(JsonFile)) is FilterState inMemState
|
||||||
|
? inMemState
|
||||||
|
: new FilterState();
|
||||||
|
|
||||||
public static bool UseDefault
|
public static bool UseDefault
|
||||||
{
|
{
|
||||||
get => inMemoryState.UseDefault;
|
get => inMemoryState.UseDefault;
|
||||||
@ -43,7 +43,7 @@ namespace LibationFileManager
|
|||||||
save(false);
|
save(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
UseDefaultChanged?.Invoke(null, null);
|
UseDefaultChanged?.Invoke(null, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ namespace LibationFileManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (invokeUpdatedEvent)
|
if (invokeUpdatedEvent)
|
||||||
Updated?.Invoke(null, null);
|
Updated?.Invoke(null, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public interface ITemplateEditor
|
public interface ITemplateEditor
|
||||||
@ -14,14 +15,11 @@ namespace LibationFileManager
|
|||||||
string DefaultTemplate { get; }
|
string DefaultTemplate { get; }
|
||||||
string TemplateName { get; }
|
string TemplateName { get; }
|
||||||
string TemplateDescription { get; }
|
string TemplateDescription { get; }
|
||||||
Templates Folder { get; }
|
|
||||||
Templates File { get; }
|
|
||||||
Templates Name { get; }
|
|
||||||
Templates EditingTemplate { get; }
|
Templates EditingTemplate { get; }
|
||||||
void SetTemplateText(string templateText);
|
bool SetTemplateText(string templateText);
|
||||||
string GetFolderName();
|
string? GetFolderName();
|
||||||
string GetFileName();
|
string? GetFileName();
|
||||||
string GetName();
|
string? GetName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TemplateEditor<T> : ITemplateEditor where T : Templates, ITemplate, new()
|
public class TemplateEditor<T> : ITemplateEditor where T : Templates, ITemplate, new()
|
||||||
@ -32,9 +30,9 @@ namespace LibationFileManager
|
|||||||
public string DefaultTemplate { get; private init; }
|
public string DefaultTemplate { get; private init; }
|
||||||
public string TemplateName { get; private init; }
|
public string TemplateName { get; private init; }
|
||||||
public string TemplateDescription { get; private init; }
|
public string TemplateDescription { get; private init; }
|
||||||
public Templates Folder { get; private set; }
|
private Templates? Folder { get; set; }
|
||||||
public Templates File { get; private set; }
|
private Templates? File { get; set; }
|
||||||
public Templates Name { get; private set; }
|
private Templates? Name { get; set; }
|
||||||
public Templates EditingTemplate
|
public Templates EditingTemplate
|
||||||
{
|
{
|
||||||
get => _editingTemplate;
|
get => _editingTemplate;
|
||||||
@ -43,10 +41,14 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
private Templates _editingTemplate;
|
private Templates _editingTemplate;
|
||||||
|
|
||||||
public void SetTemplateText(string templateText)
|
public bool SetTemplateText(string templateText)
|
||||||
|
{
|
||||||
|
if (Templates.TryGetTemplate<T>(templateText, out var template))
|
||||||
{
|
{
|
||||||
Templates.TryGetTemplate<T>(templateText, out var template);
|
|
||||||
EditingTemplate = template;
|
EditingTemplate = template;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly LibraryBookDto libraryBookDto
|
private static readonly LibraryBookDto libraryBookDto
|
||||||
@ -80,7 +82,7 @@ namespace LibationFileManager
|
|||||||
Title = "A Flight for Life"
|
Title = "A Flight for Life"
|
||||||
};
|
};
|
||||||
|
|
||||||
public string GetFolderName()
|
public string? GetFolderName()
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Path must be rooted for windows to allow long file paths. This is
|
* Path must be rooted for windows to allow long file paths. This is
|
||||||
@ -88,49 +90,49 @@ namespace LibationFileManager
|
|||||||
* subdirectories. Without rooting, we won't be allowed to create a
|
* subdirectories. Without rooting, we won't be allowed to create a
|
||||||
* relative path longer than MAX_PATH.
|
* relative path longer than MAX_PATH.
|
||||||
*/
|
*/
|
||||||
var dir = Folder.GetFilename(libraryBookDto, BaseDirectory, "");
|
var dir = Folder?.GetFilename(libraryBookDto, BaseDirectory, "");
|
||||||
|
if (dir is null) return null;
|
||||||
return Path.GetRelativePath(BaseDirectory, dir);
|
return Path.GetRelativePath(BaseDirectory, dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFileName()
|
public string? GetFileName()
|
||||||
=> File.GetFilename(libraryBookDto, partFileProperties, "", "");
|
=> File?.GetFilename(libraryBookDto, partFileProperties, "", "");
|
||||||
public string GetName()
|
public string? GetName()
|
||||||
=> Name.GetName(libraryBookDto, partFileProperties);
|
=> Name?.GetName(libraryBookDto, partFileProperties);
|
||||||
|
|
||||||
|
private TemplateEditor(
|
||||||
|
Templates editingTemplate,
|
||||||
|
LongPath baseDirectory,
|
||||||
|
string defaultTemplate,
|
||||||
|
string templateName,
|
||||||
|
string templateDescription)
|
||||||
|
{
|
||||||
|
_editingTemplate = editingTemplate;
|
||||||
|
BaseDirectory = baseDirectory;
|
||||||
|
DefaultTemplate = defaultTemplate;
|
||||||
|
TemplateName = templateName;
|
||||||
|
TemplateDescription = templateDescription;
|
||||||
|
}
|
||||||
|
|
||||||
public static ITemplateEditor CreateFilenameEditor(LongPath baseDir, string templateText)
|
public static ITemplateEditor CreateFilenameEditor(LongPath baseDir, string templateText)
|
||||||
{
|
{
|
||||||
Templates.TryGetTemplate<T>(templateText, out var template);
|
if (!Templates.TryGetTemplate<T>(templateText, out var template))
|
||||||
|
throw new ArgumentException($"Failed to parse {nameof(templateText)}");
|
||||||
|
|
||||||
var templateEditor = new TemplateEditor<T>
|
var templateEditor = new TemplateEditor<T>(template, baseDir, T.DefaultTemplate, T.Name, T.Description);
|
||||||
{
|
|
||||||
_editingTemplate = template,
|
|
||||||
BaseDirectory = baseDir,
|
|
||||||
DefaultTemplate = T.DefaultTemplate,
|
|
||||||
TemplateName = T.Name,
|
|
||||||
TemplateDescription = T.Description
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!templateEditor.IsFolder && !templateEditor.IsFilePath)
|
if (!templateEditor.IsFolder && !templateEditor.IsFilePath)
|
||||||
throw new InvalidOperationException($"This method is only for File and Folder templates. Use {nameof(CreateNameEditor)} for name templates");
|
throw new InvalidOperationException($"This method is only for File and Folder templates. Use {nameof(CreateNameEditor)} for name templates");
|
||||||
|
|
||||||
templateEditor.Folder = templateEditor.IsFolder ? template : Templates.Folder;
|
|
||||||
templateEditor.File = templateEditor.IsFolder ? Templates.File : template;
|
|
||||||
|
|
||||||
return templateEditor;
|
return templateEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ITemplateEditor CreateNameEditor(string templateText)
|
public static ITemplateEditor CreateNameEditor(string templateText)
|
||||||
{
|
{
|
||||||
Templates.TryGetTemplate<T>(templateText, out var nameTemplate);
|
if (!Templates.TryGetTemplate<T>(templateText, out var nameTemplate))
|
||||||
|
throw new ArgumentException($"Failed to parse {nameof(templateText)}");
|
||||||
|
|
||||||
var templateEditor = new TemplateEditor<T>
|
var templateEditor = new TemplateEditor<T>(nameTemplate, "", T.DefaultTemplate, T.Name, T.Description);
|
||||||
{
|
|
||||||
_editingTemplate = nameTemplate,
|
|
||||||
DefaultTemplate = T.DefaultTemplate,
|
|
||||||
TemplateName = T.Name,
|
|
||||||
TemplateDescription = T.Description
|
|
||||||
};
|
|
||||||
|
|
||||||
if (templateEditor.IsFolder || templateEditor.IsFilePath)
|
if (templateEditor.IsFolder || templateEditor.IsFilePath)
|
||||||
throw new InvalidOperationException($"This method is only for name templates. Use {nameof(CreateFilenameEditor)} for file templates");
|
throw new InvalidOperationException($"This method is only for name templates. Use {nameof(CreateFilenameEditor)} for file templates");
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using FileManager.NamingTemplate;
|
using FileManager.NamingTemplate;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public sealed class TemplateTags : ITemplateTag
|
public sealed class TemplateTags : ITemplateTag
|
||||||
@ -10,7 +11,7 @@ namespace LibationFileManager
|
|||||||
public string Description { get; }
|
public string Description { get; }
|
||||||
public string Display { get; }
|
public string Display { get; }
|
||||||
|
|
||||||
private TemplateTags(string tagName, string description, string defaultValue = null, string display = null)
|
private TemplateTags(string tagName, string description, string? defaultValue = null, string? display = null)
|
||||||
{
|
{
|
||||||
TagName = tagName;
|
TagName = tagName;
|
||||||
Description = description;
|
Description = description;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AaxDecrypter;
|
using AaxDecrypter;
|
||||||
@ -8,6 +9,7 @@ using FileManager;
|
|||||||
using FileManager.NamingTemplate;
|
using FileManager.NamingTemplate;
|
||||||
using NameParser;
|
using NameParser;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public interface ITemplate
|
public interface ITemplate
|
||||||
@ -26,10 +28,10 @@ namespace LibationFileManager
|
|||||||
//Assigning the properties in the static constructor will require all
|
//Assigning the properties in the static constructor will require all
|
||||||
//Templates users to have a valid configuration file. To allow tests
|
//Templates users to have a valid configuration file. To allow tests
|
||||||
//to work without access to Configuration, only load templates on demand.
|
//to work without access to Configuration, only load templates on demand.
|
||||||
private static FolderTemplate _folder;
|
private static FolderTemplate? _folder;
|
||||||
private static FileTemplate _file;
|
private static FileTemplate? _file;
|
||||||
private static ChapterFileTemplate _chapterFile;
|
private static ChapterFileTemplate? _chapterFile;
|
||||||
private static ChapterTitleTemplate _chapterTitle;
|
private static ChapterTitleTemplate? _chapterTitle;
|
||||||
|
|
||||||
public static FolderTemplate Folder => _folder ??= GetTemplate<FolderTemplate>(Configuration.Instance.FolderTemplate);
|
public static FolderTemplate Folder => _folder ??= GetTemplate<FolderTemplate>(Configuration.Instance.FolderTemplate);
|
||||||
public static FileTemplate File => _file ??= GetTemplate<FileTemplate>(Configuration.Instance.FileTemplate);
|
public static FileTemplate File => _file ??= GetTemplate<FileTemplate>(Configuration.Instance.FileTemplate);
|
||||||
@ -38,10 +40,10 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
#region Template Parsing
|
#region Template Parsing
|
||||||
|
|
||||||
public static T GetTemplate<T>(string templateText) where T : Templates, ITemplate, new()
|
public static T GetTemplate<T>(string? templateText) where T : Templates, ITemplate, new()
|
||||||
=> TryGetTemplate<T>(templateText, out var template) ? template : GetDefaultTemplate<T>();
|
=> TryGetTemplate<T>(templateText ?? "", out var template) ? template : GetDefaultTemplate<T>();
|
||||||
|
|
||||||
public static bool TryGetTemplate<T>(string templateText, out T template) where T : Templates, ITemplate, new()
|
public static bool TryGetTemplate<T>(string templateText, [NotNullWhen(true)] out T? template) where T : Templates, ITemplate, new()
|
||||||
{
|
{
|
||||||
var namingTemplate = NamingTemplate.Parse(templateText, T.TagCollections);
|
var namingTemplate = NamingTemplate.Parse(templateText, T.TagCollections);
|
||||||
|
|
||||||
@ -56,19 +58,19 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
Configuration.Instance.PropertyChanged +=
|
Configuration.Instance.PropertyChanged +=
|
||||||
[PropertyChangeFilter(nameof(Configuration.FolderTemplate))]
|
[PropertyChangeFilter(nameof(Configuration.FolderTemplate))]
|
||||||
(_,e) => _folder = GetTemplate<FolderTemplate>((string)e.NewValue);
|
(_,e) => _folder = GetTemplate<FolderTemplate>(e.NewValue as string);
|
||||||
|
|
||||||
Configuration.Instance.PropertyChanged +=
|
Configuration.Instance.PropertyChanged +=
|
||||||
[PropertyChangeFilter(nameof(Configuration.FileTemplate))]
|
[PropertyChangeFilter(nameof(Configuration.FileTemplate))]
|
||||||
(_, e) => _file = GetTemplate<FileTemplate>((string)e.NewValue);
|
(_, e) => _file = GetTemplate<FileTemplate>(e.NewValue as string);
|
||||||
|
|
||||||
Configuration.Instance.PropertyChanged +=
|
Configuration.Instance.PropertyChanged +=
|
||||||
[PropertyChangeFilter(nameof(Configuration.ChapterFileTemplate))]
|
[PropertyChangeFilter(nameof(Configuration.ChapterFileTemplate))]
|
||||||
(_, e) => _chapterFile = GetTemplate<ChapterFileTemplate>((string)e.NewValue);
|
(_, e) => _chapterFile = GetTemplate<ChapterFileTemplate>(e.NewValue as string);
|
||||||
|
|
||||||
Configuration.Instance.PropertyChanged +=
|
Configuration.Instance.PropertyChanged +=
|
||||||
[PropertyChangeFilter(nameof(Configuration.ChapterTitleTemplate))]
|
[PropertyChangeFilter(nameof(Configuration.ChapterTitleTemplate))]
|
||||||
(_, e) => _chapterTitle = GetTemplate<ChapterTitleTemplate>((string)e.NewValue);
|
(_, e) => _chapterTitle = GetTemplate<ChapterTitleTemplate>(e.NewValue as string);
|
||||||
|
|
||||||
HumanName.Suffixes.Add("ret");
|
HumanName.Suffixes.Add("ret");
|
||||||
HumanName.Titles.Add("professor");
|
HumanName.Titles.Add("professor");
|
||||||
@ -78,10 +80,18 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
#region Template Properties
|
#region Template Properties
|
||||||
|
|
||||||
public IEnumerable<TemplateTags> TagsRegistered => NamingTemplate.TagsRegistered.Cast<TemplateTags>();
|
public IEnumerable<TemplateTags> TagsRegistered
|
||||||
public IEnumerable<TemplateTags> TagsInUse => NamingTemplate.TagsInUse.Cast<TemplateTags>();
|
=> NamingTemplate?.TagsRegistered.Cast<TemplateTags>() ?? Enumerable.Empty<TemplateTags>();
|
||||||
public string TemplateText => NamingTemplate.TemplateText;
|
public IEnumerable<TemplateTags> TagsInUse
|
||||||
protected NamingTemplate NamingTemplate { get; private set; }
|
=> NamingTemplate?.TagsInUse.Cast<TemplateTags>() ?? Enumerable.Empty<TemplateTags>();
|
||||||
|
public string TemplateText => NamingTemplate?.TemplateText ?? "";
|
||||||
|
|
||||||
|
private readonly NamingTemplate? _namingTemplate;
|
||||||
|
protected NamingTemplate NamingTemplate
|
||||||
|
{
|
||||||
|
get => _namingTemplate ?? throw new NullReferenceException(nameof(_namingTemplate));
|
||||||
|
private init => _namingTemplate = value;
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -104,7 +114,7 @@ namespace LibationFileManager
|
|||||||
return string.Concat(NamingTemplate.Evaluate(libraryBookDto, multiChapProps).Select(p => p.Value));
|
return string.Concat(NamingTemplate.Evaluate(libraryBookDto, multiChapProps).Select(p => p.Value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LongPath GetFilename(LibraryBookDto libraryBookDto, string baseDir, string fileExtension, ReplacementCharacters replacements = null, bool returnFirstExisting = false)
|
public LongPath GetFilename(LibraryBookDto libraryBookDto, string baseDir, string fileExtension, ReplacementCharacters? replacements = null, bool returnFirstExisting = false)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
||||||
ArgumentValidator.EnsureNotNull(baseDir, nameof(baseDir));
|
ArgumentValidator.EnsureNotNull(baseDir, nameof(baseDir));
|
||||||
@ -114,7 +124,7 @@ namespace LibationFileManager
|
|||||||
return GetFilename(baseDir, fileExtension,replacements, returnFirstExisting, libraryBookDto);
|
return GetFilename(baseDir, fileExtension,replacements, returnFirstExisting, libraryBookDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LongPath GetFilename(LibraryBookDto libraryBookDto, MultiConvertFileProperties multiChapProps, string baseDir, string fileExtension, ReplacementCharacters replacements = null, bool returnFirstExisting = false)
|
public LongPath GetFilename(LibraryBookDto libraryBookDto, MultiConvertFileProperties multiChapProps, string baseDir, string fileExtension, ReplacementCharacters? replacements = null, bool returnFirstExisting = false)
|
||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
|
||||||
ArgumentValidator.EnsureNotNull(multiChapProps, nameof(multiChapProps));
|
ArgumentValidator.EnsureNotNull(multiChapProps, nameof(multiChapProps));
|
||||||
@ -246,7 +256,7 @@ namespace LibationFileManager
|
|||||||
new(caseSensative: true, StringFormatter, DateTimeFormatter, IntegerFormatter, FloatFormatter)
|
new(caseSensative: true, StringFormatter, DateTimeFormatter, IntegerFormatter, FloatFormatter)
|
||||||
{
|
{
|
||||||
//Don't allow formatting of Id
|
//Don't allow formatting of Id
|
||||||
{ TemplateTags.Id, lb => lb.AudibleProductId, v => v },
|
{ TemplateTags.Id, lb => lb.AudibleProductId, v => v ?? "" },
|
||||||
{ TemplateTags.Title, lb => lb.TitleWithSubtitle },
|
{ TemplateTags.Title, lb => lb.TitleWithSubtitle },
|
||||||
{ TemplateTags.TitleShort, lb => getTitleShort(lb.Title) },
|
{ TemplateTags.TitleShort, lb => getTitleShort(lb.Title) },
|
||||||
{ TemplateTags.AudibleTitle, lb => lb.Title },
|
{ TemplateTags.AudibleTitle, lb => lb.Title },
|
||||||
@ -308,13 +318,13 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
#region Tag Formatters
|
#region Tag Formatters
|
||||||
|
|
||||||
private static string getTitleShort(string title)
|
private static string? getTitleShort(string? title)
|
||||||
=> title?.IndexOf(':') > 0 ? title.Substring(0, title.IndexOf(':')) : title;
|
=> title?.IndexOf(':') > 0 ? title.Substring(0, title.IndexOf(':')) : title;
|
||||||
|
|
||||||
private static string getLanguageShort(string language)
|
private static string getLanguageShort(string? language)
|
||||||
{
|
{
|
||||||
if (language is null)
|
if (language is null)
|
||||||
return null;
|
return "";
|
||||||
|
|
||||||
language = language.Trim();
|
language = language.Trim();
|
||||||
if (language.Length <= 3)
|
if (language.Length <= 3)
|
||||||
@ -324,8 +334,9 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
private static string StringFormatter(ITemplateTag templateTag, string value, string formatString)
|
private static string StringFormatter(ITemplateTag templateTag, string value, string formatString)
|
||||||
{
|
{
|
||||||
if (string.Compare(formatString, "u", ignoreCase: true) == 0) return value?.ToUpper();
|
if (value is null) return "";
|
||||||
else if (string.Compare(formatString, "l", ignoreCase: true) == 0) return value?.ToLower();
|
else if (string.Compare(formatString, "u", ignoreCase: true) == 0) return value.ToUpper();
|
||||||
|
else if (string.Compare(formatString, "l", ignoreCase: true) == 0) return value.ToLower();
|
||||||
else return value;
|
else return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +369,7 @@ namespace LibationFileManager
|
|||||||
public class FolderTemplate : Templates, ITemplate
|
public class FolderTemplate : Templates, ITemplate
|
||||||
{
|
{
|
||||||
public static string Name { get; }= "Folder Template";
|
public static string Name { get; }= "Folder Template";
|
||||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.FolderTemplate));
|
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.FolderTemplate)) ?? "";
|
||||||
public static string DefaultTemplate { get; } = "<title short> [<id>]";
|
public static string DefaultTemplate { get; } = "<title short> [<id>]";
|
||||||
public static IEnumerable<TagCollection> TagCollections
|
public static IEnumerable<TagCollection> TagCollections
|
||||||
=> new TagCollection[] { filePropertyTags, conditionalTags, folderConditionalTags };
|
=> new TagCollection[] { filePropertyTags, conditionalTags, folderConditionalTags };
|
||||||
@ -378,7 +389,7 @@ namespace LibationFileManager
|
|||||||
public class FileTemplate : Templates, ITemplate
|
public class FileTemplate : Templates, ITemplate
|
||||||
{
|
{
|
||||||
public static string Name { get; } = "File Template";
|
public static string Name { get; } = "File Template";
|
||||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.FileTemplate));
|
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.FileTemplate)) ?? "";
|
||||||
public static string DefaultTemplate { get; } = "<title> [<id>]";
|
public static string DefaultTemplate { get; } = "<title> [<id>]";
|
||||||
public static IEnumerable<TagCollection> TagCollections { get; } = new TagCollection[] { filePropertyTags, conditionalTags };
|
public static IEnumerable<TagCollection> TagCollections { get; } = new TagCollection[] { filePropertyTags, conditionalTags };
|
||||||
}
|
}
|
||||||
@ -386,7 +397,7 @@ namespace LibationFileManager
|
|||||||
public class ChapterFileTemplate : Templates, ITemplate
|
public class ChapterFileTemplate : Templates, ITemplate
|
||||||
{
|
{
|
||||||
public static string Name { get; } = "Chapter File Template";
|
public static string Name { get; } = "Chapter File Template";
|
||||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate));
|
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.ChapterFileTemplate)) ?? "";
|
||||||
public static string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
|
public static string DefaultTemplate { get; } = "<title> [<id>] - <ch# 0> - <ch title>";
|
||||||
public static IEnumerable<TagCollection> TagCollections { get; } = chapterPropertyTags.Append(filePropertyTags).Append(conditionalTags);
|
public static IEnumerable<TagCollection> TagCollections { get; } = chapterPropertyTags.Append(filePropertyTags).Append(conditionalTags);
|
||||||
|
|
||||||
@ -399,7 +410,7 @@ namespace LibationFileManager
|
|||||||
public class ChapterTitleTemplate : Templates, ITemplate
|
public class ChapterTitleTemplate : Templates, ITemplate
|
||||||
{
|
{
|
||||||
public static string Name { get; } = "Chapter Title Template";
|
public static string Name { get; } = "Chapter Title Template";
|
||||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.ChapterTitleTemplate));
|
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.ChapterTitleTemplate)) ?? "";
|
||||||
public static string DefaultTemplate => "<ch#> - <title short>: <ch title>";
|
public static string DefaultTemplate => "<ch#> - <title short>: <ch title>";
|
||||||
public static IEnumerable<TagCollection> TagCollections { get; } = chapterPropertyTags.Append(conditionalTags);
|
public static IEnumerable<TagCollection> TagCollections { get; } = chapterPropertyTags.Append(conditionalTags);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user