Compare commits

..

15 Commits

Author SHA1 Message Date
Robert McRackan
768afd8ecd incr ver 2023-08-04 11:57:15 -04:00
rmcrackan
32c3fa85ce Merge pull request #699 from Mbucari/master
Fix broken template editor (#698)
2023-08-04 11:55:30 -04:00
Mbucari
6986c8f018 Fix broken template editor (#698) 2023-08-04 09:26:51 -06:00
Robert McRackan
f69c2b1cfc incr ver 2023-08-02 21:55:19 -04:00
rmcrackan
b11675c36a Merge pull request #696 from Mbucari/master
Bug fixes
2023-08-02 21:52:57 -04:00
Mbucari
379c2ed62d Fix account nickname retrieval (#629) 2023-08-02 13:54:49 -06:00
Mbucari
7c8489b52f Fix walkthrough causing freeze (#695) 2023-08-02 13:15:58 -06:00
rmcrackan
c61a863edd Merge pull request #694 from Mbucari/master
Fix DPI scaling bug (#692)
2023-08-01 15:03:21 -04:00
Mbucari
1d54f32ef3 Fix DPI scaling bug (#692) 2023-08-01 11:55:23 -06:00
rmcrackan
fabe4afd94 Merge pull request #691 from Mbucari/master
Fix #686 and enable nullable in FileManager and LibationFileManager
2023-07-30 20:22:55 -04:00
MBucari
61efa3c0c1 Update dependencies 2023-07-30 14:00:12 -06:00
MBucari
fe70daf0bc Update avalonia to v11.0.1 2023-07-30 13:54:44 -06:00
MBucari
34033e7947 Enable Nullable 2023-07-30 13:31:57 -06:00
MBucari
e8c63e9a6e Fix UI control overlapping label (#686) 2023-07-30 13:15:43 -06:00
Robert McRackan
9315165f80 update dependencies 2023-07-21 21:21:48 -04:00
48 changed files with 490 additions and 360 deletions

View File

@@ -2,10 +2,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>10.6.3.1</Version>
<Version>10.6.5.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="6.0.0" />
<PackageReference Include="Octokit" Version="7.1.0" />
<PackageReference Include="Serilog.Sinks.ZipFile" Version="1.0.1" />
</ItemGroup>
<ItemGroup>

View File

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

View File

@@ -10,14 +10,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="7.2.3.1" />
<PackageReference Include="Dinah.EntityFrameworkCore" Version="7.1.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
<PackageReference Include="Dinah.Core" Version="7.3.0.1" />
<PackageReference Include="Dinah.EntityFrameworkCore" Version="7.3.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.9">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -30,7 +30,7 @@ namespace FileLiberator
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var nickname
= persister.AccountsSettings.Accounts
.FirstOrDefault(a => a.AccountId == libraryBook.Account)
.FirstOrDefault(a => a.AccountId == libraryBook.Account && a.Locale.Name == libraryBook.Book.Locale)
?.AccountName;
return new()

View File

@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
#nullable enable
namespace FileManager
{
/// <summary>
@@ -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<FileSystemEventArgs> directoryChangesEvents { get; set; }
private Task backgroundScanner { get; set; }
private FileSystemWatcher? fileSystemWatcher { get; set; }
private BlockingCollection<FileSystemEventArgs>? directoryChangesEvents { get; set; }
private Task? backgroundScanner { get; set; }
private object fsCacheLocker { get; } = new();
private List<LongPath> 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);

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="7.2.3.1" />
<PackageReference Include="Dinah.Core" Version="7.3.0.1" />
<PackageReference Include="Polly" Version="7.2.4" />
</ItemGroup>

View File

@@ -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
{
/// <summary>
/// "txt" => ".txt"
/// <br />".txt" => ".txt"
/// <br />null or whitespace => ""
/// </summary>
public static string GetStandardizedExtension(string extension)
[return: NotNull]
public static string GetStandardizedExtension(string? extension)
=> string.IsNullOrWhiteSpace(extension)
? (extension ?? "")?.Trim()
? string.Empty
: '.' + extension.Trim().Trim('.');
/// <summary>
@@ -48,18 +49,18 @@ namespace FileManager
/// <br/>- ensure uniqueness
/// <br/>- enforce max file length
/// </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(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);

View File

@@ -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<byte> contents, string comment = null)
public Task AddFileAsync(string name, ReadOnlyMemory<byte> 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<byte> contents, string comment)
private void AddFileInternal(string name, ReadOnlySpan<byte> 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();
}
}

View File

@@ -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)]

View File

@@ -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
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
/// <param name="propertyTag">The registered <see cref="IPropertyTag"/></param>
/// <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
@@ -37,6 +39,7 @@ public class ConditionalTagCollection<TClass> : 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<TClass> : 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)

View File

@@ -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<ITemplateTag> TagsInUse => _tagsInUse;
public IEnumerable<ITemplateTag> TagsRegistered => TagCollections.SelectMany(t => t).DistinctBy(t => t.TagName);
public IEnumerable<string> Warnings => errors.Concat(warnings);
public IEnumerable<string> Errors => errors;
private Delegate templateToString;
private Delegate? templateToString;
private readonly List<string> warnings = new();
private readonly List<string> errors = new();
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>
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;
}
/// <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>
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
}
/// <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)
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<char> 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)

View File

@@ -5,6 +5,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
#nullable enable
namespace FileManager.NamingTemplate;
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
/// 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>
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
=> 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
/// 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>
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);
/// <summary>
@@ -72,14 +73,15 @@ public class PropertyTagCollection<TClass> : TagCollection
=> RegisterWithToString(templateTag, propertyGetter, toString);
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(propertyGetter, nameof(propertyGetter));
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));
else
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 PropertyFormatter<T> GetDefaultFormatter<T>()
private PropertyFormatter<T>? GetDefaultFormatter<T>()
{
try
{
@@ -109,6 +111,7 @@ public class PropertyTagCollection<TClass> : TagCollection
private class PropertyTag<TPropertyValue> : TagBase
{
public override Regex NameMatcher { get; }
private Func<Expression, string, Expression> CreateToStringExpression { get; }
public PropertyTag(ITemplateTag templateTag, RegexOptions options, Expression propertyGetter, PropertyFormatter<TPropertyValue> formatter)

View File

@@ -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
/// <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>
/// <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
{
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
/// <param name="formatter">The optional format string in the match inside the square brackets</param>
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)

View File

@@ -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;
/// <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="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>
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<ITemplateTag>
/// <param name="exactName">The <paramref name="templateString"/> substring that was matched.</param>
/// <param name="closingPropertyTag">The registered <see cref="IClosingPropertyTag"/></param>
/// <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>())
{

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
#nullable enable
namespace FileManager.NamingTemplate;
/// <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
/// a registered property, otherwise <see cref="null"/> for string literals. </summary>
public ITemplateTag TemplateTag { get; }
public ITemplateTag? TemplateTag { get; }
/// <summary>The evaluated string.</summary>
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<TemplatePart>
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<TemplatePart> GetEnumerator()
{

View File

@@ -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<string, string> stringCache { get; } = new Dictionary<string, string>();
private Dictionary<string, object> objectCache { get; } = new Dictionary<string, object>();
private Dictionary<string, string?> stringCache { get; } = new();
private Dictionary<string, object?> 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<string>();
stringCache[propertyName] = jObject[propertyName]?.Value<string>();
else
stringCache[propertyName] = defaultValue;
}
@@ -47,7 +50,8 @@ namespace FileManager
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);
@@ -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<object>();
objectCache[propertyName] = jObject[propertyName]?.Value<object>();
}
return objectCache[propertyName];
}
public string GetStringFromJsonPath(string jsonPath, string propertyName) => GetStringFromJsonPath($"{jsonPath}.{propertyName}");
public string GetStringFromJsonPath(string jsonPath)
public string? GetStringFromJsonPath(string jsonPath, string propertyName) => GetStringFromJsonPath($"{jsonPath}.{propertyName}");
public string? GetStringFromJsonPath(string jsonPath)
{
if (!stringCache.ContainsKey(jsonPath))
{
@@ -96,7 +100,7 @@ namespace FileManager
var token = jObject.SelectToken(jsonPath);
if (token is null)
return null;
stringCache[jsonPath] = (string)token;
stringCache[jsonPath] = token.Value<string>();
}
catch
{
@@ -110,7 +114,7 @@ namespace FileManager
public bool Exists(string propertyName) => readFile().ContainsKey(propertyName);
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
if (stringCache.ContainsKey(propertyName) && stringCache[propertyName] == newValue)
@@ -122,7 +126,7 @@ namespace FileManager
writeFile(propertyName, newValue);
}
public void SetNonString(string propertyName, object newValue)
public void SetNonString(string propertyName, object? newValue)
{
// set cache
objectCache[propertyName] = newValue;
@@ -160,7 +164,7 @@ namespace FileManager
return success;
}
private void writeFile(string propertyName, JToken newValue)
private void writeFile(string propertyName, JToken? newValue)
{
if (IsReadOnly)
return;
@@ -190,7 +194,7 @@ namespace FileManager
/// <summary>WILL ONLY set if already present. WILL NOT create new</summary>
/// <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)
return false;
@@ -242,7 +246,7 @@ namespace FileManager
return true;
}
private static string formatValueForLog(string value)
private static string formatValueForLog(string? value)
=> value is null ? "[null]"
: string.IsNullOrEmpty(value) ? "[empty]"
: string.IsNullOrWhiteSpace(value) ? $"[whitespace. Length={value.Length}]"
@@ -283,7 +287,6 @@ namespace FileManager
private void createNewFile()
{
File.WriteAllText(Filepath, "{}");
System.Threading.Thread.Sleep(100);
}
}
}

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
#nullable enable
namespace FileManager
{
public record Replacement
@@ -59,7 +60,7 @@ namespace FileManager
[JsonConverter(typeof(ReplacementCharactersConverter))]
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)
{
@@ -173,7 +174,7 @@ namespace FileManager
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
}).ToArray();
public IReadOnlyList<Replacement> Replacements { get; init; }
required public IReadOnlyList<Replacement> Replacements { get; init; }
private string DefaultReplacement => Replacements[0].ReplacementString;
private Replacement ForwardSlash => Replacements[1];
private Replacement BackSlash => Replacements[2];
@@ -298,12 +299,14 @@ namespace FileManager
public override bool CanConvert(Type objectType)
=> 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 replaceArr = jObj[nameof(Replacement)];
IReadOnlyList<Replacement> dict = replaceArr
.ToObject<Replacement[]>().ToList();
var dict
= 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.
//If not, reset to default.
@@ -325,9 +328,10 @@ namespace FileManager
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
.Select(JObject.FromObject).ToList();

View File

@@ -67,13 +67,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0" />
<PackageReference Include="Avalonia" Version="11.0.2" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.2" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />

View File

@@ -70,13 +70,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia" Version="11.0.0" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.2" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia" Version="11.0.2" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.2" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.2" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.2" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -3,7 +3,6 @@ using AudibleUtilities;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Styling;
using Dinah.Core.StepRunner;
@@ -13,7 +12,6 @@ using LibationFileManager;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using static Avalonia.Threading.Dispatcher;
namespace LibationAvalonia
{
@@ -36,12 +34,13 @@ namespace LibationAvalonia
AutoScan = Configuration.Instance.AutoScan;
Configuration.Instance.AutoScan = false;
MainForm = mainForm;
sequence[nameof(ShowAccountDialog)] = () => UIThread.InvokeAsync(ShowAccountDialog);
sequence[nameof(ShowSettingsDialog)] = () => UIThread.InvokeAsync(ShowSettingsDialog);
sequence[nameof(ShowAccountScanning)] = () => UIThread.InvokeAsync(ShowAccountScanning);
sequence[nameof(ShowSearching)] = () => UIThread.InvokeAsync(ShowSearching);
sequence[nameof(ShowQuickFilters)] = () => UIThread.InvokeAsync(ShowQuickFilters);
sequence[nameof(ShowTourComplete)] = () => UIThread.InvokeAsync(ShowTourComplete);
var uiDispatcher = Avalonia.Threading.Dispatcher.UIThread;
sequence[nameof(ShowAccountDialog)] = () => uiDispatcher.InvokeAsync(ShowAccountDialog);
sequence[nameof(ShowSettingsDialog)] = () => uiDispatcher.InvokeAsync(ShowSettingsDialog);
sequence[nameof(ShowAccountScanning)] = () => uiDispatcher.InvokeAsync(ShowAccountScanning);
sequence[nameof(ShowSearching)] = () => uiDispatcher.InvokeAsync(ShowSearching);
sequence[nameof(ShowQuickFilters)] = () => uiDispatcher.InvokeAsync(ShowQuickFilters);
sequence[nameof(ShowTourComplete)] = () => uiDispatcher.InvokeAsync(ShowTourComplete);
}
public async Task RunAsync()
@@ -60,7 +59,7 @@ namespace LibationAvalonia
await displayControlAsync(MainForm.accountsToolStripMenuItem);
var accountSettings = new AccountsDialog();
accountSettings.Loaded += async (_, _) => await MessageBox.Show(accountSettings, "Add your Audible account(s), then save.", "Add an Account");
accountSettings.Opened += async (_, _) => await MessageBox.Show(accountSettings, "Add your Audible account(s), then save.", "Add an Account");
await accountSettings.ShowDialog(MainForm);
return true;
}
@@ -74,14 +73,14 @@ namespace LibationAvalonia
await displayControlAsync(MainForm.settingsToolStripMenuItem);
await displayControlAsync(MainForm.basicSettingsToolStripMenuItem);
var settingsDialog = await UIThread.InvokeAsync(() => new SettingsDialog());
var settingsDialog = new SettingsDialog();
var tabsToVisit = settingsDialog.tabControl.Items.OfType<TabItem>().ToList();
foreach (var tab in tabsToVisit)
tab.PropertyChanged += TabControl_PropertyChanged;
settingsDialog.Loaded += SettingsDialog_Loaded;
settingsDialog.Opened += SettingsDialog_Opened;
settingsDialog.Closing += SettingsDialog_FormClosing;
settingsDialog.saveBtn.Content = "Next Tab";
@@ -103,7 +102,7 @@ namespace LibationAvalonia
settingTabMessages.Remove(header.Text);
}
async void SettingsDialog_Loaded(object sender, RoutedEventArgs e)
async void SettingsDialog_Opened(object sender, System.EventArgs e)
{
await ShowTabPageMessageBoxAsync(tabsToVisit[0]);
}
@@ -227,7 +226,7 @@ namespace LibationAvalonia
await displayControlAsync(editQuickFiltersToolStripMenuItem);
var editQuickFilters = new EditQuickFilters();
editQuickFilters.Loaded += async (_, _) => await MessageBox.Show(editQuickFilters, "From here you can edit, delete, and change the order of Quick Filters", "Editing Quick Filters");
editQuickFilters.Opened += async (_, _) => await MessageBox.Show(editQuickFilters, "From here you can edit, delete, and change the order of Quick Filters", "Editing Quick Filters");
await editQuickFilters.ShowDialog(MainForm);
return true;
@@ -247,12 +246,12 @@ namespace LibationAvalonia
private async Task displayControlAsync(TemplatedControl control)
{
await UIThread.InvokeAsync(() => control.IsEnabled = false);
await UIThread.InvokeAsync(() => MainForm.productsDisplay.Focus());
await UIThread.InvokeAsync(() => flashControlAsync(control));
if (control is MenuItem menuItem) await UIThread.InvokeAsync(menuItem.Open);
control.IsEnabled = false;
MainForm.productsDisplay.Focus();
await flashControlAsync(control);
if (control is MenuItem menuItem) menuItem.Open();
await Task.Delay(500);
await UIThread.InvokeAsync(() => control.IsEnabled = true);
control.IsEnabled = true;
}
private static async Task flashControlAsync(TemplatedControl control, int flashCount = 3)

View File

@@ -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<LongPath> 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<LongPath> 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<LongPath> GetFilePathsCustom(string productId)
private static BackgroundFileSystem newBookDirectoryFiles()
=> new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
protected override List<LongPath> 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<FilePathCache.CacheEntry> 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
{

View File

@@ -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; }

View File

@@ -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;
}
}

View File

@@ -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<string> getPathFunc)> directoryOptionsPaths { get; } = new()
private static List<(KnownDirectories directory, Func<string?> 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();

View File

@@ -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; }
/// <summary>
/// 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<string>();
if (jObjFinal[LIBATION_FILES_KEY]?.Value<string>() is not string valueFinal)
throw new InvalidDataException($"{LIBATION_FILES_KEY} not found in {AppsettingsJsonFile}");
return valueFinal;
}

View File

@@ -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<LogEventLevel>(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);

View File

@@ -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>(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>(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
}
/// <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)
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); }

View File

@@ -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<TKey, TValue> : Dictionary<TKey, TValue>
private class EquatableDictionary<TKey, TValue> : Dictionary<TKey, TValue> where TKey : notnull
{
public EquatableDictionary() { }
public EquatableDictionary(IEnumerable<KeyValuePair<TKey, TValue>> keyValuePairs) : base(keyValuePairs) { }
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)
{
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;

View File

@@ -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);

View File

@@ -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<CacheEntry> Inserted;
public static event EventHandler<CacheEntry> Removed;
public static event EventHandler<CacheEntry>? Inserted;
public static event EventHandler<CacheEntry>? Removed;
private static Cache<CacheEntry> cache { get; } = new Cache<CacheEntry>();
@@ -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;

View File

@@ -2,9 +2,9 @@
using System.Diagnostics;
using System.Threading.Tasks;
#nullable enable
namespace LibationFileManager
{
#nullable enable
public interface IInteropFunctions
{
/// <summary>

View File

@@ -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<string, Assembly> lowEffortCache { get; } = new();
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
private static Dictionary<string, Assembly?> 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.

View File

@@ -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<string> Authors { get; set; }
public string AuthorNames => string.Join(", ", Authors);
public string FirstAuthor => Authors.FirstOrDefault();
public IEnumerable<string>? Authors { get; set; }
public string? AuthorNames => Authors is null ? null : string.Join(", ", Authors);
public string? FirstAuthor => Authors?.FirstOrDefault();
public IEnumerable<string> Narrators { get; set; }
public string NarratorNames => string.Join(", ", Narrators);
public string FirstNarrator => Narrators.FirstOrDefault();
public IEnumerable<string>? 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; }
}
}

View File

@@ -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<string> names, string formatString)
public static string Formatter(ITemplateTag _, IEnumerable<string>? 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}");

View File

@@ -2,7 +2,6 @@
using System.Diagnostics;
#nullable enable
namespace LibationFileManager
{
public class NullInteropFunctions : IInteropFunctions

View File

@@ -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<PictureDefinition>
{
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<PictureCachedEventArgs> PictureCached;
public static event EventHandler<PictureCachedEventArgs>? PictureCached;
private static BlockingCollection<PictureDefinition> DownloadQueue { get; } = new BlockingCollection<PictureDefinition>();
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));
}
}

View File

@@ -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<string> Filters { get; set; } = new List<string>();
}
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<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
{
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);
}
}
}

View File

@@ -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<T> : 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<T>(templateText, out var template);
EditingTemplate = template;
if (Templates.TryGetTemplate<T>(templateText, out var template))
{
EditingTemplate = template;
return true;
}
return false;
}
private static readonly LibraryBookDto libraryBookDto
@@ -58,6 +60,7 @@ namespace LibationFileManager
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
AudibleProductId = "123456789",
Title = "A Study in Scarlet",
TitleWithSubtitle = "A Study in Scarlet: A Sherlock Holmes Novel",
Subtitle = "A Sherlock Holmes Novel",
Locale = "us",
YearPublished = 2017,
@@ -80,7 +83,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 +91,54 @@ 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<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>
{
_editingTemplate = template,
BaseDirectory = baseDir,
DefaultTemplate = T.DefaultTemplate,
TemplateName = T.Name,
TemplateDescription = T.Description
};
var templateEditor = new TemplateEditor<T>(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;
if (templateEditor.IsFolder)
templateEditor.File = Templates.File;
else
templateEditor.Folder = Templates.Folder;
return templateEditor;
}
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>
{
_editingTemplate = nameTemplate,
DefaultTemplate = T.DefaultTemplate,
TemplateName = T.Name,
TemplateDescription = T.Description
};
var templateEditor = new TemplateEditor<T>(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");

View File

@@ -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;

View File

@@ -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<FolderTemplate>(Configuration.Instance.FolderTemplate);
public static FileTemplate File => _file ??= GetTemplate<FileTemplate>(Configuration.Instance.FileTemplate);
@@ -38,10 +40,10 @@ namespace LibationFileManager
#region Template Parsing
public static T GetTemplate<T>(string templateText) where T : Templates, ITemplate, new()
=> TryGetTemplate<T>(templateText, out var template) ? template : GetDefaultTemplate<T>();
public static T GetTemplate<T>(string? templateText) where T : Templates, ITemplate, new()
=> 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);
@@ -56,19 +58,19 @@ namespace LibationFileManager
{
Configuration.Instance.PropertyChanged +=
[PropertyChangeFilter(nameof(Configuration.FolderTemplate))]
(_,e) => _folder = GetTemplate<FolderTemplate>((string)e.NewValue);
(_,e) => _folder = GetTemplate<FolderTemplate>(e.NewValue as string);
Configuration.Instance.PropertyChanged +=
[PropertyChangeFilter(nameof(Configuration.FileTemplate))]
(_, e) => _file = GetTemplate<FileTemplate>((string)e.NewValue);
(_, e) => _file = GetTemplate<FileTemplate>(e.NewValue as string);
Configuration.Instance.PropertyChanged +=
[PropertyChangeFilter(nameof(Configuration.ChapterFileTemplate))]
(_, e) => _chapterFile = GetTemplate<ChapterFileTemplate>((string)e.NewValue);
(_, e) => _chapterFile = GetTemplate<ChapterFileTemplate>(e.NewValue as string);
Configuration.Instance.PropertyChanged +=
[PropertyChangeFilter(nameof(Configuration.ChapterTitleTemplate))]
(_, e) => _chapterTitle = GetTemplate<ChapterTitleTemplate>((string)e.NewValue);
(_, e) => _chapterTitle = GetTemplate<ChapterTitleTemplate>(e.NewValue as string);
HumanName.Suffixes.Add("ret");
HumanName.Titles.Add("professor");
@@ -78,10 +80,18 @@ namespace LibationFileManager
#region Template Properties
public IEnumerable<TemplateTags> TagsRegistered => NamingTemplate.TagsRegistered.Cast<TemplateTags>();
public IEnumerable<TemplateTags> TagsInUse => NamingTemplate.TagsInUse.Cast<TemplateTags>();
public string TemplateText => NamingTemplate.TemplateText;
protected NamingTemplate NamingTemplate { get; private set; }
public IEnumerable<TemplateTags> TagsRegistered
=> NamingTemplate?.TagsRegistered.Cast<TemplateTags>() ?? Enumerable.Empty<TemplateTags>();
public IEnumerable<TemplateTags> TagsInUse
=> 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
@@ -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; } = "<title short> [<id>]";
public static IEnumerable<TagCollection> 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; } = "<title> [<id>]";
public static IEnumerable<TagCollection> 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; } = "<title> [<id>] - <ch# 0> - <ch title>";
public static IEnumerable<TagCollection> 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 => "<ch#> - <title short>: <ch title>";
public static IEnumerable<TagCollection> TagCollections { get; } = chapterPropertyTags.Append(conditionalTags);

View File

@@ -49,7 +49,7 @@ namespace LibationWinForms.Dialogs
// customDirectoryRb
//
customDirectoryRb.AutoSize = true;
customDirectoryRb.Location = new System.Drawing.Point(2, 62);
customDirectoryRb.Location = new System.Drawing.Point(3, 58);
customDirectoryRb.Name = "customDirectoryRb";
customDirectoryRb.Size = new System.Drawing.Size(14, 13);
customDirectoryRb.TabIndex = 2;
@@ -58,18 +58,16 @@ namespace LibationWinForms.Dialogs
//
// customTb
//
customTb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
customTb.Location = new System.Drawing.Point(22, 58);
customTb.Location = new System.Drawing.Point(23, 56);
customTb.Name = "customTb";
customTb.Size = new System.Drawing.Size(588, 23);
customTb.Size = new System.Drawing.Size(606, 23);
customTb.TabIndex = 3;
//
// customBtn
//
customBtn.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
customBtn.Location = new System.Drawing.Point(616, 58);
customBtn.Location = new System.Drawing.Point(635, 55);
customBtn.Name = "customBtn";
customBtn.Size = new System.Drawing.Size(41, 27);
customBtn.Size = new System.Drawing.Size(26, 23);
customBtn.TabIndex = 4;
customBtn.Text = "...";
customBtn.UseVisualStyleBackColor = true;
@@ -77,12 +75,9 @@ namespace LibationWinForms.Dialogs
//
// directorySelectControl
//
directorySelectControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
directorySelectControl.AutoSize = true;
directorySelectControl.Location = new System.Drawing.Point(23, 0);
directorySelectControl.Margin = new System.Windows.Forms.Padding(6, 6, 6, 6);
directorySelectControl.Name = "directorySelectControl";
directorySelectControl.Size = new System.Drawing.Size(635, 55);
directorySelectControl.Size = new System.Drawing.Size(637, 50);
directorySelectControl.TabIndex = 5;
//
// DirectoryOrCustomSelectControl
@@ -95,7 +90,7 @@ namespace LibationWinForms.Dialogs
Controls.Add(customDirectoryRb);
Controls.Add(knownDirectoryRb);
Name = "DirectoryOrCustomSelectControl";
Size = new System.Drawing.Size(660, 88);
Size = new System.Drawing.Size(660, 80);
Load += DirectoryOrCustomSelectControl_Load;
ResumeLayout(false);
PerformLayout();

View File

@@ -37,11 +37,16 @@ namespace LibationWinForms.Dialogs
if (directory != Configuration.KnownDirectories.None)
selectDir(directory, null);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
//For some reason anchors don't work when the parent form scales up, even with AutoScale
directorySelectControl.Width = customTb.Width = Width;
//Workaround for anchoring bug in user controls
//https://github.com/dotnet/winforms/issues/6381
customBtn.Location = new System.Drawing.Point(Width - customBtn.Width, customTb.Location.Y);
customBtn.Height = customTb.Height;
directorySelectControl.Width = Width - directorySelectControl.Location.X;
customTb.Width = Width - customTb.Location.X - customBtn.Width - customTb.Margin.Left;
}
/// <summary>set selection</summary>

View File

@@ -35,7 +35,7 @@ namespace LibationWinForms.Dialogs
//
// directoryComboBox
//
directoryComboBox.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
directoryComboBox.Dock = System.Windows.Forms.DockStyle.Top;
directoryComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
directoryComboBox.FormattingEnabled = true;
directoryComboBox.Location = new System.Drawing.Point(0, 0);
@@ -46,8 +46,8 @@ namespace LibationWinForms.Dialogs
//
// textBox1
//
textBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
textBox1.Location = new System.Drawing.Point(0, 29);
textBox1.Dock = System.Windows.Forms.DockStyle.Bottom;
textBox1.Location = new System.Drawing.Point(0, 26);
textBox1.Name = "textBox1";
textBox1.ReadOnly = true;
textBox1.Size = new System.Drawing.Size(814, 23);
@@ -57,11 +57,10 @@ namespace LibationWinForms.Dialogs
//
AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
AutoSize = true;
Controls.Add(textBox1);
Controls.Add(directoryComboBox);
Name = "DirectorySelectControl";
Size = new System.Drawing.Size(814, 55);
Size = new System.Drawing.Size(814, 49);
Load += DirectorySelectControl_Load;
ResumeLayout(false);
PerformLayout();

View File

@@ -48,7 +48,7 @@
//
cancelBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
cancelBtn.Location = new System.Drawing.Point(832, 118);
cancelBtn.Location = new System.Drawing.Point(832, 119);
cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
cancelBtn.Name = "cancelBtn";
cancelBtn.Size = new System.Drawing.Size(88, 27);
@@ -60,7 +60,7 @@
// saveBtn
//
saveBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
saveBtn.Location = new System.Drawing.Point(714, 118);
saveBtn.Location = new System.Drawing.Point(736, 119);
saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
saveBtn.Name = "saveBtn";
saveBtn.Size = new System.Drawing.Size(88, 27);
@@ -73,9 +73,9 @@
//
libationFilesSelectControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
libationFilesSelectControl.Location = new System.Drawing.Point(14, 28);
libationFilesSelectControl.Margin = new System.Windows.Forms.Padding(6, 6, 6, 6);
libationFilesSelectControl.Margin = new System.Windows.Forms.Padding(6);
libationFilesSelectControl.Name = "libationFilesSelectControl";
libationFilesSelectControl.Size = new System.Drawing.Size(906, 88);
libationFilesSelectControl.Size = new System.Drawing.Size(906, 81);
libationFilesSelectControl.TabIndex = 1;
//
// LibationFilesDialog
@@ -83,7 +83,7 @@
AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
AutoSize = true;
ClientSize = new System.Drawing.Size(933, 164);
ClientSize = new System.Drawing.Size(933, 158);
Controls.Add(libationFilesSelectControl);
Controls.Add(cancelBtn);
Controls.Add(saveBtn);

View File

@@ -339,11 +339,10 @@
// inProgressSelectControl
//
inProgressSelectControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
inProgressSelectControl.AutoSize = true;
inProgressSelectControl.Location = new System.Drawing.Point(7, 68);
inProgressSelectControl.Location = new System.Drawing.Point(6, 85);
inProgressSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
inProgressSelectControl.Name = "inProgressSelectControl";
inProgressSelectControl.Size = new System.Drawing.Size(830, 55);
inProgressSelectControl.Size = new System.Drawing.Size(830, 49);
inProgressSelectControl.TabIndex = 19;
//
// logsBtn
@@ -359,11 +358,10 @@
// booksSelectControl
//
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(6, 37);
booksSelectControl.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
booksSelectControl.Name = "booksSelectControl";
booksSelectControl.Size = new System.Drawing.Size(830, 102);
booksSelectControl.Size = new System.Drawing.Size(832, 102);
booksSelectControl.TabIndex = 2;
//
// loggingLevelLbl
@@ -419,7 +417,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 +489,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 +498,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 +507,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 +515,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 +525,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 +535,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 +545,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;
@@ -617,7 +615,7 @@
// saveMetadataToFileCbox
//
saveMetadataToFileCbox.AutoSize = true;
saveMetadataToFileCbox.Location = new System.Drawing.Point(482, 415);
saveMetadataToFileCbox.Location = new System.Drawing.Point(482, 428);
saveMetadataToFileCbox.Name = "saveMetadataToFileCbox";
saveMetadataToFileCbox.Size = new System.Drawing.Size(165, 19);
saveMetadataToFileCbox.TabIndex = 22;
@@ -627,7 +625,7 @@
// useCoverAsFolderIconCb
//
useCoverAsFolderIconCb.AutoSize = true;
useCoverAsFolderIconCb.Location = new System.Drawing.Point(7, 415);
useCoverAsFolderIconCb.Location = new System.Drawing.Point(7, 428);
useCoverAsFolderIconCb.Name = "useCoverAsFolderIconCb";
useCoverAsFolderIconCb.Size = new System.Drawing.Size(180, 19);
useCoverAsFolderIconCb.TabIndex = 22;
@@ -641,7 +639,7 @@
inProgressFilesGb.Controls.Add(inProgressSelectControl);
inProgressFilesGb.Location = new System.Drawing.Point(6, 281);
inProgressFilesGb.Name = "inProgressFilesGb";
inProgressFilesGb.Size = new System.Drawing.Size(842, 128);
inProgressFilesGb.Size = new System.Drawing.Size(842, 141);
inProgressFilesGb.TabIndex = 21;
inProgressFilesGb.TabStop = false;
inProgressFilesGb.Text = "In progress files";

View File

@@ -37,7 +37,7 @@
<ItemGroup>
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.2.3.1" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="7.3.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -26,7 +26,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1774.30" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.1901.177" />
</ItemGroup>
<ItemGroup>

View File

@@ -19,7 +19,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="7.2.3.1" />
<PackageReference Include="Dinah.Core" Version="7.3.0.1" />
</ItemGroup>
</Project>