diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj
index 418149e0..51a8046c 100644
--- a/Source/AppScaffolding/AppScaffolding.csproj
+++ b/Source/AppScaffolding/AppScaffolding.csproj
@@ -5,7 +5,7 @@
10.6.3.1
-
+
diff --git a/Source/FileManager/BackgroundFileSystem.cs b/Source/FileManager/BackgroundFileSystem.cs
index 3b815c43..877f9160 100644
--- a/Source/FileManager/BackgroundFileSystem.cs
+++ b/Source/FileManager/BackgroundFileSystem.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
+#nullable enable
namespace FileManager
{
///
@@ -16,9 +17,9 @@ namespace FileManager
public string SearchPattern { get; private set; }
public SearchOption SearchOption { get; private set; }
- private FileSystemWatcher fileSystemWatcher { get; set; }
- private BlockingCollection directoryChangesEvents { get; set; }
- private Task backgroundScanner { get; set; }
+ private FileSystemWatcher? fileSystemWatcher { get; set; }
+ private BlockingCollection? directoryChangesEvents { get; set; }
+ private Task? backgroundScanner { get; set; }
private object fsCacheLocker { get; } = new();
private List fsCache { get; } = new();
@@ -32,7 +33,7 @@ namespace FileManager
Init();
}
- public LongPath FindFile(System.Text.RegularExpressions.Regex regex)
+ public LongPath? FindFile(System.Text.RegularExpressions.Regex regex)
{
lock (fsCacheLocker)
return fsCache.FirstOrDefault(s => regex.IsMatch(s));
@@ -105,13 +106,13 @@ namespace FileManager
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
- directoryChangesEvents.Add(e);
+ directoryChangesEvents?.Add(e);
}
#region Background Thread
private void BackgroundScanner()
{
- while (directoryChangesEvents.TryTake(out FileSystemEventArgs change, -1))
+ while (directoryChangesEvents?.TryTake(out var change, -1) is true)
{
lock (fsCacheLocker)
UpdateLocalCache(change);
diff --git a/Source/FileManager/FileUtility.cs b/Source/FileManager/FileUtility.cs
index 748d27ec..e6d9074c 100644
--- a/Source/FileManager/FileUtility.cs
+++ b/Source/FileManager/FileUtility.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
@@ -7,20 +8,20 @@ using Dinah.Core;
using Polly;
using Polly.Retry;
+#nullable enable
namespace FileManager
{
public static class FileUtility
{
-
-
///
/// "txt" => ".txt"
///
".txt" => ".txt"
///
null or whitespace => ""
///
- public static string GetStandardizedExtension(string extension)
+ [return: NotNull]
+ public static string GetStandardizedExtension(string? extension)
=> string.IsNullOrWhiteSpace(extension)
- ? (extension ?? "")?.Trim()
+ ? string.Empty
: '.' + extension.Trim().Trim('.');
///
@@ -48,18 +49,18 @@ namespace FileManager
///
- ensure uniqueness
///
- enforce max file length
///
- 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(fileExtension, nameof(fileExtension));
+ ArgumentValidator.EnsureNotNull(replacements, nameof(replacements));
+
fileExtension = GetStandardizedExtension(fileExtension);
// remove invalid chars
path = GetSafePath(path, replacements);
// ensure uniqueness and check lengths
- var dir = Path.GetDirectoryName(path);
- dir = dir?.TruncateFilename(LongPath.MaxDirectoryLength) ?? string.Empty;
+ var dir = Path.GetDirectoryName(path)?.TruncateFilename(LongPath.MaxDirectoryLength) ?? string.Empty;
var fileName = Path.GetFileName(path);
var extIndex = fileName.LastIndexOf(fileExtension, StringComparison.OrdinalIgnoreCase);
@@ -84,6 +85,7 @@ namespace FileManager
public static LongPath GetSafePath(LongPath path, ReplacementCharacters replacements)
{
ArgumentValidator.EnsureNotNull(path, nameof(path));
+ ArgumentValidator.EnsureNotNull(replacements, nameof(replacements));
var pathNoPrefix = path.PathWithoutPrefix;
@@ -159,7 +161,7 @@ namespace FileManager
LongPath source,
LongPath destination,
ReplacementCharacters replacements,
- string extension = null,
+ string? extension = null,
bool overwrite = false)
{
extension ??= Path.GetExtension(source);
@@ -213,6 +215,9 @@ namespace FileManager
SaferDelete(destination);
var dir = Path.GetDirectoryName(destination);
+ if (dir is null)
+ throw new DirectoryNotFoundException();
+
Serilog.Log.Logger.Debug("Attempt to create directory: {@DebugText}", new { dir });
Directory.CreateDirectory(dir);
diff --git a/Source/FileManager/LogArchiver.cs b/Source/FileManager/LogArchiver.cs
index 248d26eb..04342220 100644
--- a/Source/FileManager/LogArchiver.cs
+++ b/Source/FileManager/LogArchiver.cs
@@ -8,9 +8,10 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
+#nullable enable
namespace FileManager
{
- public sealed class LogArchiver : IAsyncDisposable
+ public sealed class LogArchiver : IAsyncDisposable, IDisposable
{
public Encoding Encoding { get; set; }
public string FileName { get; }
@@ -39,28 +40,28 @@ namespace FileManager
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));
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));
await AddFileAsync(name, Encoding.GetBytes(contents), comment);
}
- public Task AddFileAsync(string name, ReadOnlyMemory contents, string comment = null)
+ public Task AddFileAsync(string name, ReadOnlyMemory contents, string? comment = null)
{
ArgumentValidator.EnsureNotNull(name, nameof(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 void AddfileInternal(string name, ReadOnlySpan contents, string comment)
+ private void AddFileInternal(string name, ReadOnlySpan contents, string? comment)
{
lock (lockObj)
{
@@ -73,5 +74,7 @@ namespace FileManager
}
public async ValueTask DisposeAsync() => await Task.Run(archive.Dispose);
+
+ public void Dispose() => archive.Dispose();
}
}
diff --git a/Source/FileManager/LongPath.cs b/Source/FileManager/LongPath.cs
index 956f46a4..a6e00e3d 100644
--- a/Source/FileManager/LongPath.cs
+++ b/Source/FileManager/LongPath.cs
@@ -1,9 +1,11 @@
using Newtonsoft.Json;
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
+#nullable enable
namespace FileManager
{
public class LongPath
@@ -15,9 +17,9 @@ namespace FileManager
public static readonly int MaxPathLength;
private const int WIN_MAX_PATH = 260;
private const string WIN_LONG_PATH_PREFIX = @"\\?\";
- internal static readonly bool IsWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
- internal static readonly bool IsLinux = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
- internal static readonly bool IsOSX = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ internal static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+ internal static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
+ internal static readonly bool IsOSX = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public string Path { get; }
@@ -60,7 +62,8 @@ namespace FileManager
=> IsWindows ? filename.Length
: 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;
@@ -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]
public string ShortPathName
@@ -127,8 +131,6 @@ namespace FileManager
//for newly-created entries in ther file system. Existing entries made while
//8dot3 names were disabled will not be reachable by short paths.
- if (Path is null) return null;
-
StringBuilder shortPathBuffer = new(MaxPathLength);
GetShortPathName(Path, shortPathBuffer, MaxPathLength);
return shortPathBuffer.ToString();
@@ -141,7 +143,6 @@ namespace FileManager
get
{
if (!IsWindows) return Path;
- if (Path is null) return null;
StringBuilder longPathBuffer = new(MaxPathLength);
GetLongPathName(Path, longPathBuffer, MaxPathLength);
@@ -156,17 +157,18 @@ namespace FileManager
{
if (!IsWindows) return Path;
return
- Path?.StartsWith(WIN_LONG_PATH_PREFIX) == true ? Path.Remove(0, WIN_LONG_PATH_PREFIX.Length)
- :Path;
+ Path.StartsWith(WIN_LONG_PATH_PREFIX)
+ ? Path.Remove(0, WIN_LONG_PATH_PREFIX.Length)
+ : Path;
}
}
public override string ToString() => Path;
public override int GetHashCode() => Path.GetHashCode();
- 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);
+ public override bool Equals(object? obj) => obj is LongPath other && Path == other.Path;
+ public static bool operator ==(LongPath? path1, LongPath? path2) => path1?.Equals(path2) is true;
+ public static bool operator !=(LongPath? path1, LongPath? path2) => path1 is null || path2 is null || !path1.Equals(path2);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
diff --git a/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs b/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs
index c2d2a8b1..1cefe52f 100644
--- a/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs
+++ b/Source/FileManager/NamingTemplate/ConditionalTagCollection[TClass].cs
@@ -1,7 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
+#nullable enable
namespace FileManager.NamingTemplate;
internal interface IClosingPropertyTag : IPropertyTag
@@ -17,7 +19,7 @@ internal interface IClosingPropertyTag : IPropertyTag
/// The substring that was matched.
/// The registered
/// True if the starts with this tag.
- 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 : TagCollection
@@ -37,6 +39,7 @@ public class ConditionalTagCollection : TagCollection
private class ConditionalTag : TagBase, IClosingPropertyTag
{
+ public override Regex NameMatcher { get; }
public Regex NameCloseMatcher { get; }
public ConditionalTag(ITemplateTag templateTag, RegexOptions options, Expression conditionExpression)
@@ -46,7 +49,7 @@ public class ConditionalTagCollection : TagCollection
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);
if (match.Success)
diff --git a/Source/FileManager/NamingTemplate/NamingTemplate.cs b/Source/FileManager/NamingTemplate/NamingTemplate.cs
index acd51bb1..0c243398 100644
--- a/Source/FileManager/NamingTemplate/NamingTemplate.cs
+++ b/Source/FileManager/NamingTemplate/NamingTemplate.cs
@@ -1,19 +1,21 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
+#nullable enable
namespace FileManager.NamingTemplate;
public class NamingTemplate
{
- public string TemplateText { get; private set; }
+ public string TemplateText { get; private set; } = string.Empty;
public IEnumerable TagsInUse => _tagsInUse;
public IEnumerable TagsRegistered => TagCollections.SelectMany(t => t).DistinctBy(t => t.TagName);
public IEnumerable Warnings => errors.Concat(warnings);
public IEnumerable Errors => errors;
- private Delegate templateToString;
+ private Delegate? templateToString;
private readonly List warnings = new();
private readonly List errors = new();
private readonly IEnumerable TagCollections;
@@ -30,6 +32,9 @@ public class NamingTemplate
/// Instances of the TClass used in and
public TemplatePart Evaluate(params object[] propertyClasses)
{
+ if (templateToString is null)
+ throw new InvalidOperationException();
+
// Match propertyClasses to the arguments required by templateToString.DynamicInvoke().
// First parameter is "this", so ignore it.
var delegateArgTypes = templateToString.Method.GetParameters().Skip(1);
@@ -39,7 +44,7 @@ public class NamingTemplate
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())}");
- return ((TemplatePart)templateToString.DynamicInvoke(args)).FirstPart;
+ return (templateToString.DynamicInvoke(args) as TemplatePart)!.FirstPart;
}
/// Parse a template string to a
@@ -69,7 +74,7 @@ public class NamingTemplate
}
/// Builds an tree that will evaluate to a
- private static Expression GetExpressionTree(BinaryNode node)
+ private static Expression GetExpressionTree(BinaryNode? node)
{
if (node is null) return TemplatePart.Blank;
else if (node.IsValue) return node.Expression;
@@ -81,10 +86,10 @@ public class NamingTemplate
}
/// Parse a template string into a tree
- private BinaryNode IntermediateParse(string templateString)
+ private BinaryNode IntermediateParse(string? templateString)
{
if (templateString is null)
- throw new NullReferenceException(ERROR_NULL_IS_INVALID);
+ throw new ArgumentException(ERROR_NULL_IS_INVALID);
else if (string.IsNullOrEmpty(templateString))
warnings.Add(WARNING_EMPTY);
else if (string.IsNullOrWhiteSpace(templateString))
@@ -93,12 +98,12 @@ public class NamingTemplate
TemplateText = templateString;
BinaryNode topNode = BinaryNode.CreateRoot();
- BinaryNode currentNode = topNode;
+ BinaryNode? currentNode = topNode;
List literalChars = new();
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();
@@ -116,7 +121,7 @@ public class NamingTemplate
{
checkAndAddLiterals();
- BinaryNode lastParenth = currentNode;
+ BinaryNode? lastParenth = currentNode;
while (lastParenth?.IsConditional is false)
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)
{
@@ -182,7 +187,7 @@ public class NamingTemplate
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)
{
@@ -198,36 +203,36 @@ public class NamingTemplate
private class BinaryNode
{
public string Name { get; }
- public BinaryNode Parent { get; private set; }
- public BinaryNode RightChild { get; private set; }
- public BinaryNode LeftChild { get; private set; }
- public Expression Expression { get; private init; }
+ public BinaryNode? Parent { get; private set; }
+ public BinaryNode? RightChild { get; private set; }
+ public BinaryNode? LeftChild { get; private set; }
+ public Expression Expression { get; }
public bool IsConditional { 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")
- {
- IsValue = true,
- Expression = TemplatePart.CreateLiteral(literal)
- };
+ public static BinaryNode CreateValue(string literal)
+ => new("Literal", TemplatePart.CreateLiteral(literal))
+ {
+ IsValue = true
+ };
- public static BinaryNode CreateValue(ITemplateTag templateTag, Expression property) => new(templateTag.TagName)
- {
- IsValue = true,
- Expression = TemplatePart.CreateProperty(templateTag, property)
- };
+ public static BinaryNode CreateValue(ITemplateTag templateTag, Expression property)
+ => new(templateTag.TagName, TemplatePart.CreateProperty(templateTag, property))
+ {
+ IsValue = true
+ };
- public static BinaryNode CreateConditional(ITemplateTag templateTag, Expression property) => new(templateTag.TagName)
- {
- IsConditional = true,
- Expression = property
- };
+ public static BinaryNode CreateConditional(ITemplateTag templateTag, Expression property)
+ => new(templateTag.TagName, property)
+ {
+ IsConditional = true
+ };
private static BinaryNode CreateConcatenation(BinaryNode left, BinaryNode right)
{
- var newNode = new BinaryNode("Concatenation")
+ var newNode = new BinaryNode("Concatenation", Expression.Empty())
{
LeftChild = left,
RightChild = right
@@ -237,7 +242,12 @@ public class NamingTemplate
return newNode;
}
- private BinaryNode(string name) => Name = name;
+ private BinaryNode(string name, Expression expression)
+ {
+ Name = name;
+ Expression = expression;
+ }
+
public override string ToString() => Name;
public BinaryNode AddNewNode(BinaryNode newNode)
diff --git a/Source/FileManager/NamingTemplate/PropertyTagCollection[TClass].cs b/Source/FileManager/NamingTemplate/PropertyTagCollection[TClass].cs
index 118956a7..9f3acc75 100644
--- a/Source/FileManager/NamingTemplate/PropertyTagCollection[TClass].cs
+++ b/Source/FileManager/NamingTemplate/PropertyTagCollection[TClass].cs
@@ -5,6 +5,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
+#nullable enable
namespace FileManager.NamingTemplate;
public delegate string PropertyFormatter(ITemplateTag templateTag, T value, string formatString);
@@ -37,7 +38,7 @@ public class PropertyTagCollection : TagCollection
/// Optional formatting function that accepts the property
/// and a formatting string and returnes the value the formatted string. If , use the default
/// formatter if present, or
- public void Add(ITemplateTag templateTag, Func propertyGetter, PropertyFormatter formatter = null)
+ public void Add(ITemplateTag templateTag, Func propertyGetter, PropertyFormatter? formatter = null)
where TProperty : struct
=> RegisterWithFormatter(templateTag, propertyGetter, formatter);
@@ -59,7 +60,7 @@ public class PropertyTagCollection : TagCollection
/// Optional formatting function that accepts the property
/// and a formatting string and returnes the value formatted to string. If , use the default
/// formatter if present, or
- public void Add(ITemplateTag templateTag, Func propertyGetter, PropertyFormatter formatter = null)
+ public void Add(ITemplateTag templateTag, Func propertyGetter, PropertyFormatter? formatter = null)
=> RegisterWithFormatter(templateTag, propertyGetter, formatter);
///
@@ -72,14 +73,15 @@ public class PropertyTagCollection : TagCollection
=> RegisterWithToString(templateTag, propertyGetter, toString);
private void RegisterWithFormatter
- (ITemplateTag templateTag, Func propertyGetter, PropertyFormatter formatter)
+ (ITemplateTag templateTag, Func propertyGetter, PropertyFormatter? formatter)
{
ArgumentValidator.EnsureNotNull(templateTag, nameof(templateTag));
ArgumentValidator.EnsureNotNull(propertyGetter, nameof(propertyGetter));
var expr = Expression.Call(Expression.Constant(propertyGetter.Target), propertyGetter.Method, Parameter);
+ formatter ??= GetDefaultFormatter();
- if ((formatter ??= GetDefaultFormatter()) is null)
+ if (formatter is null)
AddPropertyTag(new PropertyTag(templateTag, Options, expr, ToStringFunc));
else
AddPropertyTag(new PropertyTag(templateTag, Options, expr, formatter));
@@ -97,7 +99,7 @@ public class PropertyTagCollection : TagCollection
private static string ToStringFunc(T propertyValue) => propertyValue?.ToString() ?? "";
- private PropertyFormatter GetDefaultFormatter()
+ private PropertyFormatter? GetDefaultFormatter()
{
try
{
@@ -109,6 +111,7 @@ public class PropertyTagCollection : TagCollection
private class PropertyTag : TagBase
{
+ public override Regex NameMatcher { get; }
private Func CreateToStringExpression { get; }
public PropertyTag(ITemplateTag templateTag, RegexOptions options, Expression propertyGetter, PropertyFormatter formatter)
diff --git a/Source/FileManager/NamingTemplate/TagBase.cs b/Source/FileManager/NamingTemplate/TagBase.cs
index a6bf9864..264e48a3 100644
--- a/Source/FileManager/NamingTemplate/TagBase.cs
+++ b/Source/FileManager/NamingTemplate/TagBase.cs
@@ -1,7 +1,9 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
+#nullable enable
namespace FileManager.NamingTemplate;
internal interface IPropertyTag
@@ -22,13 +24,13 @@ internal interface IPropertyTag
/// The substring that was matched.
/// The that returns the property's value
/// True if the starts with this tag.
- 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
{
public ITemplateTag TemplateTag { get; }
- public Regex NameMatcher { get; protected init; }
+ public abstract Regex NameMatcher { get; }
public Type ReturnType => ValueExpression.Type;
protected Expression ValueExpression { get; }
@@ -43,7 +45,7 @@ internal abstract class TagBase : IPropertyTag
/// The optional format string in the match inside the square brackets
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);
if (match.Success)
diff --git a/Source/FileManager/NamingTemplate/TagCollection.cs b/Source/FileManager/NamingTemplate/TagCollection.cs
index 8e793571..117c3a33 100644
--- a/Source/FileManager/NamingTemplate/TagCollection.cs
+++ b/Source/FileManager/NamingTemplate/TagCollection.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
+#nullable enable
namespace FileManager.NamingTemplate;
/// A collection of s registered to a single .
@@ -32,7 +34,7 @@ public abstract class TagCollection : IEnumerable
/// The substring that was matched.
/// The that returns the 's value
/// True if the starts with a tag registered in this class.
- 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)
{
@@ -57,7 +59,7 @@ public abstract class TagCollection : IEnumerable
/// The substring that was matched.
/// The registered
/// True if the starts with this tag.
- 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())
{
diff --git a/Source/FileManager/NamingTemplate/TemplatePart.cs b/Source/FileManager/NamingTemplate/TemplatePart.cs
index 1b7f7630..447a9e0f 100644
--- a/Source/FileManager/NamingTemplate/TemplatePart.cs
+++ b/Source/FileManager/NamingTemplate/TemplatePart.cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
+#nullable enable
namespace FileManager.NamingTemplate;
/// Represents one part of an evaluated .
@@ -15,13 +16,13 @@ public class TemplatePart : IEnumerable
/// The 's if is
/// a registered property, otherwise for string literals.
- public ITemplateTag TemplateTag { get; }
+ public ITemplateTag? TemplateTag { get; }
/// The evaluated string.
public string Value { get; }
- private TemplatePart previous;
- private TemplatePart next;
+ private TemplatePart? previous;
+ private TemplatePart? next;
private TemplatePart(string name, string value)
{
TagName = name;
@@ -53,14 +54,33 @@ public class TemplatePart : IEnumerable
private static Expression CreateExpression(string name, Expression value)
=> Expression.New(constructorInfo, Expression.Constant(name), value);
- private static readonly ConstructorInfo constructorInfo
- = typeof(TemplatePart).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(string), typeof(string) });
+ private static readonly ConstructorInfo constructorInfo;
+ private static readonly ConstructorInfo tagTemplateConstructorInfo;
+ private static readonly MethodInfo addMethodInfo;
+ static TemplatePart()
+ {
+ var type = typeof(TemplatePart);
- private static readonly ConstructorInfo tagTemplateConstructorInfo
- = typeof(TemplatePart).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(ITemplateTag), typeof(string) });
+ if (type.GetConstructor(
+ BindingFlags.NonPublic | BindingFlags.Instance,
+ new Type[] { typeof(string), typeof(string) }) is not ConstructorInfo c1)
+ throw new MissingMethodException(nameof(TemplatePart));
- private static readonly MethodInfo addMethodInfo
- = typeof(TemplatePart).GetMethod(nameof(Concatenate), BindingFlags.NonPublic | BindingFlags.Static, new Type[] { typeof(TemplatePart), typeof(TemplatePart) });
+ if (type.GetConstructor(
+ 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 GetEnumerator()
{
diff --git a/Source/FileManager/PersistentDictionary.cs b/Source/FileManager/PersistentDictionary.cs
index 41918e99..3ebbd1cc 100644
--- a/Source/FileManager/PersistentDictionary.cs
+++ b/Source/FileManager/PersistentDictionary.cs
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+#nullable enable
namespace FileManager
{
public class PersistentDictionary
@@ -13,19 +15,19 @@ namespace FileManager
public bool IsReadOnly { get; }
// optimize for strings. expectation is most settings will be strings and a rare exception will be something else
- private Dictionary stringCache { get; } = new Dictionary();
- private Dictionary objectCache { get; } = new Dictionary();
+ private Dictionary stringCache { get; } = new();
+ private Dictionary objectCache { get; } = new();
public PersistentDictionary(string filepath, bool isReadOnly = false)
{
Filepath = filepath;
IsReadOnly = isReadOnly;
- if (File.Exists(Filepath))
+ if (File.Exists(Filepath) || Path.GetDirectoryName(Filepath) is not string dirName)
return;
// will create any missing directories, incl subdirectories. if all already exist: no action
- Directory.CreateDirectory(Path.GetDirectoryName(filepath));
+ Directory.CreateDirectory(dirName);
if (IsReadOnly)
return;
@@ -33,13 +35,14 @@ namespace FileManager
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))
{
var jObject = readFile();
if (jObject.ContainsKey(propertyName))
- stringCache[propertyName] = jObject[propertyName].Value();
+ stringCache[propertyName] = jObject[propertyName]?.Value();
else
stringCache[propertyName] = defaultValue;
}
@@ -47,7 +50,8 @@ namespace FileManager
return stringCache[propertyName];
}
- public T GetNonString(string propertyName, T defaultValue = default)
+ [return: NotNullIfNotNull(nameof(defaultValue))]
+ public T? GetNonString(string propertyName, T? defaultValue = default)
{
var obj = GetObject(propertyName);
@@ -72,21 +76,21 @@ namespace FileManager
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))
{
var jObject = readFile();
if (!jObject.ContainsKey(propertyName))
return null;
- objectCache[propertyName] = jObject[propertyName].Value
-
-
+
+
-
-
-
-
+
+
+
+
diff --git a/Source/LibationAvalonia/LibationAvalonia.csproj b/Source/LibationAvalonia/LibationAvalonia.csproj
index 7972f931..c284d70b 100644
--- a/Source/LibationAvalonia/LibationAvalonia.csproj
+++ b/Source/LibationAvalonia/LibationAvalonia.csproj
@@ -70,13 +70,13 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/Source/LibationFileManager/AudibleFileStorage.cs b/Source/LibationFileManager/AudibleFileStorage.cs
index 55157e92..8ca7a356 100644
--- a/Source/LibationFileManager/AudibleFileStorage.cs
+++ b/Source/LibationFileManager/AudibleFileStorage.cs
@@ -9,11 +9,12 @@ using System.Threading.Tasks;
using System.Threading;
using FileManager;
+#nullable enable
namespace LibationFileManager
{
public abstract class AudibleFileStorage
{
- protected abstract LongPath GetFilePathCustom(string productId);
+ protected abstract LongPath? GetFilePathCustom(string productId);
protected abstract List GetFilePathsCustom(string productId);
#region static
@@ -57,7 +58,7 @@ namespace LibationFileManager
regexTemplate = $@"{{0}}.*?\.({extAggr})$";
}
- protected LongPath GetFilePath(string productId)
+ protected LongPath? GetFilePath(string productId)
{
// primary lookup
var cachedFile = FilePathCache.GetFirstPath(productId, FileType);
@@ -87,7 +88,7 @@ namespace LibationFileManager
{
internal AaxcFileStorage() : base(FileType.AAXC) { }
- protected override LongPath GetFilePathCustom(string productId)
+ protected override LongPath? GetFilePathCustom(string productId)
=> GetFilePathsCustom(productId).FirstOrDefault();
protected override List GetFilePathsCustom(string productId)
@@ -104,9 +105,9 @@ namespace LibationFileManager
public class AudioFileStorage : AudibleFileStorage
{
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 EnumerationOptions enumerationOptions { get; } = new()
{
@@ -115,17 +116,20 @@ namespace LibationFileManager
MatchCasing = MatchCasing.CaseInsensitive
};
- protected override LongPath GetFilePathCustom(string productId)
+ protected override LongPath? GetFilePathCustom(string productId)
=> GetFilePathsCustom(productId).FirstOrDefault();
- protected override List GetFilePathsCustom(string productId)
+ private static BackgroundFileSystem newBookDirectoryFiles()
+ => new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
+
+ protected override List GetFilePathsCustom(string productId)
{
// If user changed the BooksDirectory: reinitialize
lock (bookDirectoryFilesLocker)
- if (BooksDirectory != BookDirectoryFiles.RootDirectory)
- BookDirectoryFiles = new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
+ if (BooksDirectory != BookDirectoryFiles?.RootDirectory)
+ BookDirectoryFiles = newBookDirectoryFiles();
- var regex = GetBookSearchRegex(productId);
+ var regex = GetBookSearchRegex(productId);
//Find all extant files matching the productId
//using both the file system and the file path cache
@@ -138,9 +142,16 @@ namespace LibationFileManager
.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 FindAudiobooksAsync(LongPath searchDirectory, [EnumeratorCancellation] CancellationToken cancellationToken)
{
@@ -151,7 +162,7 @@ namespace LibationFileManager
if (cancellationToken.IsCancellationRequested)
yield break;
- FilePathCache.CacheEntry audioFile = default;
+ FilePathCache.CacheEntry? audioFile = default;
try
{
diff --git a/Source/LibationFileManager/Configuration.Environment.cs b/Source/LibationFileManager/Configuration.Environment.cs
index 55ce4fdc..66ba5302 100644
--- a/Source/LibationFileManager/Configuration.Environment.cs
+++ b/Source/LibationFileManager/Configuration.Environment.cs
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
+#nullable enable
namespace LibationFileManager
{
[Flags]
@@ -20,7 +21,7 @@ namespace LibationFileManager
public static bool IsWindows { get; } = OperatingSystem.IsWindows();
public static bool IsLinux { get; } = OperatingSystem.IsLinux();
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 OS OS { get; }
diff --git a/Source/LibationFileManager/Configuration.HelpText.cs b/Source/LibationFileManager/Configuration.HelpText.cs
index a3c747bc..8eb7679c 100644
--- a/Source/LibationFileManager/Configuration.HelpText.cs
+++ b/Source/LibationFileManager/Configuration.HelpText.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
+#nullable enable
namespace LibationFileManager
{
public partial class Configuration
@@ -29,8 +30,7 @@ namespace LibationFileManager
}
.AsReadOnly();
- public static string GetHelpText(string settingName)
+ public static string? GetHelpText(string settingName)
=> HelpText.TryGetValue(settingName, out var value) ? value : null;
-
}
}
diff --git a/Source/LibationFileManager/Configuration.KnownDirectories.cs b/Source/LibationFileManager/Configuration.KnownDirectories.cs
index d989b616..0549d54d 100644
--- a/Source/LibationFileManager/Configuration.KnownDirectories.cs
+++ b/Source/LibationFileManager/Configuration.KnownDirectories.cs
@@ -5,11 +5,12 @@ using System.IO;
using System.Linq;
using Dinah.Core;
+#nullable enable
namespace LibationFileManager
{
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_Absolute => Path.GetFullPath(Path.Combine(ProcessDirectory, LIBATION_FILES_KEY));
public static string MyDocs => Path.GetFullPath(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Libation"));
@@ -36,7 +37,7 @@ namespace LibationFileManager
LibationFiles = 5
}
// use func calls so we always get the latest value of LibationFiles
- private static List<(KnownDirectories directory, Func getPathFunc)> directoryOptionsPaths { get; } = new()
+ private static List<(KnownDirectories directory, Func getPathFunc)> directoryOptionsPaths { get; } = new()
{
(KnownDirectories.None, () => null),
(KnownDirectories.UserProfile, () => UserProfile),
@@ -47,7 +48,7 @@ namespace LibationFileManager
// also, keep this at bottom of this list
(KnownDirectories.LibationFiles, () => libationFilesPathCache)
};
- public static string GetKnownDirectoryPath(KnownDirectories directory)
+ public static string? GetKnownDirectoryPath(KnownDirectories directory)
{
var dirFunc = directoryOptionsPaths.SingleOrDefault(dirFunc => dirFunc.directory == directory);
return dirFunc == default ? null : dirFunc.getPathFunc();
diff --git a/Source/LibationFileManager/Configuration.LibationFiles.cs b/Source/LibationFileManager/Configuration.LibationFiles.cs
index 553c6a5f..00246d99 100644
--- a/Source/LibationFileManager/Configuration.LibationFiles.cs
+++ b/Source/LibationFileManager/Configuration.LibationFiles.cs
@@ -7,6 +7,7 @@ using Newtonsoft.Json;
using Serilog;
using Dinah.Core.Logging;
+#nullable enable
namespace LibationFileManager
{
public partial class Configuration
@@ -44,7 +45,7 @@ namespace LibationFileManager
}
}
- private static string libationFilesPathCache { get; set; }
+ private static string? libationFilesPathCache { get; set; }
///
/// 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
// verify from live file. no try/catch. want failures to be visible
var jObjFinal = JObject.Parse(File.ReadAllText(AppsettingsJsonFile));
- var valueFinal = jObjFinal[LIBATION_FILES_KEY].Value();
+
+ if (jObjFinal[LIBATION_FILES_KEY]?.Value() is not string valueFinal)
+ throw new InvalidDataException($"{LIBATION_FILES_KEY} not found in {AppsettingsJsonFile}");
+
return valueFinal;
}
diff --git a/Source/LibationFileManager/Configuration.Logging.cs b/Source/LibationFileManager/Configuration.Logging.cs
index 3a7dcee4..66ea7cda 100644
--- a/Source/LibationFileManager/Configuration.Logging.cs
+++ b/Source/LibationFileManager/Configuration.Logging.cs
@@ -1,19 +1,18 @@
using System;
-using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
-using Dinah.Core;
using Dinah.Core.Logging;
using FileManager;
using Microsoft.Extensions.Configuration;
using Serilog;
using Serilog.Events;
+#nullable enable
namespace LibationFileManager
{
public partial class Configuration
{
- private IConfigurationRoot configuration;
+ private IConfigurationRoot? configuration;
public void ConfigureLogging()
{
@@ -31,20 +30,20 @@ namespace LibationFileManager
{
get
{
- var logLevelStr = persistentDictionary.GetStringFromJsonPath("Serilog", "MinimumLevel");
+ var logLevelStr = Settings.GetStringFromJsonPath("Serilog", "MinimumLevel");
return Enum.TryParse(logLevelStr, out var logLevelEnum) ? logLevelEnum : LogEventLevel.Information;
}
set
{
OnPropertyChanging(nameof(LogLevel), LogLevel, value);
- var valueWasChanged = persistentDictionary.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
+ var valueWasChanged = Settings.SetWithJsonPath("Serilog", "MinimumLevel", value.ToString());
if (!valueWasChanged)
{
Log.Logger.Debug("LogLevel.set attempt. No change");
return;
}
- configuration.Reload();
+ configuration?.Reload();
OnPropertyChanged(nameof(LogLevel), value);
diff --git a/Source/LibationFileManager/Configuration.PersistentSettings.cs b/Source/LibationFileManager/Configuration.PersistentSettings.cs
index 82a91193..bac5e03b 100644
--- a/Source/LibationFileManager/Configuration.PersistentSettings.cs
+++ b/Source/LibationFileManager/Configuration.PersistentSettings.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
@@ -8,6 +9,7 @@ using FileManager;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
+#nullable enable
namespace LibationFileManager
{
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
// 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 defaultValue, [CallerMemberName] string propertyName = "") => persistentDictionary.GetNonString(propertyName, defaultValue);
- public object GetObject([CallerMemberName] string propertyName = "") => persistentDictionary.GetObject(propertyName);
- public string GetString(string defaultValue = null, [CallerMemberName] string propertyName = "") => persistentDictionary.GetString(propertyName, defaultValue);
- public void SetNonString(object newValue, [CallerMemberName] string propertyName = "")
+ public bool RemoveProperty(string propertyName) => Settings.RemoveProperty(propertyName);
+
+ [return: NotNullIfNotNull(nameof(defaultValue))]
+ public T? GetNonString(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);
if (existing?.Equals(newValue) is true) return;
OnPropertyChanging(propertyName, existing, newValue);
- persistentDictionary.SetNonString(propertyName, newValue);
+ Settings.SetNonString(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);
if (existing?.Equals(newValue) is true) return;
OnPropertyChanging(propertyName, existing, newValue);
- persistentDictionary.SetString(propertyName, newValue);
+ Settings.SetString(propertyName, newValue);
OnPropertyChanged(propertyName, newValue);
}
- private object getExistingValue(string propertyName)
+ private object? getExistingValue(string propertyName)
{
var property = GetType().GetProperty(propertyName);
if (property is not null) return property.GetValue(this);
@@ -53,16 +73,16 @@ namespace LibationFileManager
}
/// WILL ONLY set if already present. WILL NOT create new
- 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)
configuration?.Reload();
}
public string SettingsFilePath => Path.Combine(LibationFiles, "Settings.json");
- public static string GetDescription(string propertyName)
+ public static string? GetDescription(string propertyName)
{
var attribute = typeof(Configuration)
.GetProperty(propertyName)
@@ -73,7 +93,7 @@ namespace LibationFileManager
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.")]
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); }
[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?")]
public bool OverwriteExisting { get => GetNonString(defaultValue: false); set => SetNonString(value); }
diff --git a/Source/LibationFileManager/Configuration.PropertyChange.cs b/Source/LibationFileManager/Configuration.PropertyChange.cs
index f6d70073..e1fbb08c 100644
--- a/Source/LibationFileManager/Configuration.PropertyChange.cs
+++ b/Source/LibationFileManager/Configuration.PropertyChange.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
+#nullable enable
namespace LibationFileManager
{
public partial class Configuration
@@ -9,17 +10,17 @@ namespace LibationFileManager
* and be sure to clone it before returning. This allows Configuration to
* accurately detect if any of the Dictionary's elements have changed.
*/
- private class EquatableDictionary : Dictionary
+ private class EquatableDictionary : Dictionary where TKey : notnull
{
public EquatableDictionary() { }
public EquatableDictionary(IEnumerable> keyValuePairs) : base(keyValuePairs) { }
public EquatableDictionary Clone() => new(this);
- public override bool Equals(object obj)
+ public override bool Equals(object? obj)
{
if (obj is Dictionary dic && Count == dic.Count)
{
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 true;
diff --git a/Source/LibationFileManager/Configuration.cs b/Source/LibationFileManager/Configuration.cs
index 74d95c86..4867ce27 100644
--- a/Source/LibationFileManager/Configuration.cs
+++ b/Source/LibationFileManager/Configuration.cs
@@ -4,7 +4,7 @@ using System.Linq;
using Dinah.Core;
using FileManager;
-
+#nullable enable
namespace LibationFileManager
{
public partial class Configuration : PropertyChangeFilter
@@ -24,9 +24,12 @@ namespace LibationFileManager
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.
//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
{
Directory.CreateDirectory(booksDir);
diff --git a/Source/LibationFileManager/FilePathCache.cs b/Source/LibationFileManager/FilePathCache.cs
index 9cf34adb..21aee8f6 100644
--- a/Source/LibationFileManager/FilePathCache.cs
+++ b/Source/LibationFileManager/FilePathCache.cs
@@ -6,6 +6,7 @@ using Dinah.Core.Collections.Immutable;
using FileManager;
using Newtonsoft.Json;
+#nullable enable
namespace LibationFileManager
{
public static class FilePathCache
@@ -14,8 +15,8 @@ namespace LibationFileManager
private const string FILENAME = "FileLocations.json";
- public static event EventHandler Inserted;
- public static event EventHandler Removed;
+ public static event EventHandler? Inserted;
+ public static event EventHandler? Removed;
private static Cache cache { get; } = new Cache();
@@ -51,7 +52,7 @@ namespace LibationFileManager
.Select(entry => (entry.FileType, entry.Path))
.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)
?.FirstOrDefault()
?.Path;
diff --git a/Source/LibationFileManager/IInteropFunctions.cs b/Source/LibationFileManager/IInteropFunctions.cs
index a79cc0f3..cc1bcbeb 100644
--- a/Source/LibationFileManager/IInteropFunctions.cs
+++ b/Source/LibationFileManager/IInteropFunctions.cs
@@ -2,9 +2,9 @@
using System.Diagnostics;
using System.Threading.Tasks;
+#nullable enable
namespace LibationFileManager
{
-#nullable enable
public interface IInteropFunctions
{
///
diff --git a/Source/LibationFileManager/InteropFactory.cs b/Source/LibationFileManager/InteropFactory.cs
index eddc97be..03762099 100644
--- a/Source/LibationFileManager/InteropFactory.cs
+++ b/Source/LibationFileManager/InteropFactory.cs
@@ -5,11 +5,12 @@ using System.Linq;
using System.Reflection;
using Dinah.Core;
+#nullable enable
namespace LibationFileManager
{
public static class InteropFactory
{
- public static Type InteropFunctionsType { get; }
+ public static Type? InteropFunctionsType { get; }
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(params object[] values) => _create(values);
- private static IInteropFunctions instance { get; set; }
+ private static IInteropFunctions? instance { get; set; }
private static IInteropFunctions _create(params object[] values)
{
instance ??=
InteropFunctionsType is null
? new NullInteropFunctions()
: Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions;
+
+ if (instance is null)
+ throw new TypeLoadException();
+
return instance;
}
@@ -66,7 +71,7 @@ namespace LibationFileManager
.GetTypes()
.FirstOrDefault(type.IsAssignableFrom);
}
- private static string getOSConfigApp()
+ private static string? getOSConfigApp()
{
// find '*ConfigApp.dll' files
var appName =
@@ -76,8 +81,8 @@ namespace LibationFileManager
return appName;
}
- private static Dictionary lowEffortCache { get; } = new();
- private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
+ private static Dictionary lowEffortCache { get; } = new();
+ private static Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args)
{
var asmName = new AssemblyName(args.Name);
var here = Configuration.ProcessDirectory;
@@ -97,7 +102,7 @@ namespace LibationFileManager
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.
diff --git a/Source/LibationFileManager/LibraryBookDto.cs b/Source/LibationFileManager/LibraryBookDto.cs
index 0703873c..9059d364 100644
--- a/Source/LibationFileManager/LibraryBookDto.cs
+++ b/Source/LibationFileManager/LibraryBookDto.cs
@@ -2,26 +2,27 @@
using System.Collections.Generic;
using System.Linq;
+#nullable enable
namespace LibationFileManager
{
public class BookDto
{
- public string AudibleProductId { get; set; }
- public string Title { get; set; }
- public string Subtitle { get; set; }
- public string TitleWithSubtitle { get; set; }
- public string Locale { get; set; }
+ public string? AudibleProductId { get; set; }
+ public string? Title { get; set; }
+ public string? Subtitle { get; set; }
+ public string? TitleWithSubtitle { get; set; }
+ public string? Locale { get; set; }
public int? YearPublished { get; set; }
- public IEnumerable Authors { get; set; }
- public string AuthorNames => string.Join(", ", Authors);
- public string FirstAuthor => Authors.FirstOrDefault();
+ public IEnumerable? Authors { get; set; }
+ public string? AuthorNames => Authors is null ? null : string.Join(", ", Authors);
+ public string? FirstAuthor => Authors?.FirstOrDefault();
- public IEnumerable Narrators { get; set; }
- public string NarratorNames => string.Join(", ", Narrators);
- public string FirstNarrator => Narrators.FirstOrDefault();
+ public IEnumerable? Narrators { get; set; }
+ public string? NarratorNames => Narrators is null? null: string.Join(", ", Narrators);
+ public string? FirstNarrator => Narrators?.FirstOrDefault();
- public string SeriesName { get; set; }
+ public string? SeriesName { get; set; }
public float? SeriesNumber { get; set; }
public bool IsSeries => !string.IsNullOrEmpty(SeriesName);
public bool IsPodcastParent { get; set; }
@@ -32,13 +33,13 @@ namespace LibationFileManager
public int Channels { get; set; }
public DateTime FileDate { get; set; } = DateTime.Now;
public DateTime? DatePublished { get; set; }
- public string Language { get; set; }
+ public string? Language { get; set; }
}
public class LibraryBookDto : BookDto
{
public DateTime? DateAdded { get; set; }
- public string Account { get; set; }
- public string AccountNickname { get; set; }
+ public string? Account { get; set; }
+ public string? AccountNickname { get; set; }
}
}
diff --git a/Source/LibationFileManager/NameListFormat.cs b/Source/LibationFileManager/NameListFormat.cs
index 2ca5f94b..1e906d58 100644
--- a/Source/LibationFileManager/NameListFormat.cs
+++ b/Source/LibationFileManager/NameListFormat.cs
@@ -5,13 +5,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
+#nullable enable
namespace LibationFileManager
{
internal partial class NameListFormat
{
- public static string Formatter(ITemplateTag _, IEnumerable names, string formatString)
+ public static string Formatter(ITemplateTag _, IEnumerable? names, string formatString)
{
- var humanNames = names.Select(n => new HumanName(RemoveSuffix(n), Prefer.FirstOverPrefix));
+ if (names is null) return "";
+
+ var humanNames = names.Select(n => new HumanName(RemoveSuffix(n), Prefer.FirstOverPrefix));
var sortedNames = Sort(humanNames, formatString);
var nameFormatString = Format(formatString, defaultValue: "{T} {F} {M} {L} {S}");
diff --git a/Source/LibationFileManager/NullInteropFunctions.cs b/Source/LibationFileManager/NullInteropFunctions.cs
index b72e354d..19022f94 100644
--- a/Source/LibationFileManager/NullInteropFunctions.cs
+++ b/Source/LibationFileManager/NullInteropFunctions.cs
@@ -2,7 +2,6 @@
using System.Diagnostics;
#nullable enable
-
namespace LibationFileManager
{
public class NullInteropFunctions : IInteropFunctions
diff --git a/Source/LibationFileManager/PictureStorage.cs b/Source/LibationFileManager/PictureStorage.cs
index e3081038..d72c3614 100644
--- a/Source/LibationFileManager/PictureStorage.cs
+++ b/Source/LibationFileManager/PictureStorage.cs
@@ -6,18 +6,25 @@ using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
+#nullable enable
namespace LibationFileManager
{
public enum PictureSize { Native, _80x80 = 80, _300x300 = 300, _500x500 = 500 }
public class PictureCachedEventArgs : EventArgs
{
- public PictureDefinition Definition { get; internal set; }
- public byte[] Picture { get; internal set; }
+ public PictureDefinition Definition { get; }
+ public byte[] Picture { get; }
+
+ internal PictureCachedEventArgs(PictureDefinition definition, byte[] picture)
+ {
+ Definition = definition;
+ Picture = picture;
+ }
}
public struct PictureDefinition : IEquatable
{
- public string PictureId { get; }
- public PictureSize Size { get; }
+ public string PictureId { get; init; }
+ public PictureSize Size { get; init; }
public PictureDefinition(string pictureId, PictureSize pictureSize)
{
@@ -45,7 +52,7 @@ namespace LibationFileManager
.Start();
}
- public static event EventHandler PictureCached;
+ public static event EventHandler? PictureCached;
private static BlockingCollection DownloadQueue { get; } = new BlockingCollection();
private static object cacheLocker { get; } = new object();
@@ -112,7 +119,7 @@ namespace LibationFileManager
lock (cacheLocker)
cache[def] = bytes;
- PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs { Definition = def, Picture = bytes });
+ PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs(def, bytes));
}
}
diff --git a/Source/LibationFileManager/QuickFilters.cs b/Source/LibationFileManager/QuickFilters.cs
index 497f6e58..6b028f2b 100644
--- a/Source/LibationFileManager/QuickFilters.cs
+++ b/Source/LibationFileManager/QuickFilters.cs
@@ -5,30 +5,30 @@ using System.Linq;
using Dinah.Core.Collections.Generic;
using Newtonsoft.Json;
+#nullable enable
namespace LibationFileManager
{
public static class QuickFilters
{
- public static event EventHandler Updated;
+ public static event EventHandler? Updated;
- internal class FilterState
+ public static event EventHandler? UseDefaultChanged;
+
+ internal class FilterState
{
public bool UseDefault { get; set; }
public List Filters { get; set; } = new List();
}
- static FilterState inMemoryState { get; } = new FilterState();
-
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(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(File.ReadAllText(JsonFile)) is FilterState inMemState
+ ? inMemState
+ : new FilterState();
+
public static bool UseDefault
{
get => inMemoryState.UseDefault;
@@ -43,7 +43,7 @@ namespace LibationFileManager
save(false);
}
- UseDefaultChanged?.Invoke(null, null);
+ UseDefaultChanged?.Invoke(null, EventArgs.Empty);
}
}
@@ -121,7 +121,7 @@ namespace LibationFileManager
}
if (invokeUpdatedEvent)
- Updated?.Invoke(null, null);
+ Updated?.Invoke(null, EventArgs.Empty);
}
}
}
diff --git a/Source/LibationFileManager/TemplateEditor[T].cs b/Source/LibationFileManager/TemplateEditor[T].cs
index e3d68dc1..2064876c 100644
--- a/Source/LibationFileManager/TemplateEditor[T].cs
+++ b/Source/LibationFileManager/TemplateEditor[T].cs
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System;
using System.IO;
+#nullable enable
namespace LibationFileManager
{
public interface ITemplateEditor
@@ -14,14 +15,11 @@ namespace LibationFileManager
string DefaultTemplate { get; }
string TemplateName { get; }
string TemplateDescription { get; }
- Templates Folder { get; }
- Templates File { get; }
- Templates Name { get; }
Templates EditingTemplate { get; }
- void SetTemplateText(string templateText);
- string GetFolderName();
- string GetFileName();
- string GetName();
+ bool SetTemplateText(string templateText);
+ string? GetFolderName();
+ string? GetFileName();
+ string? GetName();
}
public class TemplateEditor : ITemplateEditor where T : Templates, ITemplate, new()
@@ -32,9 +30,9 @@ namespace LibationFileManager
public string DefaultTemplate { get; private init; }
public string TemplateName { get; private init; }
public string TemplateDescription { get; private init; }
- public Templates Folder { get; private set; }
- public Templates File { get; private set; }
- public Templates Name { get; private set; }
+ private Templates? Folder { get; set; }
+ private Templates? File { get; set; }
+ private Templates? Name { get; set; }
public Templates EditingTemplate
{
get => _editingTemplate;
@@ -43,10 +41,14 @@ namespace LibationFileManager
private Templates _editingTemplate;
- public void SetTemplateText(string templateText)
+ public bool SetTemplateText(string templateText)
{
- Templates.TryGetTemplate(templateText, out var template);
- EditingTemplate = template;
+ if (Templates.TryGetTemplate(templateText, out var template))
+ {
+ EditingTemplate = template;
+ return true;
+ }
+ return false;
}
private static readonly LibraryBookDto libraryBookDto
@@ -80,7 +82,7 @@ namespace LibationFileManager
Title = "A Flight for Life"
};
- public string GetFolderName()
+ public string? GetFolderName()
{
/*
* 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
* 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);
}
- public string GetFileName()
- => File.GetFilename(libraryBookDto, partFileProperties, "", "");
- public string GetName()
- => Name.GetName(libraryBookDto, partFileProperties);
+ public string? GetFileName()
+ => File?.GetFilename(libraryBookDto, partFileProperties, "", "");
+ public string? GetName()
+ => 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)
{
- Templates.TryGetTemplate(templateText, out var template);
+ if (!Templates.TryGetTemplate(templateText, out var template))
+ throw new ArgumentException($"Failed to parse {nameof(templateText)}");
- var templateEditor = new TemplateEditor
- {
- _editingTemplate = template,
- BaseDirectory = baseDir,
- DefaultTemplate = T.DefaultTemplate,
- TemplateName = T.Name,
- TemplateDescription = T.Description
-
- };
+ var templateEditor = new TemplateEditor(template, baseDir, T.DefaultTemplate, T.Name, T.Description);
if (!templateEditor.IsFolder && !templateEditor.IsFilePath)
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;
}
public static ITemplateEditor CreateNameEditor(string templateText)
{
- Templates.TryGetTemplate(templateText, out var nameTemplate);
+ if (!Templates.TryGetTemplate(templateText, out var nameTemplate))
+ throw new ArgumentException($"Failed to parse {nameof(templateText)}");
- var templateEditor = new TemplateEditor
- {
- _editingTemplate = nameTemplate,
- DefaultTemplate = T.DefaultTemplate,
- TemplateName = T.Name,
- TemplateDescription = T.Description
- };
+ var templateEditor = new TemplateEditor(nameTemplate, "", T.DefaultTemplate, T.Name, T.Description);
if (templateEditor.IsFolder || templateEditor.IsFilePath)
throw new InvalidOperationException($"This method is only for name templates. Use {nameof(CreateFilenameEditor)} for file templates");
diff --git a/Source/LibationFileManager/TemplateTags.cs b/Source/LibationFileManager/TemplateTags.cs
index 725eb98d..c4a74aba 100644
--- a/Source/LibationFileManager/TemplateTags.cs
+++ b/Source/LibationFileManager/TemplateTags.cs
@@ -1,5 +1,6 @@
using FileManager.NamingTemplate;
+#nullable enable
namespace LibationFileManager
{
public sealed class TemplateTags : ITemplateTag
@@ -10,7 +11,7 @@ namespace LibationFileManager
public string Description { 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;
Description = description;
diff --git a/Source/LibationFileManager/Templates.cs b/Source/LibationFileManager/Templates.cs
index 2a24cf22..edcbbedf 100644
--- a/Source/LibationFileManager/Templates.cs
+++ b/Source/LibationFileManager/Templates.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using AaxDecrypter;
@@ -8,6 +9,7 @@ using FileManager;
using FileManager.NamingTemplate;
using NameParser;
+#nullable enable
namespace LibationFileManager
{
public interface ITemplate
@@ -26,10 +28,10 @@ namespace LibationFileManager
//Assigning the properties in the static constructor will require all
//Templates users to have a valid configuration file. To allow tests
//to work without access to Configuration, only load templates on demand.
- private static FolderTemplate _folder;
- private static FileTemplate _file;
- private static ChapterFileTemplate _chapterFile;
- private static ChapterTitleTemplate _chapterTitle;
+ private static FolderTemplate? _folder;
+ private static FileTemplate? _file;
+ private static ChapterFileTemplate? _chapterFile;
+ private static ChapterTitleTemplate? _chapterTitle;
public static FolderTemplate Folder => _folder ??= GetTemplate(Configuration.Instance.FolderTemplate);
public static FileTemplate File => _file ??= GetTemplate(Configuration.Instance.FileTemplate);
@@ -38,10 +40,10 @@ namespace LibationFileManager
#region Template Parsing
- public static T GetTemplate(string templateText) where T : Templates, ITemplate, new()
- => TryGetTemplate(templateText, out var template) ? template : GetDefaultTemplate();
+ public static T GetTemplate(string? templateText) where T : Templates, ITemplate, new()
+ => TryGetTemplate(templateText ?? "", out var template) ? template : GetDefaultTemplate();
- public static bool TryGetTemplate(string templateText, out T template) where T : Templates, ITemplate, new()
+ public static bool TryGetTemplate(string templateText, [NotNullWhen(true)] out T? template) where T : Templates, ITemplate, new()
{
var namingTemplate = NamingTemplate.Parse(templateText, T.TagCollections);
@@ -56,19 +58,19 @@ namespace LibationFileManager
{
Configuration.Instance.PropertyChanged +=
[PropertyChangeFilter(nameof(Configuration.FolderTemplate))]
- (_,e) => _folder = GetTemplate((string)e.NewValue);
+ (_,e) => _folder = GetTemplate(e.NewValue as string);
Configuration.Instance.PropertyChanged +=
[PropertyChangeFilter(nameof(Configuration.FileTemplate))]
- (_, e) => _file = GetTemplate((string)e.NewValue);
+ (_, e) => _file = GetTemplate(e.NewValue as string);
Configuration.Instance.PropertyChanged +=
[PropertyChangeFilter(nameof(Configuration.ChapterFileTemplate))]
- (_, e) => _chapterFile = GetTemplate((string)e.NewValue);
+ (_, e) => _chapterFile = GetTemplate(e.NewValue as string);
Configuration.Instance.PropertyChanged +=
[PropertyChangeFilter(nameof(Configuration.ChapterTitleTemplate))]
- (_, e) => _chapterTitle = GetTemplate((string)e.NewValue);
+ (_, e) => _chapterTitle = GetTemplate(e.NewValue as string);
HumanName.Suffixes.Add("ret");
HumanName.Titles.Add("professor");
@@ -78,10 +80,18 @@ namespace LibationFileManager
#region Template Properties
- public IEnumerable TagsRegistered => NamingTemplate.TagsRegistered.Cast();
- public IEnumerable TagsInUse => NamingTemplate.TagsInUse.Cast();
- public string TemplateText => NamingTemplate.TemplateText;
- protected NamingTemplate NamingTemplate { get; private set; }
+ public IEnumerable TagsRegistered
+ => NamingTemplate?.TagsRegistered.Cast() ?? Enumerable.Empty();
+ public IEnumerable TagsInUse
+ => NamingTemplate?.TagsInUse.Cast() ?? Enumerable.Empty();
+ public string TemplateText => NamingTemplate?.TemplateText ?? "";
+
+ private readonly NamingTemplate? _namingTemplate;
+ protected NamingTemplate NamingTemplate
+ {
+ get => _namingTemplate ?? throw new NullReferenceException(nameof(_namingTemplate));
+ private init => _namingTemplate = value;
+ }
#endregion
@@ -104,7 +114,7 @@ namespace LibationFileManager
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(baseDir, nameof(baseDir));
@@ -114,7 +124,7 @@ namespace LibationFileManager
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(multiChapProps, nameof(multiChapProps));
@@ -246,7 +256,7 @@ namespace LibationFileManager
new(caseSensative: true, StringFormatter, DateTimeFormatter, IntegerFormatter, FloatFormatter)
{
//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.TitleShort, lb => getTitleShort(lb.Title) },
{ TemplateTags.AudibleTitle, lb => lb.Title },
@@ -308,13 +318,13 @@ namespace LibationFileManager
#region Tag Formatters
- private static string getTitleShort(string title)
+ private static string? getTitleShort(string? 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)
- return null;
+ return "";
language = language.Trim();
if (language.Length <= 3)
@@ -324,8 +334,9 @@ namespace LibationFileManager
private static string StringFormatter(ITemplateTag templateTag, string value, string formatString)
{
- if (string.Compare(formatString, "u", ignoreCase: true) == 0) return value?.ToUpper();
- else if (string.Compare(formatString, "l", ignoreCase: true) == 0) return value?.ToLower();
+ if (value is null) return "";
+ 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;
}
@@ -358,7 +369,7 @@ namespace LibationFileManager
public class FolderTemplate : Templates, ITemplate
{
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; } = " []";
public static IEnumerable TagCollections
=> new TagCollection[] { filePropertyTags, conditionalTags, folderConditionalTags };
@@ -378,7 +389,7 @@ namespace LibationFileManager
public class FileTemplate : Templates, ITemplate
{
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; } = " []";
public static IEnumerable TagCollections { get; } = new TagCollection[] { filePropertyTags, conditionalTags };
}
@@ -386,7 +397,7 @@ namespace LibationFileManager
public class ChapterFileTemplate : Templates, ITemplate
{
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; } = " [] - - ";
public static IEnumerable TagCollections { get; } = chapterPropertyTags.Append(filePropertyTags).Append(conditionalTags);
@@ -399,7 +410,7 @@ namespace LibationFileManager
public class ChapterTitleTemplate : Templates, ITemplate
{
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 => " - : ";
public static IEnumerable TagCollections { get; } = chapterPropertyTags.Append(conditionalTags);
diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs
index f2c51a5e..92bd78c3 100644
--- a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs
+++ b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs
@@ -360,7 +360,7 @@
//
booksSelectControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
booksSelectControl.AutoSize = true;
- booksSelectControl.Location = new System.Drawing.Point(7, 23);
+ booksSelectControl.Location = new System.Drawing.Point(8, 38);
booksSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
booksSelectControl.Name = "booksSelectControl";
booksSelectControl.Size = new System.Drawing.Size(830, 102);
@@ -419,7 +419,7 @@
groupBox1.Controls.Add(gridScaleFactorTbar);
groupBox1.Controls.Add(gridFontScaleFactorLbl);
groupBox1.Controls.Add(gridFontScaleFactorTbar);
- groupBox1.Location = new System.Drawing.Point(6, 261);
+ groupBox1.Location = new System.Drawing.Point(6, 277);
groupBox1.Name = "groupBox1";
groupBox1.Size = new System.Drawing.Size(844, 83);
groupBox1.TabIndex = 9;
@@ -491,7 +491,7 @@
booksGb.Controls.Add(booksLocationDescLbl);
booksGb.Location = new System.Drawing.Point(6, 6);
booksGb.Name = "booksGb";
- booksGb.Size = new System.Drawing.Size(844, 249);
+ booksGb.Size = new System.Drawing.Size(844, 265);
booksGb.TabIndex = 0;
booksGb.TabStop = false;
booksGb.Text = "Books location";
@@ -500,7 +500,7 @@
//
lastWriteTimeCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
lastWriteTimeCb.FormattingEnabled = true;
- lastWriteTimeCb.Location = new System.Drawing.Point(211, 214);
+ lastWriteTimeCb.Location = new System.Drawing.Point(212, 229);
lastWriteTimeCb.Name = "lastWriteTimeCb";
lastWriteTimeCb.Size = new System.Drawing.Size(272, 23);
lastWriteTimeCb.TabIndex = 5;
@@ -509,7 +509,7 @@
//
creationTimeCb.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
creationTimeCb.FormattingEnabled = true;
- creationTimeCb.Location = new System.Drawing.Point(211, 185);
+ creationTimeCb.Location = new System.Drawing.Point(212, 200);
creationTimeCb.Name = "creationTimeCb";
creationTimeCb.Size = new System.Drawing.Size(272, 23);
creationTimeCb.TabIndex = 5;
@@ -517,7 +517,7 @@
// lastWriteTimeLbl
//
lastWriteTimeLbl.AutoSize = true;
- lastWriteTimeLbl.Location = new System.Drawing.Point(7, 217);
+ lastWriteTimeLbl.Location = new System.Drawing.Point(8, 232);
lastWriteTimeLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
lastWriteTimeLbl.Name = "lastWriteTimeLbl";
lastWriteTimeLbl.Size = new System.Drawing.Size(116, 15);
@@ -527,7 +527,7 @@
// creationTimeLbl
//
creationTimeLbl.AutoSize = true;
- creationTimeLbl.Location = new System.Drawing.Point(7, 188);
+ creationTimeLbl.Location = new System.Drawing.Point(8, 203);
creationTimeLbl.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
creationTimeLbl.Name = "creationTimeLbl";
creationTimeLbl.Size = new System.Drawing.Size(112, 15);
@@ -537,7 +537,7 @@
// overwriteExistingCbox
//
overwriteExistingCbox.AutoSize = true;
- overwriteExistingCbox.Location = new System.Drawing.Point(7, 156);
+ overwriteExistingCbox.Location = new System.Drawing.Point(8, 171);
overwriteExistingCbox.Name = "overwriteExistingCbox";
overwriteExistingCbox.Size = new System.Drawing.Size(129, 19);
overwriteExistingCbox.TabIndex = 3;
@@ -547,7 +547,7 @@
// saveEpisodesToSeriesFolderCbox
//
saveEpisodesToSeriesFolderCbox.AutoSize = true;
- saveEpisodesToSeriesFolderCbox.Location = new System.Drawing.Point(7, 131);
+ saveEpisodesToSeriesFolderCbox.Location = new System.Drawing.Point(8, 146);
saveEpisodesToSeriesFolderCbox.Name = "saveEpisodesToSeriesFolderCbox";
saveEpisodesToSeriesFolderCbox.Size = new System.Drawing.Size(191, 19);
saveEpisodesToSeriesFolderCbox.TabIndex = 3;
diff --git a/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj b/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj
index 7ba33656..146a4b64 100644
--- a/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj
+++ b/Source/LoadByOS/WindowsConfigApp/WindowsConfigApp.csproj
@@ -26,7 +26,7 @@
-
+