Merge pull request #1322 from Mbucari/master
Improve Libation's interaction with the file system & other minor fixes
This commit is contained in:
commit
6d0c4a9b3c
@ -126,8 +126,8 @@ namespace AaxDecrypter
|
|||||||
if (DownloadOptions.SeriesName is string series)
|
if (DownloadOptions.SeriesName is string series)
|
||||||
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "SERIES", series);
|
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "SERIES", series);
|
||||||
|
|
||||||
if (DownloadOptions.SeriesNumber is float part)
|
if (DownloadOptions.SeriesNumber is string part)
|
||||||
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "PART", part.ToString());
|
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "PART", part);
|
||||||
}
|
}
|
||||||
|
|
||||||
OnRetrievedTitle(AaxFile.AppleTags.TitleSansUnabridged);
|
OnRetrievedTitle(AaxFile.AppleTags.TitleSansUnabridged);
|
||||||
|
|||||||
@ -43,7 +43,7 @@ namespace AaxDecrypter
|
|||||||
string? Publisher { get; }
|
string? Publisher { get; }
|
||||||
string? Language { get; }
|
string? Language { get; }
|
||||||
string? SeriesName { get; }
|
string? SeriesName { get; }
|
||||||
float? SeriesNumber { get; }
|
string? SeriesNumber { get; }
|
||||||
NAudio.Lame.LameConfig? LameConfig { get; }
|
NAudio.Lame.LameConfig? LameConfig { get; }
|
||||||
bool Downsample { get; }
|
bool Downsample { get; }
|
||||||
bool MatchSourceBitrate { get; }
|
bool MatchSourceBitrate { get; }
|
||||||
|
|||||||
@ -121,7 +121,7 @@ namespace AppScaffolding
|
|||||||
zipFileSink["Name"] = "File";
|
zipFileSink["Name"] = "File";
|
||||||
fileChanged = true;
|
fileChanged = true;
|
||||||
}
|
}
|
||||||
var hooks = $"{nameof(LibationFileManager)}.{nameof(FileSinkHook)}, {nameof(LibationFileManager)}";
|
var hooks = typeof(FileSinkHook).AssemblyQualifiedName;
|
||||||
if (serilog.SelectToken("$.WriteTo[?(@.Name == 'File')].Args", false) is JObject fileSinkArgs
|
if (serilog.SelectToken("$.WriteTo[?(@.Name == 'File')].Args", false) is JObject fileSinkArgs
|
||||||
&& fileSinkArgs["hooks"]?.Value<string>() != hooks)
|
&& fileSinkArgs["hooks"]?.Value<string>() != hooks)
|
||||||
{
|
{
|
||||||
@ -158,7 +158,8 @@ namespace AppScaffolding
|
|||||||
// - with class and method info: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}";
|
// - with class and method info: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}";
|
||||||
// output example: 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForms.Program.init()) Begin Libation
|
// output example: 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForms.Program.init()) Begin Libation
|
||||||
// {Properties:j} needed for expanded exception logging
|
// {Properties:j} needed for expanded exception logging
|
||||||
{ "outputTemplate", "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception} {Properties:j}" }
|
{ "outputTemplate", "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception} {Properties:j}" },
|
||||||
|
{ "hooks", typeof(FileSinkHook).AssemblyQualifiedName }, // for FileSinkHook
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace DataLayer
|
namespace DataLayer
|
||||||
{
|
{
|
||||||
// only library importing should use tracking. All else should be NoTracking.
|
// only library importing should use tracking. All else should be NoTracking.
|
||||||
@ -24,13 +25,13 @@ namespace DataLayer
|
|||||||
.Where(c => !c.Book.IsEpisodeParent() || includeParents)
|
.Where(c => !c.Book.IsEpisodeParent() || includeParents)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
public static LibraryBook GetLibraryBook_Flat_NoTracking(this LibationContext context, string productId)
|
public static LibraryBook? GetLibraryBook_Flat_NoTracking(this LibationContext context, string productId)
|
||||||
=> context
|
=> context
|
||||||
.LibraryBooks
|
.LibraryBooks
|
||||||
.AsNoTrackingWithIdentityResolution()
|
.AsNoTrackingWithIdentityResolution()
|
||||||
.GetLibraryBook(productId);
|
.GetLibraryBook(productId);
|
||||||
|
|
||||||
public static LibraryBook GetLibraryBook(this IQueryable<LibraryBook> library, string productId)
|
public static LibraryBook? GetLibraryBook(this IQueryable<LibraryBook> library, string productId)
|
||||||
=> library
|
=> library
|
||||||
.GetLibrary()
|
.GetLibrary()
|
||||||
.SingleOrDefault(lb => lb.Book.AudibleProductId == productId);
|
.SingleOrDefault(lb => lb.Book.AudibleProductId == productId);
|
||||||
|
|||||||
@ -26,7 +26,7 @@ namespace FileLiberator
|
|||||||
public string Language => LibraryBook.Book.Language;
|
public string Language => LibraryBook.Book.Language;
|
||||||
public string? AudibleProductId => LibraryBookDto.AudibleProductId;
|
public string? AudibleProductId => LibraryBookDto.AudibleProductId;
|
||||||
public string? SeriesName => LibraryBookDto.FirstSeries?.Name;
|
public string? SeriesName => LibraryBookDto.FirstSeries?.Name;
|
||||||
public float? SeriesNumber => LibraryBookDto.FirstSeries?.Number;
|
public string? SeriesNumber => LibraryBookDto.FirstSeries?.Number;
|
||||||
public NAudio.Lame.LameConfig? LameConfig { get; }
|
public NAudio.Lame.LameConfig? LameConfig { get; }
|
||||||
public string UserAgent => AudibleApi.Resources.Download_User_Agent;
|
public string UserAgent => AudibleApi.Resources.Download_User_Agent;
|
||||||
public bool StripUnabridged => Config.AllowLibationFixup && Config.StripUnabridged;
|
public bool StripUnabridged => Config.AllowLibationFixup && Config.StripUnabridged;
|
||||||
|
|||||||
@ -83,7 +83,7 @@ namespace FileLiberator
|
|||||||
.Select(sb
|
.Select(sb
|
||||||
=> new SeriesDto(
|
=> new SeriesDto(
|
||||||
sb.Series.Name,
|
sb.Series.Name,
|
||||||
sb.Book.IsEpisodeParent() ? null : sb.Index,
|
sb.Book.IsEpisodeParent() ? null : sb.Order,
|
||||||
sb.Series.AudibleSeriesId)
|
sb.Series.AudibleSeriesId)
|
||||||
).ToList();
|
).ToList();
|
||||||
}
|
}
|
||||||
|
|||||||
74
Source/FileManager/FileSystemTest.cs
Normal file
74
Source/FileManager/FileSystemTest.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace FileManager
|
||||||
|
{
|
||||||
|
public static class FileSystemTest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Additional characters which are illegal for filenames in Windows environments.
|
||||||
|
/// Double quotes and slashes are already illegal filename characters on all platforms,
|
||||||
|
/// so they are not included here.
|
||||||
|
/// </summary>
|
||||||
|
public static string AdditionalInvalidWindowsFilenameCharacters { get; } = "<>|:*?";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test if the directory supports filenames with characters that are invalid on Windows (:, *, ?, <, >, |).
|
||||||
|
/// </summary>
|
||||||
|
public static bool CanWriteWindowsInvalidChars(LongPath directoryName)
|
||||||
|
{
|
||||||
|
var testFile = Path.Combine(directoryName, AdditionalInvalidWindowsFilenameCharacters + Guid.NewGuid().ToString());
|
||||||
|
return CanWriteFile(testFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test if the directory supports filenames with 255 unicode characters.
|
||||||
|
/// </summary>
|
||||||
|
public static bool CanWrite255UnicodeChars(LongPath directoryName)
|
||||||
|
{
|
||||||
|
const char unicodeChar = 'ü';
|
||||||
|
var testFileName = new string(unicodeChar, 255);
|
||||||
|
var testFile = Path.Combine(directoryName, testFileName);
|
||||||
|
return CanWriteFile(testFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test if a directory has write access by attempting to create an empty file in it.
|
||||||
|
/// <para/>Returns true even if the temporary file can not be deleted.
|
||||||
|
/// </summary>
|
||||||
|
public static bool CanWriteDirectory(LongPath directoryName)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(directoryName))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Serilog.Log.Logger.Debug("Testing write permissions for directory: {@DirectoryName}", directoryName);
|
||||||
|
var testFilePath = Path.Combine(directoryName, Guid.NewGuid().ToString());
|
||||||
|
return CanWriteFile(testFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanWriteFile(LongPath filename)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Debug("Testing ability to write filename: {@filename}", filename);
|
||||||
|
File.WriteAllBytes(filename, []);
|
||||||
|
Serilog.Log.Logger.Debug("Deleting test file after successful write: {@filename}", filename);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
FileUtility.SaferDelete(filename);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
//An error deleting the file doesn't constitute a write failure.
|
||||||
|
Serilog.Log.Logger.Debug(ex, "Error deleting test file: {@filename}", filename);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Debug(ex, "Error writing test file: {@filename}", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -56,7 +56,7 @@ namespace FileManager
|
|||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(name, nameof(name));
|
ArgumentValidator.EnsureNotNull(name, nameof(name));
|
||||||
|
|
||||||
name = ReplacementCharacters.Barebones.ReplaceFilenameChars(name);
|
name = ReplacementCharacters.Barebones(true).ReplaceFilenameChars(name);
|
||||||
return Task.Run(() => AddFileInternal(name, contents.Span, comment));
|
return Task.Run(() => AddFileInternal(name, contents.Span, comment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -74,12 +74,14 @@ namespace FileManager
|
|||||||
}
|
}
|
||||||
public override int GetHashCode() => Replacements.GetHashCode();
|
public override int GetHashCode() => Replacements.GetHashCode();
|
||||||
|
|
||||||
public static readonly ReplacementCharacters Default
|
public static ReplacementCharacters Default(bool ntfs) => ntfs ? HiFi_NTFS : HiFi_Other;
|
||||||
= IsWindows
|
public static ReplacementCharacters LoFiDefault(bool ntfs) => ntfs ? LoFi_NTFS : LoFi_Other;
|
||||||
? new()
|
public static ReplacementCharacters Barebones(bool ntfs) => ntfs ? BareBones_NTFS : BareBones_Other;
|
||||||
{
|
|
||||||
Replacements = new Replacement[]
|
#region Defaults
|
||||||
{
|
private static readonly ReplacementCharacters HiFi_NTFS = new()
|
||||||
|
{
|
||||||
|
Replacements = [
|
||||||
Replacement.OtherInvalid("_"),
|
Replacement.OtherInvalid("_"),
|
||||||
Replacement.FilenameForwardSlash("∕"),
|
Replacement.FilenameForwardSlash("∕"),
|
||||||
Replacement.FilenameBackSlash(""),
|
Replacement.FilenameBackSlash(""),
|
||||||
@ -91,28 +93,23 @@ namespace FileManager
|
|||||||
Replacement.Colon("_"),
|
Replacement.Colon("_"),
|
||||||
Replacement.Asterisk("✱"),
|
Replacement.Asterisk("✱"),
|
||||||
Replacement.QuestionMark("?"),
|
Replacement.QuestionMark("?"),
|
||||||
Replacement.Pipe("⏐"),
|
Replacement.Pipe("⏐")]
|
||||||
}
|
};
|
||||||
}
|
|
||||||
: new()
|
private static readonly ReplacementCharacters HiFi_Other = new()
|
||||||
{
|
{
|
||||||
Replacements = new Replacement[]
|
Replacements = [
|
||||||
{
|
|
||||||
Replacement.OtherInvalid("_"),
|
Replacement.OtherInvalid("_"),
|
||||||
Replacement.FilenameForwardSlash("∕"),
|
Replacement.FilenameForwardSlash("∕"),
|
||||||
Replacement.FilenameBackSlash("\\"),
|
Replacement.FilenameBackSlash("\\"),
|
||||||
Replacement.OpenQuote("“"),
|
Replacement.OpenQuote("“"),
|
||||||
Replacement.CloseQuote("”"),
|
Replacement.CloseQuote("”"),
|
||||||
Replacement.OtherQuote("\"")
|
Replacement.OtherQuote("\"")]
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
public static readonly ReplacementCharacters LoFiDefault
|
private static readonly ReplacementCharacters LoFi_NTFS = new()
|
||||||
= IsWindows
|
{
|
||||||
? new()
|
Replacements = [
|
||||||
{
|
|
||||||
Replacements = new Replacement[]
|
|
||||||
{
|
|
||||||
Replacement.OtherInvalid("_"),
|
Replacement.OtherInvalid("_"),
|
||||||
Replacement.FilenameForwardSlash("_"),
|
Replacement.FilenameForwardSlash("_"),
|
||||||
Replacement.FilenameBackSlash("_"),
|
Replacement.FilenameBackSlash("_"),
|
||||||
@ -121,56 +118,54 @@ namespace FileManager
|
|||||||
Replacement.OtherQuote("'"),
|
Replacement.OtherQuote("'"),
|
||||||
Replacement.OpenAngleBracket("{"),
|
Replacement.OpenAngleBracket("{"),
|
||||||
Replacement.CloseAngleBracket("}"),
|
Replacement.CloseAngleBracket("}"),
|
||||||
Replacement.Colon("-"),
|
Replacement.Colon("-")]
|
||||||
}
|
};
|
||||||
}
|
|
||||||
: new ()
|
private static readonly ReplacementCharacters LoFi_Other = new()
|
||||||
{
|
{
|
||||||
Replacements = new Replacement[]
|
Replacements = [
|
||||||
{
|
|
||||||
Replacement.OtherInvalid("_"),
|
Replacement.OtherInvalid("_"),
|
||||||
Replacement.FilenameForwardSlash("_"),
|
Replacement.FilenameForwardSlash("_"),
|
||||||
Replacement.FilenameBackSlash("\\"),
|
Replacement.FilenameBackSlash("\\"),
|
||||||
Replacement.OpenQuote("\""),
|
Replacement.OpenQuote("\""),
|
||||||
Replacement.CloseQuote("\""),
|
Replacement.CloseQuote("\""),
|
||||||
Replacement.OtherQuote("\"")
|
Replacement.OtherQuote("\"")]
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
public static readonly ReplacementCharacters Barebones
|
private static readonly ReplacementCharacters BareBones_NTFS = new()
|
||||||
= IsWindows
|
{
|
||||||
? new ()
|
Replacements = [
|
||||||
{
|
|
||||||
Replacements = new Replacement[]
|
|
||||||
{
|
|
||||||
Replacement.OtherInvalid("_"),
|
Replacement.OtherInvalid("_"),
|
||||||
Replacement.FilenameForwardSlash("_"),
|
Replacement.FilenameForwardSlash("_"),
|
||||||
Replacement.FilenameBackSlash("_"),
|
Replacement.FilenameBackSlash("_"),
|
||||||
Replacement.OpenQuote("_"),
|
Replacement.OpenQuote("_"),
|
||||||
Replacement.CloseQuote("_"),
|
Replacement.CloseQuote("_"),
|
||||||
Replacement.OtherQuote("_")
|
Replacement.OtherQuote("_")]
|
||||||
}
|
};
|
||||||
}
|
|
||||||
: new ()
|
private static readonly ReplacementCharacters BareBones_Other = new()
|
||||||
{
|
{
|
||||||
Replacements = new Replacement[]
|
Replacements = [
|
||||||
{
|
|
||||||
Replacement.OtherInvalid("_"),
|
Replacement.OtherInvalid("_"),
|
||||||
Replacement.FilenameForwardSlash("_"),
|
Replacement.FilenameForwardSlash("_"),
|
||||||
Replacement.FilenameBackSlash("\\"),
|
Replacement.FilenameBackSlash("\\"),
|
||||||
Replacement.OpenQuote("\""),
|
Replacement.OpenQuote("\""),
|
||||||
Replacement.CloseQuote("\""),
|
Replacement.CloseQuote("\""),
|
||||||
Replacement.OtherQuote("\"")
|
Replacement.OtherQuote("\"")]
|
||||||
}
|
};
|
||||||
};
|
#endregion
|
||||||
|
/// <summary>
|
||||||
|
/// Characters to consider invalid in filenames in addition to those returned by <see cref="Path.GetInvalidFileNameChars()"/>
|
||||||
|
/// </summary>
|
||||||
|
public static char[] AdditionalInvalidFilenameCharacters { get; set; } = [];
|
||||||
|
|
||||||
private static bool IsWindows => Environment.OSVersion.Platform is PlatformID.Win32NT;
|
internal static bool IsWindows => Environment.OSVersion.Platform is PlatformID.Win32NT;
|
||||||
|
|
||||||
private static readonly char[] invalidPathChars = Path.GetInvalidFileNameChars().Except(new[] {
|
private static char[] invalidPathChars { get; } = Path.GetInvalidFileNameChars().Except(new[] {
|
||||||
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
|
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
private static readonly char[] invalidSlashes = Path.GetInvalidFileNameChars().Intersect(new[] {
|
private static char[] invalidSlashes { get; } = Path.GetInvalidFileNameChars().Intersect(new[] {
|
||||||
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
|
Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
@ -229,8 +224,11 @@ namespace FileManager
|
|||||||
return DefaultReplacement;
|
return DefaultReplacement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool CharIsPathInvalid(char c)
|
||||||
|
=> invalidPathChars.Contains(c) || AdditionalInvalidFilenameCharacters.Contains(c);
|
||||||
|
|
||||||
public static bool ContainsInvalidPathChar(string path)
|
public static bool ContainsInvalidPathChar(string path)
|
||||||
=> path.Any(c => invalidPathChars.Contains(c));
|
=> path.Any(CharIsPathInvalid);
|
||||||
public static bool ContainsInvalidFilenameChar(string path)
|
public static bool ContainsInvalidFilenameChar(string path)
|
||||||
=> ContainsInvalidPathChar(path) || path.Any(c => invalidSlashes.Contains(c));
|
=> ContainsInvalidPathChar(path) || path.Any(c => invalidSlashes.Contains(c));
|
||||||
|
|
||||||
@ -242,7 +240,7 @@ namespace FileManager
|
|||||||
{
|
{
|
||||||
var c = fileName[i];
|
var c = fileName[i];
|
||||||
|
|
||||||
if (invalidPathChars.Contains(c)
|
if (CharIsPathInvalid(c)
|
||||||
|| invalidSlashes.Contains(c)
|
|| invalidSlashes.Contains(c)
|
||||||
|| Replacements.Any(r => r.CharacterToReplace == c) /* Replace any other legal characters that they user wants. */ )
|
|| Replacements.Any(r => r.CharacterToReplace == c) /* Replace any other legal characters that they user wants. */ )
|
||||||
{
|
{
|
||||||
@ -267,14 +265,14 @@ namespace FileManager
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
(
|
(
|
||||||
invalidPathChars.Contains(c)
|
CharIsPathInvalid(c)
|
||||||
|| ( // Replace any other legal characters that they user wants.
|
|| ( // Replace any other legal characters that they user wants.
|
||||||
c != Path.DirectorySeparatorChar
|
c != Path.DirectorySeparatorChar
|
||||||
&& c != Path.AltDirectorySeparatorChar
|
&& c != Path.AltDirectorySeparatorChar
|
||||||
&& Replacements.Any(r => r.CharacterToReplace == c)
|
&& Replacements.Any(r => r.CharacterToReplace == c)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
&& !( // replace all colons except drive letter designator on Windows
|
&& !( // replace all colons except drive letter designator on Windows
|
||||||
c == ':'
|
c == ':'
|
||||||
&& i == 1
|
&& i == 1
|
||||||
&& Path.IsPathRooted(pathStr)
|
&& Path.IsPathRooted(pathStr)
|
||||||
@ -282,9 +280,9 @@ namespace FileManager
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
char preceding = i > 0 ? pathStr[i - 1] : default;
|
char preceding = i > 0 ? pathStr[i - 1] : default;
|
||||||
char succeeding = i < pathStr.Length - 1 ? pathStr[i + 1] : default;
|
char succeeding = i < pathStr.Length - 1 ? pathStr[i + 1] : default;
|
||||||
builder.Append(GetPathCharReplacement(c, preceding, succeeding));
|
builder.Append(GetPathCharReplacement(c, preceding, succeeding));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
builder.Append(c);
|
builder.Append(c);
|
||||||
@ -301,23 +299,21 @@ namespace FileManager
|
|||||||
|
|
||||||
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 defaults = ReplacementCharacters.Default(ReplacementCharacters.IsWindows).Replacements;
|
||||||
|
|
||||||
var jObj = JObject.Load(reader);
|
var jObj = JObject.Load(reader);
|
||||||
var replaceArr = jObj[nameof(Replacement)];
|
var replaceArr = jObj[nameof(Replacement)];
|
||||||
var dict
|
var dict = replaceArr?.ToObject<Replacement[]>()?.ToList() ?? defaults;
|
||||||
= replaceArr?.ToObject<Replacement[]>()?.ToList()
|
|
||||||
?? ReplacementCharacters.Default.Replacements;
|
|
||||||
|
|
||||||
|
|
||||||
//Ensure that the first 6 replacements are for the expected chars and that all replacement strings are valid.
|
//Ensure that the first 6 replacements are for the expected chars and that all replacement strings are valid.
|
||||||
//If not, reset to default.
|
//If not, reset to default.
|
||||||
|
|
||||||
for (int i = 0; i < Replacement.FIXED_COUNT; i++)
|
for (int i = 0; i < Replacement.FIXED_COUNT; i++)
|
||||||
{
|
{
|
||||||
if (dict.Count < Replacement.FIXED_COUNT
|
if (dict.Count < Replacement.FIXED_COUNT
|
||||||
|| dict[i].CharacterToReplace != ReplacementCharacters.Barebones.Replacements[i].CharacterToReplace
|
|| dict[i].CharacterToReplace != defaults[i].CharacterToReplace
|
||||||
|| dict[i].Description != ReplacementCharacters.Barebones.Replacements[i].Description)
|
|| dict[i].Description != defaults[i].Description)
|
||||||
{
|
{
|
||||||
dict = ReplacementCharacters.Default.Replacements;
|
dict = defaults;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -120,7 +120,10 @@ namespace LibationAvalonia
|
|||||||
ShowMainWindow(desktop);
|
ShowMainWindow(desktop);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
await CancelInstallation();
|
{
|
||||||
|
e.Cancel = true;
|
||||||
|
await CancelInstallation(setupDialog);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (setupDialog.IsReturningUser)
|
else if (setupDialog.IsReturningUser)
|
||||||
{
|
{
|
||||||
@ -128,7 +131,8 @@ namespace LibationAvalonia
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await CancelInstallation();
|
e.Cancel = true;
|
||||||
|
await CancelInstallation(setupDialog);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,11 +143,11 @@ namespace LibationAvalonia
|
|||||||
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
|
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await MessageBox.ShowAdminAlert(null, body, title, ex);
|
await MessageBox.ShowAdminAlert(setupDialog, body, title, ex);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
await MessageBox.Show($"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
await MessageBox.Show(setupDialog, $"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -190,6 +194,7 @@ namespace LibationAvalonia
|
|||||||
{
|
{
|
||||||
// path did not result in valid settings
|
// path did not result in valid settings
|
||||||
var continueResult = await MessageBox.Show(
|
var continueResult = await MessageBox.Show(
|
||||||
|
libationFilesDialog,
|
||||||
$"No valid settings were found at this location.\r\nWould you like to create a new install settings in this folder?\r\n\r\n{libationFilesDialog.SelectedDirectory}",
|
$"No valid settings were found at this location.\r\nWould you like to create a new install settings in this folder?\r\n\r\n{libationFilesDialog.SelectedDirectory}",
|
||||||
"New install?",
|
"New install?",
|
||||||
MessageBoxButtons.YesNo,
|
MessageBoxButtons.YesNo,
|
||||||
@ -207,18 +212,18 @@ namespace LibationAvalonia
|
|||||||
ShowMainWindow(desktop);
|
ShowMainWindow(desktop);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
await CancelInstallation();
|
await CancelInstallation(libationFilesDialog);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
await CancelInstallation();
|
await CancelInstallation(libationFilesDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
libationFilesDialog.Close();
|
libationFilesDialog.Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async Task CancelInstallation()
|
static async Task CancelInstallation(Window window)
|
||||||
{
|
{
|
||||||
await MessageBox.Show("Initial set up cancelled.", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
await MessageBox.Show(window, "Initial set up cancelled.", "Cancelled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
Environment.Exit(0);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -92,13 +92,11 @@ namespace LibationAvalonia.Controls
|
|||||||
base.UpdateDataValidation(property, state, error);
|
base.UpdateDataValidation(property, state, error);
|
||||||
if (property == CommandProperty)
|
if (property == CommandProperty)
|
||||||
{
|
{
|
||||||
if (state == BindingValueType.BindingError)
|
var canExecure = !state.HasFlag(BindingValueType.HasError);
|
||||||
|
if (canExecure != _commandCanExecute)
|
||||||
{
|
{
|
||||||
if (_commandCanExecute)
|
_commandCanExecute = canExecure;
|
||||||
{
|
UpdateIsEffectivelyEnabled();
|
||||||
_commandCanExecute = false;
|
|
||||||
UpdateIsEffectivelyEnabled();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@
|
|||||||
MinWidth="500" MinHeight="450"
|
MinWidth="500" MinHeight="450"
|
||||||
Width="500" Height="450"
|
Width="500" Height="450"
|
||||||
x:Class="LibationAvalonia.Dialogs.EditReplacementChars"
|
x:Class="LibationAvalonia.Dialogs.EditReplacementChars"
|
||||||
|
xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs"
|
||||||
|
x:DataType="dialogs:EditReplacementChars"
|
||||||
Title="Illegal Character Replacement">
|
Title="Illegal Character Replacement">
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
@ -23,31 +25,30 @@
|
|||||||
BeginningEdit="ReplacementGrid_BeginningEdit"
|
BeginningEdit="ReplacementGrid_BeginningEdit"
|
||||||
CellEditEnding="ReplacementGrid_CellEditEnding"
|
CellEditEnding="ReplacementGrid_CellEditEnding"
|
||||||
KeyDown="ReplacementGrid_KeyDown"
|
KeyDown="ReplacementGrid_KeyDown"
|
||||||
ItemsSource="{Binding replacements}">
|
ItemsSource="{CompiledBinding replacements}">
|
||||||
|
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
|
|
||||||
|
|
||||||
<DataGridTemplateColumn Header="Char to
Replace">
|
<DataGridTemplateColumn Header="Char to
Replace">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate x:DataType="dialogs:EditReplacementChars+ReplacementsExt">
|
||||||
<TextBox IsReadOnly="{Binding Mandatory}" Text="{Binding CharacterToReplace, Mode=TwoWay}" />
|
<TextBox IsReadOnly="{CompiledBinding Mandatory}" Text="{CompiledBinding CharacterToReplace, Mode=TwoWay}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Header="Replacement
Text">
|
<DataGridTemplateColumn Header="Replacement
Text">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate x:DataType="dialogs:EditReplacementChars+ReplacementsExt">
|
||||||
<TextBox Text="{Binding ReplacementText, Mode=TwoWay}" />
|
<TextBox Text="{CompiledBinding ReplacementText, Mode=TwoWay}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
|
|
||||||
<DataGridTemplateColumn Width="*" Header="Description">
|
<DataGridTemplateColumn Width="*" Header="Description">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate x:DataType="dialogs:EditReplacementChars+ReplacementsExt">
|
||||||
<TextBox IsReadOnly="{Binding Mandatory}" Text="{Binding Description, Mode=TwoWay}" />
|
<TextBox IsReadOnly="{CompiledBinding Mandatory}" Text="{CompiledBinding Description, Mode=TwoWay}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
@ -55,21 +56,31 @@
|
|||||||
</DataGrid.Columns>
|
</DataGrid.Columns>
|
||||||
</DataGrid>
|
</DataGrid>
|
||||||
|
|
||||||
<StackPanel
|
<Grid
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
|
RowDefinitions="Auto,Auto"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Orientation="Horizontal">
|
ColumnDefinitions="Auto,Auto,Auto,Auto">
|
||||||
|
|
||||||
<Button Margin="0,0,10,0" Command="{Binding Defaults}" Content="Defaults" />
|
<TextBlock IsVisible="{CompiledBinding !EnvironmentIsWindows}" Text="This System:" Margin="0,0,10,0" VerticalAlignment="Center" />
|
||||||
<Button Margin="0,0,10,0" Command="{Binding LoFiDefaults}" Content="LoFi Defaults" />
|
<TextBlock IsVisible="{CompiledBinding !EnvironmentIsWindows}" Grid.Row="1" Text="NTFS:" Margin="0,0,10,0" VerticalAlignment="Center" />
|
||||||
<Button Command="{Binding Barebones}" Content="Barebones" />
|
|
||||||
</StackPanel>
|
<Button Grid.Column="1" Margin="0,0,10,0" Command="{CompiledBinding Defaults}" CommandParameter="{CompiledBinding EnvironmentIsWindows}" Content="Defaults" />
|
||||||
|
<Button Grid.Column="2" Margin="0,0,10,0" Command="{CompiledBinding LoFiDefaults}" CommandParameter="{CompiledBinding EnvironmentIsWindows}" Content="LoFi Defaults" />
|
||||||
|
<Button Grid.Column="3" Command="{CompiledBinding Barebones}" CommandParameter="{CompiledBinding EnvironmentIsWindows}" Content="Barebones" />
|
||||||
|
|
||||||
|
<Button IsVisible="{CompiledBinding !EnvironmentIsWindows}" Grid.Row="1" Grid.Column="1" Margin="0,10,10,0" Command="{CompiledBinding Defaults}" CommandParameter="True" Content="Defaults" />
|
||||||
|
<Button IsVisible="{CompiledBinding !EnvironmentIsWindows}" Grid.Row="1" Grid.Column="2" Margin="0,10,10,0" Command="{CompiledBinding LoFiDefaults}" CommandParameter="True" Content="LoFi Defaults" />
|
||||||
|
<Button IsVisible="{CompiledBinding !EnvironmentIsWindows}" Grid.Row="1" Grid.Column="3" Margin="0,10,0,0" Command="{CompiledBinding Barebones}" CommandParameter="True" Content="Barebones" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
|
|
||||||
<Button Margin="0,0,10,0" Command="{Binding Close}" Content="Cancel" />
|
<Button Margin="0,0,10,0" Command="{Binding Close}" Content="Cancel" />
|
||||||
|
|||||||
@ -13,6 +13,8 @@ namespace LibationAvalonia.Dialogs
|
|||||||
{
|
{
|
||||||
Configuration config;
|
Configuration config;
|
||||||
|
|
||||||
|
public bool EnvironmentIsWindows => Configuration.IsWindows;
|
||||||
|
|
||||||
private readonly List<ReplacementsExt> SOURCE = new();
|
private readonly List<ReplacementsExt> SOURCE = new();
|
||||||
public DataGridCollectionView replacements { get; }
|
public DataGridCollectionView replacements { get; }
|
||||||
public EditReplacementChars()
|
public EditReplacementChars()
|
||||||
@ -23,7 +25,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
|
|
||||||
if (Design.IsDesignMode)
|
if (Design.IsDesignMode)
|
||||||
{
|
{
|
||||||
LoadTable(ReplacementCharacters.Default.Replacements);
|
LoadTable(ReplacementCharacters.Default(true).Replacements);
|
||||||
}
|
}
|
||||||
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
@ -35,12 +37,12 @@ namespace LibationAvalonia.Dialogs
|
|||||||
LoadTable(config.ReplacementCharacters.Replacements);
|
LoadTable(config.ReplacementCharacters.Replacements);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Defaults()
|
public void Defaults(bool isNtfs)
|
||||||
=> LoadTable(ReplacementCharacters.Default.Replacements);
|
=> LoadTable(ReplacementCharacters.Default(isNtfs).Replacements);
|
||||||
public void LoFiDefaults()
|
public void LoFiDefaults(bool isNtfs)
|
||||||
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
|
=> LoadTable(ReplacementCharacters.LoFiDefault(isNtfs).Replacements);
|
||||||
public void Barebones()
|
public void Barebones(bool isNtfs)
|
||||||
=> LoadTable(ReplacementCharacters.Barebones.Replacements);
|
=> LoadTable(ReplacementCharacters.Barebones(isNtfs).Replacements);
|
||||||
|
|
||||||
protected override void SaveAndClose()
|
protected override void SaveAndClose()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -6,24 +6,28 @@
|
|||||||
Width="800" Height="450"
|
Width="800" Height="450"
|
||||||
x:Class="LibationAvalonia.Dialogs.EditTemplateDialog"
|
x:Class="LibationAvalonia.Dialogs.EditTemplateDialog"
|
||||||
xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs"
|
xmlns:dialogs="clr-namespace:LibationAvalonia.Dialogs"
|
||||||
|
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||||
|
x:DataType="dialogs:EditTemplateDialog+EditTemplateViewModel"
|
||||||
Title="EditTemplateDialog">
|
Title="EditTemplateDialog">
|
||||||
|
|
||||||
<Grid RowDefinitions="Auto,*,Auto">
|
<Grid RowDefinitions="Auto,*,Auto" Margin="10">
|
||||||
<Grid
|
<Grid
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
RowDefinitions="Auto,Auto"
|
RowDefinitions="Auto,Auto"
|
||||||
ColumnDefinitions="*,Auto" Margin="5">
|
ColumnDefinitions="*,Auto"
|
||||||
|
Margin="0,0,0,10">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Text="{Binding Description}" />
|
Margin="0,0,0,10"
|
||||||
|
Text="{CompiledBinding Description}" />
|
||||||
|
|
||||||
<TextBox
|
<TextBox
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Name="userEditTbox"
|
Name="userEditTbox"
|
||||||
Text="{Binding UserTemplateText, Mode=TwoWay}" />
|
Text="{CompiledBinding UserTemplateText, Mode=TwoWay}" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
@ -32,9 +36,10 @@
|
|||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
VerticalContentAlignment="Center"
|
VerticalContentAlignment="Center"
|
||||||
Content="Reset to Default"
|
Content="Reset to Default"
|
||||||
Command="{Binding ResetToDefault}"/>
|
Command="{CompiledBinding ResetToDefault}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid Grid.Row="1" ColumnDefinitions="Auto,*">
|
<Grid Grid.Row="1" ColumnDefinitions="Auto,*"
|
||||||
|
Margin="0,0,0,10">
|
||||||
|
|
||||||
<DataGrid
|
<DataGrid
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
@ -44,13 +49,13 @@
|
|||||||
GridLinesVisibility="All"
|
GridLinesVisibility="All"
|
||||||
AutoGenerateColumns="False"
|
AutoGenerateColumns="False"
|
||||||
DoubleTapped="EditTemplateViewModel_DoubleTapped"
|
DoubleTapped="EditTemplateViewModel_DoubleTapped"
|
||||||
ItemsSource="{Binding ListItems}" >
|
ItemsSource="{CompiledBinding ListItems}" >
|
||||||
|
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTemplateColumn Width="Auto" Header="Tag">
|
<DataGridTemplateColumn Width="Auto" Header="Tag">
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<TextBlock Height="18" Margin="10,0,10,0" VerticalAlignment="Center" Text="{Binding Item1}" />
|
<TextBlock Height="18" Margin="10,0,10,0" VerticalAlignment="Center" Text="{CompiledBinding Item1}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
@ -61,7 +66,7 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
Height="18"
|
Height="18"
|
||||||
Margin="10,0,10,0"
|
Margin="10,0,10,0"
|
||||||
VerticalAlignment="Center" Text="{Binding Item2}" />
|
VerticalAlignment="Center" Text="{CompiledBinding Item2}" />
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
</DataGridTemplateColumn>
|
</DataGridTemplateColumn>
|
||||||
@ -71,23 +76,22 @@
|
|||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="5,0,5,0"
|
Margin="10,0,0,0"
|
||||||
RowDefinitions="Auto,*,Auto"
|
RowDefinitions="Auto,*,Auto"
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="5,5,5,10"
|
Margin="0,0,0,5"
|
||||||
Text="Example:"/>
|
Text="Example:"/>
|
||||||
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="5"
|
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
|
BorderBrush="{DynamicResource DataGridGridLinesBrush}">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
TextWrapping="WrapWithOverflow"
|
TextWrapping="WrapWithOverflow"
|
||||||
Inlines="{Binding Inlines}" />
|
Inlines="{CompiledBinding Inlines}" />
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@ -95,13 +99,17 @@
|
|||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Foreground="Firebrick"
|
Foreground="Firebrick"
|
||||||
Text="{Binding WarningText}"
|
Text="{CompiledBinding WarningText}"
|
||||||
IsVisible="{Binding WarningText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
IsVisible="{CompiledBinding WarningText, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<controls:LinkLabel
|
||||||
|
Grid.Row="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="Read about naming templates on the Wiki"
|
||||||
|
Command="{Binding GoToNamingTemplateWiki}" />
|
||||||
<Button
|
<Button
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="5"
|
|
||||||
Padding="30,5,30,5"
|
Padding="30,5,30,5"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Content="Save"
|
Content="Save"
|
||||||
|
|||||||
@ -70,7 +70,7 @@ public partial class EditTemplateDialog : DialogWindow
|
|||||||
public async void SaveButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void SaveButton_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
=> await SaveAndCloseAsync();
|
=> await SaveAndCloseAsync();
|
||||||
|
|
||||||
private class EditTemplateViewModel : ViewModels.ViewModelBase
|
internal class EditTemplateViewModel : ViewModels.ViewModelBase
|
||||||
{
|
{
|
||||||
private readonly Configuration config;
|
private readonly Configuration config;
|
||||||
public InlineCollection Inlines { get; } = new();
|
public InlineCollection Inlines { get; } = new();
|
||||||
@ -96,6 +96,9 @@ public partial class EditTemplateDialog : DialogWindow
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void GoToNamingTemplateWiki()
|
||||||
|
=> Go.To.Url(@"ht" + "tps://github.com/rmcrackan/Libation/blob/master/Documentation/NamingTemplates.md");
|
||||||
|
|
||||||
// hold the work-in-progress value. not guaranteed to be valid
|
// hold the work-in-progress value. not guaranteed to be valid
|
||||||
private string _userTemplateText;
|
private string _userTemplateText;
|
||||||
public string UserTemplateText
|
public string UserTemplateText
|
||||||
|
|||||||
@ -13,11 +13,12 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
{
|
{
|
||||||
public partial class LocateAudiobooksDialog : DialogWindow
|
public partial class LocateAudiobooksDialog : DialogWindow
|
||||||
{
|
{
|
||||||
private event EventHandler<FilePathCache.CacheEntry> FileFound;
|
private event EventHandler<FilePathCache.CacheEntry>? FileFound;
|
||||||
private readonly CancellationTokenSource tokenSource = new();
|
private readonly CancellationTokenSource tokenSource = new();
|
||||||
private readonly List<string> foundAsins = new();
|
private readonly List<string> foundAsins = new();
|
||||||
private readonly LocatedAudiobooksViewModel _viewModel;
|
private readonly LocatedAudiobooksViewModel _viewModel;
|
||||||
@ -41,7 +42,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LocateAudiobooksDialog_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
private void LocateAudiobooksDialog_Closing(object? sender, System.ComponentModel.CancelEventArgs e)
|
||||||
{
|
{
|
||||||
tokenSource.Cancel();
|
tokenSource.Cancel();
|
||||||
//If this dialog is closed before it's completed, Closing is fired
|
//If this dialog is closed before it's completed, Closing is fired
|
||||||
@ -50,7 +51,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
this.SaveSizeAndLocation(Configuration.Instance);
|
this.SaveSizeAndLocation(Configuration.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LocateAudiobooks_FileFound(object sender, FilePathCache.CacheEntry e)
|
private void LocateAudiobooks_FileFound(object? sender, FilePathCache.CacheEntry e)
|
||||||
{
|
{
|
||||||
var newItem = new Tuple<string, string>($"[{e.Id}]", Path.GetFileName(e.Path));
|
var newItem = new Tuple<string, string>($"[{e.Id}]", Path.GetFileName(e.Path));
|
||||||
_viewModel.FoundFiles.Add(newItem);
|
_viewModel.FoundFiles.Add(newItem);
|
||||||
@ -63,13 +64,13 @@ namespace LibationAvalonia.Dialogs
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void LocateAudiobooksDialog_Opened(object sender, EventArgs e)
|
private async void LocateAudiobooksDialog_Opened(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
var folderPicker = new FolderPickerOpenOptions
|
var folderPicker = new FolderPickerOpenOptions
|
||||||
{
|
{
|
||||||
Title = "Select the folder to search for audiobooks",
|
Title = "Select the folder to search for audiobooks",
|
||||||
AllowMultiple = false,
|
AllowMultiple = false,
|
||||||
SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix)
|
SuggestedStartLocation = await StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books?.PathWithoutPrefix ?? "")
|
||||||
};
|
};
|
||||||
|
|
||||||
var selectedFolder = (await StorageProvider.OpenFolderPickerAsync(folderPicker))?.SingleOrDefault()?.TryGetLocalPath();
|
var selectedFolder = (await StorageProvider.OpenFolderPickerAsync(folderPicker))?.SingleOrDefault()?.TryGetLocalPath();
|
||||||
@ -89,11 +90,13 @@ namespace LibationAvalonia.Dialogs
|
|||||||
FilePathCache.Insert(book);
|
FilePathCache.Insert(book);
|
||||||
|
|
||||||
var lb = context.GetLibraryBook_Flat_NoTracking(book.Id);
|
var lb = context.GetLibraryBook_Flat_NoTracking(book.Id);
|
||||||
if (lb?.Book?.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated)
|
if (lb is not null && lb.Book?.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated)
|
||||||
await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated));
|
await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated));
|
||||||
|
|
||||||
|
tokenSource.Token.ThrowIfCancellationRequested();
|
||||||
FileFound?.Invoke(this, book);
|
FileFound?.Invoke(this, book);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException) { }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Serilog.Log.Error(ex, "Error adding found audiobook file to Libation. {@audioFile}", book);
|
Serilog.Log.Error(ex, "Error adding found audiobook file to Libation. {@audioFile}", book);
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using FileManager;
|
||||||
using LibationAvalonia.ViewModels.Settings;
|
using LibationAvalonia.ViewModels.Settings;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using LibationUiBase.Forms;
|
using LibationUiBase.Forms;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
@ -39,6 +41,21 @@ namespace LibationAvalonia.Dialogs
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void SaveButton_Clicked(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
=> await SaveAndCloseAsync();
|
{
|
||||||
|
LongPath lonNewBooks = settingsDisp.ImportantSettings.GetBooksDirectory();
|
||||||
|
if (!System.IO.Directory.Exists(lonNewBooks))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.IO.Directory.CreateDirectory(lonNewBooks);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await MessageBox.Show(this, $"Error creating Books Location:\n\n{ex.Message}", "Error creating directory", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await SaveAndCloseAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
private bool _removeButtonsVisible = Design.IsDesignMode;
|
private bool _removeButtonsVisible = Design.IsDesignMode;
|
||||||
private int _numAccountsScanning = 2;
|
private int _numAccountsScanning = 2;
|
||||||
private int _accountsCount = 0;
|
private int _accountsCount = 0;
|
||||||
|
public string LocateAudiobooksTip => Configuration.GetHelpText("LocateAudiobooks");
|
||||||
|
|
||||||
/// <summary> Auto scanning accounts is enables </summary>
|
/// <summary> Auto scanning accounts is enables </summary>
|
||||||
public bool AutoScanChecked { get => _autoScanChecked; set => Configuration.Instance.AutoScan = this.RaiseAndSetIfChanged(ref _autoScanChecked, value); }
|
public bool AutoScanChecked { get => _autoScanChecked; set => Configuration.Instance.AutoScan = this.RaiseAndSetIfChanged(ref _autoScanChecked, value); }
|
||||||
@ -68,7 +69,8 @@ namespace LibationAvalonia.ViewModels
|
|||||||
MainWindow.Loaded += (_, _) =>
|
MainWindow.Loaded += (_, _) =>
|
||||||
{
|
{
|
||||||
refreshImportMenu();
|
refreshImportMenu();
|
||||||
AccountsSettingsPersister.Saved += refreshImportMenu;
|
AccountsSettingsPersister.Saved += (_, _)
|
||||||
|
=> Avalonia.Threading.Dispatcher.UIThread.Invoke(refreshImportMenu);
|
||||||
};
|
};
|
||||||
|
|
||||||
AutoScanChecked = Configuration.Instance.AutoScan;
|
AutoScanChecked = Configuration.Instance.AutoScan;
|
||||||
@ -172,8 +174,19 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
public async Task LocateAudiobooksAsync()
|
public async Task LocateAudiobooksAsync()
|
||||||
{
|
{
|
||||||
var locateDialog = new LibationAvalonia.Dialogs.LocateAudiobooksDialog();
|
var result = await MessageBox.Show(
|
||||||
await locateDialog.ShowDialog(MainWindow);
|
MainWindow,
|
||||||
|
Configuration.GetHelpText(nameof(LibationAvalonia.Dialogs.LocateAudiobooksDialog)),
|
||||||
|
"Locate Previously-Liberated Audiobook Files",
|
||||||
|
MessageBoxButtons.OKCancel,
|
||||||
|
MessageBoxIcon.Information,
|
||||||
|
MessageBoxDefaultButton.Button1);
|
||||||
|
|
||||||
|
if (result is DialogResult.OK)
|
||||||
|
{
|
||||||
|
var locateDialog = new LibationAvalonia.Dialogs.LocateAudiobooksDialog();
|
||||||
|
await locateDialog.ShowDialog(MainWindow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setyNumScanningAccounts(int numScanning)
|
private void setyNumScanningAccounts(int numScanning)
|
||||||
@ -222,7 +235,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshImportMenu(object? _ = null, EventArgs? __ = null)
|
private void refreshImportMenu()
|
||||||
{
|
{
|
||||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
AccountsCount = persister.AccountsSettings.Accounts.Count;
|
AccountsCount = persister.AccountsSettings.Accounts.Count;
|
||||||
|
|||||||
@ -36,10 +36,7 @@ namespace LibationAvalonia.ViewModels.Settings
|
|||||||
|
|
||||||
public void SaveSettings(Configuration config)
|
public void SaveSettings(Configuration config)
|
||||||
{
|
{
|
||||||
LongPath lonNewBooks = Configuration.GetKnownDirectory(BooksDirectory) is Configuration.KnownDirectories.None ? BooksDirectory : System.IO.Path.Combine(BooksDirectory, "Books");
|
config.Books = GetBooksDirectory();
|
||||||
if (!System.IO.Directory.Exists(lonNewBooks))
|
|
||||||
System.IO.Directory.CreateDirectory(lonNewBooks);
|
|
||||||
config.Books = lonNewBooks;
|
|
||||||
config.SavePodcastsToParentFolder = SavePodcastsToParentFolder;
|
config.SavePodcastsToParentFolder = SavePodcastsToParentFolder;
|
||||||
config.OverwriteExisting = OverwriteExisting;
|
config.OverwriteExisting = OverwriteExisting;
|
||||||
config.CreationTime = CreationTime.Value;
|
config.CreationTime = CreationTime.Value;
|
||||||
@ -47,6 +44,9 @@ namespace LibationAvalonia.ViewModels.Settings
|
|||||||
config.LogLevel = LoggingLevel;
|
config.LogLevel = LoggingLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LongPath GetBooksDirectory()
|
||||||
|
=> Configuration.GetKnownDirectory(BooksDirectory) is Configuration.KnownDirectories.None ? BooksDirectory : System.IO.Path.Combine(BooksDirectory, "Books");
|
||||||
|
|
||||||
private static float scaleFactorToLinearRange(float scaleFactor)
|
private static float scaleFactorToLinearRange(float scaleFactor)
|
||||||
=> float.Round(100 * MathF.Log2(scaleFactor));
|
=> float.Round(100 * MathF.Log2(scaleFactor));
|
||||||
private static float linearRangeToScaleFactor(float value)
|
private static float linearRangeToScaleFactor(float value)
|
||||||
|
|||||||
@ -110,7 +110,7 @@
|
|||||||
<MenuItem IsVisible="{CompiledBinding MultipleAccounts}" IsEnabled="{CompiledBinding RemoveMenuItemsEnabled}" Command="{CompiledBinding RemoveBooksSomeAsync}" Header="_Remove Books from Some Accounts" />
|
<MenuItem IsVisible="{CompiledBinding MultipleAccounts}" IsEnabled="{CompiledBinding RemoveMenuItemsEnabled}" Command="{CompiledBinding RemoveBooksSomeAsync}" Header="_Remove Books from Some Accounts" />
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
<MenuItem Command="{CompiledBinding LocateAudiobooksAsync}" Header="L_ocate Audiobooks..." />
|
<MenuItem Command="{CompiledBinding LocateAudiobooksAsync}" Header="L_ocate Audiobooks..." ToolTip.Tip="{CompiledBinding LocateAudiobooksTip}" />
|
||||||
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|
||||||
|
|||||||
@ -43,6 +43,22 @@ namespace LibationAvalonia.Views
|
|||||||
KeyBindings.Add(new KeyBinding { Command = ReactiveCommand.Create(ViewModel.ShowAccountsAsync), Gesture = new KeyGesture(Key.A, KeyModifiers.Control | KeyModifiers.Shift) });
|
KeyBindings.Add(new KeyBinding { Command = ReactiveCommand.Create(ViewModel.ShowAccountsAsync), Gesture = new KeyGesture(Key.A, KeyModifiers.Control | KeyModifiers.Shift) });
|
||||||
KeyBindings.Add(new KeyBinding { Command = ReactiveCommand.Create(ViewModel.ExportLibraryAsync), Gesture = new KeyGesture(Key.S, KeyModifiers.Control) });
|
KeyBindings.Add(new KeyBinding { Command = ReactiveCommand.Create(ViewModel.ExportLibraryAsync), Gesture = new KeyGesture(Key.S, KeyModifiers.Control) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Configuration.Instance.PropertyChanged += Settings_PropertyChanged;
|
||||||
|
Settings_PropertyChanged(this, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Dinah.Core.PropertyChangeFilter(nameof(Configuration.Books))]
|
||||||
|
private void Settings_PropertyChanged(object sender, Dinah.Core.PropertyChangedEventArgsEx e)
|
||||||
|
{
|
||||||
|
if (!Configuration.IsWindows)
|
||||||
|
{
|
||||||
|
//The books directory does not support filenames with windows' invalid characters.
|
||||||
|
//Tell the ReplacementCharacters configuration to treat those characters as invalid.
|
||||||
|
ReplacementCharacters.AdditionalInvalidFilenameCharacters
|
||||||
|
= Configuration.Instance.BooksCanWriteWindowsInvalidChars ? []
|
||||||
|
: FileSystemTest.AdditionalInvalidWindowsFilenameCharacters.ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AudibleApiStorage_LoadError(object sender, AccountSettingsLoadErrorEventArgs e)
|
private void AudibleApiStorage_LoadError(object sender, AccountSettingsLoadErrorEventArgs e)
|
||||||
@ -54,7 +70,7 @@ namespace LibationAvalonia.Views
|
|||||||
FileUtility.SaferMoveToValidPath(
|
FileUtility.SaferMoveToValidPath(
|
||||||
e.SettingsFilePath,
|
e.SettingsFilePath,
|
||||||
e.SettingsFilePath,
|
e.SettingsFilePath,
|
||||||
ReplacementCharacters.Barebones,
|
Configuration.Instance.ReplacementCharacters,
|
||||||
"bak");
|
"bak");
|
||||||
AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
@ -103,6 +119,20 @@ namespace LibationAvalonia.Views
|
|||||||
|
|
||||||
private async void MainWindow_Opened(object sender, EventArgs e)
|
private async void MainWindow_Opened(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
if (AudibleFileStorage.BooksDirectory is null)
|
||||||
|
{
|
||||||
|
var result = await MessageBox.Show(
|
||||||
|
this,
|
||||||
|
"Please set a valid Books location in the settings dialog.",
|
||||||
|
"Books Directory Not Set",
|
||||||
|
MessageBoxButtons.OKCancel,
|
||||||
|
MessageBoxIcon.Warning,
|
||||||
|
MessageBoxDefaultButton.Button1);
|
||||||
|
|
||||||
|
if (result is DialogResult.OK)
|
||||||
|
await new SettingsDialog().ShowDialog(this);
|
||||||
|
}
|
||||||
|
|
||||||
if (Configuration.Instance.FirstLaunch)
|
if (Configuration.Instance.FirstLaunch)
|
||||||
{
|
{
|
||||||
var result = await MessageBox.Show(this, "Would you like a guided tour to get started?", "Libation Walkthrough", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
|
var result = await MessageBox.Show(this, "Would you like a guided tour to get started?", "Libation Walkthrough", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
|
||||||
|
|||||||
@ -5,14 +5,15 @@ using DataLayer;
|
|||||||
using LibationUiBase;
|
using LibationUiBase;
|
||||||
using LibationUiBase.ProcessQueue;
|
using LibationUiBase.ProcessQueue;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationAvalonia.Views
|
namespace LibationAvalonia.Views
|
||||||
{
|
{
|
||||||
public delegate void QueueItemPositionButtonClicked(ProcessBookViewModel item, QueuePosition queueButton);
|
public delegate void QueueItemPositionButtonClicked(ProcessBookViewModel? item, QueuePosition queueButton);
|
||||||
public delegate void QueueItemCancelButtonClicked(ProcessBookViewModel item);
|
public delegate void QueueItemCancelButtonClicked(ProcessBookViewModel? item);
|
||||||
public partial class ProcessBookControl : UserControl
|
public partial class ProcessBookControl : UserControl
|
||||||
{
|
{
|
||||||
public static event QueueItemPositionButtonClicked PositionButtonClicked;
|
public static event QueueItemPositionButtonClicked? PositionButtonClicked;
|
||||||
public static event QueueItemCancelButtonClicked CancelButtonClicked;
|
public static event QueueItemCancelButtonClicked? CancelButtonClicked;
|
||||||
|
|
||||||
public static readonly StyledProperty<ProcessBookStatus> ProcessBookStatusProperty =
|
public static readonly StyledProperty<ProcessBookStatus> ProcessBookStatusProperty =
|
||||||
AvaloniaProperty.Register<ProcessBookControl, ProcessBookStatus>(nameof(ProcessBookStatus), enableDataValidation: true);
|
AvaloniaProperty.Register<ProcessBookControl, ProcessBookStatus>(nameof(ProcessBookStatus), enableDataValidation: true);
|
||||||
@ -31,12 +32,13 @@ namespace LibationAvalonia.Views
|
|||||||
{
|
{
|
||||||
using var context = DbContexts.GetContext();
|
using var context = DbContexts.GetContext();
|
||||||
ViewModels.MainVM.Configure_NonUI();
|
ViewModels.MainVM.Configure_NonUI();
|
||||||
DataContext = new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"));
|
if (context.GetLibraryBook_Flat_NoTracking("B017V4IM1G") is LibraryBook book)
|
||||||
|
DataContext = new ProcessBookViewModel(book);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProcessBookViewModel DataItem => DataContext is null ? null : DataContext as ProcessBookViewModel;
|
private ProcessBookViewModel? DataItem => DataContext as ProcessBookViewModel;
|
||||||
|
|
||||||
public void Cancel_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public void Cancel_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
=> CancelButtonClicked?.Invoke(DataItem);
|
=> CancelButtonClicked?.Invoke(DataItem);
|
||||||
|
|||||||
@ -34,44 +34,51 @@ namespace LibationAvalonia.Views
|
|||||||
var vm = new ProcessQueueViewModel();
|
var vm = new ProcessQueueViewModel();
|
||||||
DataContext = vm;
|
DataContext = vm;
|
||||||
using var context = DbContexts.GetContext();
|
using var context = DbContexts.GetContext();
|
||||||
|
|
||||||
|
|
||||||
|
var trialBook = context.GetLibraryBook_Flat_NoTracking("B017V4IM1G") ?? context.GetLibrary_Flat_NoTracking().FirstOrDefault();
|
||||||
|
if (trialBook is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
List<ProcessBookViewModel> testList = new()
|
List<ProcessBookViewModel> testList = new()
|
||||||
{
|
{
|
||||||
new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"))
|
new ProcessBookViewModel(trialBook)
|
||||||
{
|
{
|
||||||
Result = ProcessBookResult.FailedAbort,
|
Result = ProcessBookResult.FailedAbort,
|
||||||
Status = ProcessBookStatus.Failed,
|
Status = ProcessBookStatus.Failed,
|
||||||
},
|
},
|
||||||
new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4IWVG"))
|
new ProcessBookViewModel(trialBook)
|
||||||
{
|
{
|
||||||
Result = ProcessBookResult.FailedSkip,
|
Result = ProcessBookResult.FailedSkip,
|
||||||
Status = ProcessBookStatus.Failed,
|
Status = ProcessBookStatus.Failed,
|
||||||
},
|
},
|
||||||
new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4JA2Q"))
|
new ProcessBookViewModel(trialBook)
|
||||||
{
|
{
|
||||||
Result = ProcessBookResult.FailedRetry,
|
Result = ProcessBookResult.FailedRetry,
|
||||||
Status = ProcessBookStatus.Failed,
|
Status = ProcessBookStatus.Failed,
|
||||||
},
|
},
|
||||||
new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4NUPO"))
|
new ProcessBookViewModel(trialBook)
|
||||||
{
|
{
|
||||||
Result = ProcessBookResult.ValidationFail,
|
Result = ProcessBookResult.ValidationFail,
|
||||||
Status = ProcessBookStatus.Failed,
|
Status = ProcessBookStatus.Failed,
|
||||||
},
|
},
|
||||||
new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4NMX4"))
|
new ProcessBookViewModel(trialBook)
|
||||||
{
|
{
|
||||||
Result = ProcessBookResult.Cancelled,
|
Result = ProcessBookResult.Cancelled,
|
||||||
Status = ProcessBookStatus.Cancelled,
|
Status = ProcessBookStatus.Cancelled,
|
||||||
},
|
},
|
||||||
new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4NOZ0"))
|
new ProcessBookViewModel(trialBook)
|
||||||
{
|
{
|
||||||
Result = ProcessBookResult.Success,
|
Result = ProcessBookResult.Success,
|
||||||
Status = ProcessBookStatus.Completed,
|
Status = ProcessBookStatus.Completed,
|
||||||
},
|
},
|
||||||
new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6"))
|
new ProcessBookViewModel(trialBook)
|
||||||
{
|
{
|
||||||
Result = ProcessBookResult.None,
|
Result = ProcessBookResult.None,
|
||||||
Status = ProcessBookStatus.Working,
|
Status = ProcessBookStatus.Working,
|
||||||
},
|
},
|
||||||
new ProcessBookViewModel(context.GetLibraryBook_Flat_NoTracking("B017V4IM1G"))
|
new ProcessBookViewModel(trialBook)
|
||||||
{
|
{
|
||||||
Result = ProcessBookResult.None,
|
Result = ProcessBookResult.None,
|
||||||
Status = ProcessBookStatus.Queued,
|
Status = ProcessBookStatus.Queued,
|
||||||
@ -99,7 +106,7 @@ namespace LibationAvalonia.Views
|
|||||||
|
|
||||||
#region Control event handlers
|
#region Control event handlers
|
||||||
|
|
||||||
private async void ProcessBookControl2_CancelButtonClicked(ProcessBookViewModel item)
|
private async void ProcessBookControl2_CancelButtonClicked(ProcessBookViewModel? item)
|
||||||
{
|
{
|
||||||
if (item is not null)
|
if (item is not null)
|
||||||
{
|
{
|
||||||
@ -108,19 +115,20 @@ namespace LibationAvalonia.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessBookControl2_ButtonClicked(ProcessBookViewModel item, QueuePosition queueButton)
|
private void ProcessBookControl2_ButtonClicked(ProcessBookViewModel? item, QueuePosition queueButton)
|
||||||
{
|
{
|
||||||
Queue?.MoveQueuePosition(item, queueButton);
|
if (item is not null)
|
||||||
|
Queue?.MoveQueuePosition(item, queueButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void CancelAllBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public async void CancelAllBtn_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Queue?.ClearQueue();
|
Queue?.ClearQueue();
|
||||||
if (Queue?.Current is not null)
|
if (Queue?.Current is not null)
|
||||||
await Queue.Current.CancelAsync();
|
await Queue.Current.CancelAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearFinishedBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public void ClearFinishedBtn_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Queue?.ClearCompleted();
|
Queue?.ClearCompleted();
|
||||||
|
|
||||||
@ -128,12 +136,12 @@ namespace LibationAvalonia.Views
|
|||||||
_viewModel.RunningTime = string.Empty;
|
_viewModel.RunningTime = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearLogBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
public void ClearLogBtn_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_viewModel?.LogEntries.Clear();
|
_viewModel?.LogEntries.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void LogCopyBtn_Click(object sender, Avalonia.Interactivity.RoutedEventArgs e)
|
private async void LogCopyBtn_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (_viewModel is ProcessQueueViewModel vm)
|
if (_viewModel is ProcessQueueViewModel vm)
|
||||||
{
|
{
|
||||||
@ -143,14 +151,14 @@ namespace LibationAvalonia.Views
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void cancelAllBtn_Click(object sender, EventArgs e)
|
private async void cancelAllBtn_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Queue?.ClearQueue();
|
Queue?.ClearQueue();
|
||||||
if (Queue?.Current is not null)
|
if (Queue?.Current is not null)
|
||||||
await Queue.Current.CancelAsync();
|
await Queue.Current.CancelAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void btnClearFinished_Click(object sender, EventArgs e)
|
private void btnClearFinished_Click(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Queue?.ClearCompleted();
|
Queue?.ClearCompleted();
|
||||||
|
|
||||||
|
|||||||
@ -62,25 +62,22 @@ namespace LibationAvalonia.Views
|
|||||||
if (Design.IsDesignMode)
|
if (Design.IsDesignMode)
|
||||||
{
|
{
|
||||||
using var context = DbContexts.GetContext();
|
using var context = DbContexts.GetContext();
|
||||||
List<LibraryBook> sampleEntries;
|
LibraryBook?[] sampleEntries;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sampleEntries = new()
|
sampleEntries = [
|
||||||
{
|
|
||||||
//context.GetLibraryBook_Flat_NoTracking("B00DCD0OXU"),try{
|
|
||||||
context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6"),
|
context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6"),
|
||||||
context.GetLibraryBook_Flat_NoTracking("B017V4IWVG"),
|
context.GetLibraryBook_Flat_NoTracking("B017V4IWVG"),
|
||||||
context.GetLibraryBook_Flat_NoTracking("B017V4JA2Q"),
|
context.GetLibraryBook_Flat_NoTracking("B017V4JA2Q"),
|
||||||
context.GetLibraryBook_Flat_NoTracking("B017V4NUPO"),
|
context.GetLibraryBook_Flat_NoTracking("B017V4NUPO"),
|
||||||
context.GetLibraryBook_Flat_NoTracking("B017V4NMX4"),
|
context.GetLibraryBook_Flat_NoTracking("B017V4NMX4"),
|
||||||
context.GetLibraryBook_Flat_NoTracking("B017V4NOZ0"),
|
context.GetLibraryBook_Flat_NoTracking("B017V4NOZ0"),
|
||||||
context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6")
|
context.GetLibraryBook_Flat_NoTracking("B017WJ5ZK6")];
|
||||||
};
|
|
||||||
}
|
}
|
||||||
catch { sampleEntries = new(); }
|
catch { sampleEntries = []; }
|
||||||
|
|
||||||
var pdvm = new ProductsDisplayViewModel();
|
var pdvm = new ProductsDisplayViewModel();
|
||||||
_ = pdvm.BindToGridAsync(sampleEntries);
|
_ = pdvm.BindToGridAsync(sampleEntries.OfType<LibraryBook>().ToList());
|
||||||
DataContext = pdvm;
|
DataContext = pdvm;
|
||||||
|
|
||||||
setGridScale(1);
|
setGridScale(1);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using Avalonia.Controls.Presenters;
|
|||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
|
using Dinah.Core;
|
||||||
using Dinah.Core.StepRunner;
|
using Dinah.Core.StepRunner;
|
||||||
using LibationAvalonia.Dialogs;
|
using LibationAvalonia.Dialogs;
|
||||||
using LibationAvalonia.Views;
|
using LibationAvalonia.Views;
|
||||||
@ -164,7 +165,8 @@ namespace LibationAvalonia
|
|||||||
{
|
{
|
||||||
//if we imported new books, wait for the grid to update before proceeding.
|
//if we imported new books, wait for the grid to update before proceeding.
|
||||||
if (newCount > 0)
|
if (newCount > 0)
|
||||||
MainForm.ViewModel.ProductsDisplay.VisibleCountChanged += productsDisplay_VisibleCountChanged;
|
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||||
|
MainForm.ViewModel.ProductsDisplay.VisibleCountChanged += productsDisplay_VisibleCountChanged);
|
||||||
else
|
else
|
||||||
tcs.SetResult();
|
tcs.SetResult();
|
||||||
}
|
}
|
||||||
@ -176,7 +178,7 @@ namespace LibationAvalonia
|
|||||||
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
||||||
if (books.Count == 0) return true;
|
if (books.Count == 0) return true;
|
||||||
|
|
||||||
var firstAuthor = getFirstAuthor();
|
var firstAuthor = getFirstAuthor()?.SurroundWithQuotes();
|
||||||
if (firstAuthor == null) return true;
|
if (firstAuthor == null) return true;
|
||||||
|
|
||||||
if (!await ProceedMessageBox("You can filter the grid entries by searching", "Searching"))
|
if (!await ProceedMessageBox("You can filter the grid entries by searching", "Searching"))
|
||||||
@ -193,7 +195,7 @@ namespace LibationAvalonia
|
|||||||
|
|
||||||
await displayControlAsync(MainForm.filterBtn);
|
await displayControlAsync(MainForm.filterBtn);
|
||||||
|
|
||||||
MainForm.filterBtn.Command.Execute(null);
|
MainForm.filterBtn.Command.Execute(firstAuthor);
|
||||||
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
|
|
||||||
@ -209,8 +211,7 @@ namespace LibationAvalonia
|
|||||||
|
|
||||||
private async Task<bool> ShowQuickFilters()
|
private async Task<bool> ShowQuickFilters()
|
||||||
{
|
{
|
||||||
var firstAuthor = getFirstAuthor();
|
var firstAuthor = getFirstAuthor()?.SurroundWithQuotes();
|
||||||
|
|
||||||
if (firstAuthor == null) return true;
|
if (firstAuthor == null) return true;
|
||||||
|
|
||||||
if (!await ProceedMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters"))
|
if (!await ProceedMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters"))
|
||||||
@ -222,7 +223,7 @@ namespace LibationAvalonia
|
|||||||
|
|
||||||
await Task.Delay(750);
|
await Task.Delay(750);
|
||||||
await displayControlAsync(MainForm.addQuickFilterBtn);
|
await displayControlAsync(MainForm.addQuickFilterBtn);
|
||||||
MainForm.addQuickFilterBtn.Command.Execute(null);
|
MainForm.addQuickFilterBtn.Command.Execute(firstAuthor);
|
||||||
await displayControlAsync(MainForm.quickFiltersToolStripMenuItem);
|
await displayControlAsync(MainForm.quickFiltersToolStripMenuItem);
|
||||||
await displayControlAsync(editQuickFiltersToolStripMenuItem);
|
await displayControlAsync(editQuickFiltersToolStripMenuItem);
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
using LibationFileManager;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
@ -6,6 +8,15 @@ namespace LibationCli
|
|||||||
[Verb("convert", HelpText = "Convert mp4 to mp3.")]
|
[Verb("convert", HelpText = "Convert mp4 to mp3.")]
|
||||||
public class ConvertOptions : ProcessableOptionsBase
|
public class ConvertOptions : ProcessableOptionsBase
|
||||||
{
|
{
|
||||||
protected override Task ProcessAsync() => RunAsync(CreateProcessable<FileLiberator.ConvertToMp3>());
|
protected override Task ProcessAsync()
|
||||||
|
{
|
||||||
|
if (AudibleFileStorage.BooksDirectory is null)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Error: Books directory is not set. Please configure the 'Books' setting in Settings.json.");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return RunAsync(CreateProcessable<FileLiberator.ConvertToMp3>());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
using CommandLine;
|
using CommandLine;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using FileLiberator;
|
using FileLiberator;
|
||||||
|
using LibationFileManager;
|
||||||
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
@ -13,9 +15,17 @@ namespace LibationCli
|
|||||||
public bool PdfOnly { get; set; }
|
public bool PdfOnly { get; set; }
|
||||||
|
|
||||||
protected override Task ProcessAsync()
|
protected override Task ProcessAsync()
|
||||||
=> PdfOnly
|
{
|
||||||
|
if (AudibleFileStorage.BooksDirectory is null)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Error: Books directory is not set. Please configure the 'Books' setting in Settings.json.");
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
return PdfOnly
|
||||||
? RunAsync(CreateProcessable<DownloadPdf>())
|
? RunAsync(CreateProcessable<DownloadPdf>())
|
||||||
: RunAsync(CreateBackupBook());
|
: RunAsync(CreateBackupBook());
|
||||||
|
}
|
||||||
|
|
||||||
private static Processable CreateBackupBook()
|
private static Processable CreateBackupBook()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -45,13 +45,24 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
public static AudioFileStorage Audio { get; } = new AudioFileStorage();
|
public static AudioFileStorage Audio { get; } = new AudioFileStorage();
|
||||||
|
|
||||||
public static LongPath BooksDirectory
|
/// <summary>
|
||||||
|
/// The fully-qualified Books durectory path if the directory exists, otherwise null.
|
||||||
|
/// </summary>
|
||||||
|
public static LongPath? BooksDirectory
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(Configuration.Instance.Books))
|
if (string.IsNullOrWhiteSpace(Configuration.Instance.Books))
|
||||||
Configuration.Instance.Books = Configuration.DefaultBooksDirectory;
|
return null;
|
||||||
return Directory.CreateDirectory(Configuration.Instance.Books).FullName;
|
try
|
||||||
|
{
|
||||||
|
return Directory.CreateDirectory(Configuration.Instance.Books)?.FullName;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Serilog.Log.Error(ex, "Error creating Books directory: {@BooksDirectory}", Configuration.Instance.Books);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
@ -129,8 +140,9 @@ namespace LibationFileManager
|
|||||||
protected override LongPath? GetFilePathCustom(string productId)
|
protected override LongPath? GetFilePathCustom(string productId)
|
||||||
=> GetFilePathsCustom(productId).FirstOrDefault();
|
=> GetFilePathsCustom(productId).FirstOrDefault();
|
||||||
|
|
||||||
private static BackgroundFileSystem newBookDirectoryFiles()
|
private static BackgroundFileSystem? newBookDirectoryFiles()
|
||||||
=> new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
|
=> BooksDirectory is LongPath books ? new BackgroundFileSystem(books, "*.*", SearchOption.AllDirectories)
|
||||||
|
: null;
|
||||||
|
|
||||||
protected override List<LongPath> GetFilePathsCustom(string productId)
|
protected override List<LongPath> GetFilePathsCustom(string productId)
|
||||||
{
|
{
|
||||||
@ -140,6 +152,7 @@ namespace LibationFileManager
|
|||||||
BookDirectoryFiles = newBookDirectoryFiles();
|
BookDirectoryFiles = newBookDirectoryFiles();
|
||||||
|
|
||||||
var regex = GetBookSearchRegex(productId);
|
var regex = GetBookSearchRegex(productId);
|
||||||
|
var diskFiles = BookDirectoryFiles?.FindFiles(regex) ?? [];
|
||||||
|
|
||||||
//Find all extant files matching the productId
|
//Find all extant files matching the productId
|
||||||
//using both the file system and the file path cache
|
//using both the file system and the file path cache
|
||||||
@ -148,17 +161,17 @@ namespace LibationFileManager
|
|||||||
.GetFiles(productId)
|
.GetFiles(productId)
|
||||||
.Where(c => c.fileType == FileType.Audio && File.Exists(c.path))
|
.Where(c => c.fileType == FileType.Audio && File.Exists(c.path))
|
||||||
.Select(c => c.path)
|
.Select(c => c.path)
|
||||||
.Union(BookDirectoryFiles.FindFiles(regex))
|
.Union(diskFiles)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Refresh()
|
public void Refresh()
|
||||||
{
|
{
|
||||||
if (BookDirectoryFiles is null)
|
if (BookDirectoryFiles is null && BooksDirectory is not null)
|
||||||
lock (bookDirectoryFilesLocker)
|
lock (bookDirectoryFilesLocker)
|
||||||
BookDirectoryFiles = newBookDirectoryFiles();
|
BookDirectoryFiles = newBookDirectoryFiles();
|
||||||
else
|
|
||||||
BookDirectoryFiles?.RefreshFiles();
|
BookDirectoryFiles?.RefreshFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
public LongPath? GetPath(string productId) => GetFilePath(productId);
|
public LongPath? GetPath(string productId) => GetFilePath(productId);
|
||||||
|
|||||||
@ -107,8 +107,27 @@ namespace LibationFileManager
|
|||||||
don't have a spatial audio version will be download
|
don't have a spatial audio version will be download
|
||||||
as usual based on your other file quality settings.
|
as usual based on your other file quality settings.
|
||||||
""" },
|
""" },
|
||||||
}
|
{"LocateAudiobooks","""
|
||||||
.AsReadOnly();
|
Scan the contents a folder to find audio files that
|
||||||
|
match books in Libation's database. This is useful
|
||||||
|
if you moved your Books folder or re-installed
|
||||||
|
Libation and want it to be able to find your
|
||||||
|
already downloaded audiobooks.
|
||||||
|
|
||||||
|
Prerequisite: An audiobook must already exist in
|
||||||
|
Libation's database (through an Audible account
|
||||||
|
scan) for a matching audio file to be found.
|
||||||
|
""" },
|
||||||
|
{"LocateAudiobooksDialog","""
|
||||||
|
Libation will search all .m4b and .mp3 files in a folder, looking for audio files belonging to library books in Libation's database.
|
||||||
|
|
||||||
|
If an audiobook file is found that matches one of Libation's library books, Libation will mark that book as "Liberated" (green stoplight).
|
||||||
|
|
||||||
|
For an audio file to be identified, Libation must have that library book in its database. If you're on a fresh installation of Libation, be sure to add and scan all of your Audible accounts before running this action.
|
||||||
|
|
||||||
|
This may take a while, depending on the number of audio files in the folder and the speed of your storage device.
|
||||||
|
""" }
|
||||||
|
}.AsReadOnly();
|
||||||
|
|
||||||
public static string GetHelpText(string? settingName)
|
public static string GetHelpText(string? settingName)
|
||||||
=> settingName != null && HelpText.TryGetValue(settingName, out var value) ? value : "";
|
=> settingName != null && HelpText.TryGetValue(settingName, out var value) ? value : "";
|
||||||
|
|||||||
@ -84,7 +84,7 @@ namespace LibationFileManager
|
|||||||
ProcessDirectory,
|
ProcessDirectory,
|
||||||
LocalAppData,
|
LocalAppData,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
Path.Combine(Path.GetTempPath(), "Libation")
|
WinTemp,
|
||||||
};
|
};
|
||||||
|
|
||||||
//Try to find and validate appsettings.json in each folder
|
//Try to find and validate appsettings.json in each folder
|
||||||
@ -181,7 +181,7 @@ namespace LibationFileManager
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Serilog.Log.Error(e, "Failed to run shell command. {Arguments}", psi.ArgumentList);
|
Serilog.Log.Error(e, "Failed to run shell command. {@Arguments}", psi.ArgumentList);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ namespace LibationFileManager
|
|||||||
//https://github.com/serilog/serilog-settings-configuration/issues/406
|
//https://github.com/serilog/serilog-settings-configuration/issues/406
|
||||||
var readerOptions = new ConfigurationReaderOptions(
|
var readerOptions = new ConfigurationReaderOptions(
|
||||||
typeof(ILogger).Assembly, // Serilog
|
typeof(ILogger).Assembly, // Serilog
|
||||||
|
typeof(LoggerCallerEnrichmentConfiguration).Assembly, // Dinah.Core
|
||||||
typeof(LoggerEnrichmentConfigurationExtensions).Assembly, // Serilog.Exceptions
|
typeof(LoggerEnrichmentConfigurationExtensions).Assembly, // Serilog.Exceptions
|
||||||
typeof(ConsoleLoggerConfigurationExtensions).Assembly, // Serilog.Sinks.Console
|
typeof(ConsoleLoggerConfigurationExtensions).Assembly, // Serilog.Sinks.Console
|
||||||
typeof(FileLoggerConfigurationExtensions).Assembly); // Serilog.Sinks.File
|
typeof(FileLoggerConfigurationExtensions).Assembly); // Serilog.Sinks.File
|
||||||
|
|||||||
@ -36,12 +36,12 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
[return: NotNullIfNotNull(nameof(defaultValue))]
|
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||||
public T? GetNonString<T>(T defaultValue, [CallerMemberName] string propertyName = "")
|
public T? GetNonString<T>(T defaultValue, [CallerMemberName] string propertyName = "")
|
||||||
=> Settings.GetNonString(propertyName, defaultValue);
|
=> Settings is null ? default : Settings.GetNonString(propertyName, defaultValue);
|
||||||
|
|
||||||
|
|
||||||
[return: NotNullIfNotNull(nameof(defaultValue))]
|
[return: NotNullIfNotNull(nameof(defaultValue))]
|
||||||
public string? GetString(string? defaultValue = null, [CallerMemberName] string propertyName = "")
|
public string? GetString(string? defaultValue = null, [CallerMemberName] string propertyName = "")
|
||||||
=> Settings.GetString(propertyName, defaultValue);
|
=> Settings?.GetString(propertyName, defaultValue);
|
||||||
|
|
||||||
public object? GetObject([CallerMemberName] string propertyName = "") => Settings.GetObject(propertyName);
|
public object? GetObject([CallerMemberName] string propertyName = "") => Settings.GetObject(propertyName);
|
||||||
|
|
||||||
@ -111,7 +111,34 @@ namespace LibationFileManager
|
|||||||
public bool BetaOptIn { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
public bool BetaOptIn { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
||||||
|
|
||||||
[Description("Location for book storage. Includes destination of newly liberated books")]
|
[Description("Location for book storage. Includes destination of newly liberated books")]
|
||||||
public LongPath? Books { get => GetString(); set => SetString(value); }
|
public LongPath? Books {
|
||||||
|
get => GetString();
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value != Books)
|
||||||
|
{
|
||||||
|
OnPropertyChanging(nameof(Books), Books, value);
|
||||||
|
Settings.SetString(nameof(Books), value);
|
||||||
|
m_BooksCanWrite255UnicodeChars = null;
|
||||||
|
m_BooksCanWriteWindowsInvalidChars = null;
|
||||||
|
OnPropertyChanged(nameof(Books), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool? m_BooksCanWrite255UnicodeChars;
|
||||||
|
private bool? m_BooksCanWriteWindowsInvalidChars;
|
||||||
|
/// <summary>
|
||||||
|
/// True if the Books directory can be written to with 255 unicode character filenames
|
||||||
|
/// <para/> Does not persist. Check and set this value at runtime and whenever Books is changed.
|
||||||
|
/// </summary>
|
||||||
|
public bool BooksCanWrite255UnicodeChars => m_BooksCanWrite255UnicodeChars ??= FileSystemTest.CanWrite255UnicodeChars(AudibleFileStorage.BooksDirectory);
|
||||||
|
/// <summary>
|
||||||
|
/// True if the Books directory can be written to with filenames containing characters invalid on Windows (:, *, ?, <, >, |)
|
||||||
|
/// <para/> Always false on Windows platforms.
|
||||||
|
/// <para/> Does not persist. Check and set this value at runtime and whenever Books is changed.
|
||||||
|
/// </summary>
|
||||||
|
public bool BooksCanWriteWindowsInvalidChars => !IsWindows && (m_BooksCanWriteWindowsInvalidChars ??= FileSystemTest.CanWriteWindowsInvalidChars(AudibleFileStorage.BooksDirectory));
|
||||||
|
|
||||||
[Description("Overwrite existing files if they already exist?")]
|
[Description("Overwrite existing files if they already exist?")]
|
||||||
public bool OverwriteExisting { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
public bool OverwriteExisting { get => GetNonString(defaultValue: false); set => SetNonString(value); }
|
||||||
@ -319,7 +346,7 @@ namespace LibationFileManager
|
|||||||
#region templates: custom file naming
|
#region templates: custom file naming
|
||||||
|
|
||||||
[Description("Edit how filename characters are replaced")]
|
[Description("Edit how filename characters are replaced")]
|
||||||
public ReplacementCharacters ReplacementCharacters { get => GetNonString(defaultValue: ReplacementCharacters.Default); set => SetNonString(value); }
|
public ReplacementCharacters ReplacementCharacters { get => GetNonString(defaultValue: ReplacementCharacters.Default(IsWindows)); set => SetNonString(value); }
|
||||||
|
|
||||||
[Description("How to format the folders in which files will be saved")]
|
[Description("How to format the folders in which files will be saved")]
|
||||||
public string FolderTemplate
|
public string FolderTemplate
|
||||||
|
|||||||
@ -3,48 +3,58 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager
|
||||||
{
|
{
|
||||||
public partial class Configuration : PropertyChangeFilter
|
public partial class Configuration : PropertyChangeFilter
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if <see cref="SettingsFilePath"/> exists and the <see cref="Books"/> property has a non-null, non-empty value.
|
||||||
|
/// Does not verify the existence of the <see cref="Books"/> directory.
|
||||||
|
/// </summary>
|
||||||
public bool LibationSettingsAreValid => SettingsFileIsValid(SettingsFilePath);
|
public bool LibationSettingsAreValid => SettingsFileIsValid(SettingsFilePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if <paramref name="settingsFile"/> exists and the <see cref="Books"/> property has a non-null, non-empty value.
|
||||||
|
/// Does not verify the existence of the <see cref="Books"/> directory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settingsFile">File path to the settings JSON file</param>
|
||||||
public static bool SettingsFileIsValid(string settingsFile)
|
public static bool SettingsFileIsValid(string settingsFile)
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(Path.GetDirectoryName(settingsFile)) || !File.Exists(settingsFile))
|
if (!Directory.Exists(Path.GetDirectoryName(settingsFile)) || !File.Exists(settingsFile))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var pDic = new PersistentDictionary(settingsFile, isReadOnly: false);
|
try
|
||||||
|
|
||||||
if (pDic.GetString(nameof(Books)) is not string booksDir)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!Directory.Exists(booksDir))
|
|
||||||
{
|
{
|
||||||
if (Path.GetDirectoryName(settingsFile) is not string dir)
|
var settingsJson = JObject.Parse(File.ReadAllText(settingsFile));
|
||||||
throw new DirectoryNotFoundException(settingsFile);
|
return !string.IsNullOrWhiteSpace(settingsJson[nameof(Books)]?.Value<string>());
|
||||||
|
}
|
||||||
//"Books" is not null, so setup has already been run.
|
catch (Exception ex)
|
||||||
//Since Books can't be found, try to create it
|
{
|
||||||
//and then revert to the default books directory
|
Serilog.Log.Logger.Error(ex, "Failed to load settings file: {@SettingsFile}", settingsFile);
|
||||||
foreach (string d in new string[] { booksDir, DefaultBooksDirectory })
|
try
|
||||||
{
|
{
|
||||||
|
Serilog.Log.Logger.Information("Deleting invalid settings file: {@SettingsFile}", settingsFile);
|
||||||
|
FileUtility.SaferDelete(settingsFile);
|
||||||
|
Serilog.Log.Logger.Information("Creating a new, empty setting file: {@SettingsFile}", settingsFile);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(d);
|
File.WriteAllText(settingsFile, "{}");
|
||||||
|
}
|
||||||
pDic.SetString(nameof(Books), d);
|
catch (Exception createEx)
|
||||||
|
{
|
||||||
return Directory.Exists(d);
|
Serilog.Log.Logger.Error(createEx, "Failed to create new settings file: {@SettingsFile}", settingsFile);
|
||||||
}
|
}
|
||||||
catch { /* Do Nothing */ }
|
|
||||||
}
|
}
|
||||||
|
catch (Exception deleteEx)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Error(deleteEx, "Failed to delete the invalid settings file: {@SettingsFile}", settingsFile);
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region singleton stuff
|
#region singleton stuff
|
||||||
|
|||||||
@ -7,9 +7,9 @@ public record SeriesDto : IFormattable
|
|||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
public float? Number { get; }
|
public string? Number { get; }
|
||||||
public string AudibleSeriesId { get; }
|
public string AudibleSeriesId { get; }
|
||||||
public SeriesDto(string name, float? number, string audibleSeriesId)
|
public SeriesDto(string name, string? number, string audibleSeriesId)
|
||||||
{
|
{
|
||||||
Name = name;
|
Name = name;
|
||||||
Number = number;
|
Number = number;
|
||||||
|
|||||||
@ -68,7 +68,7 @@ namespace LibationFileManager.Templates
|
|||||||
YearPublished = 2017,
|
YearPublished = 2017,
|
||||||
Authors = [new("Arthur Conan Doyle", "B000AQ43GQ"), new("Stephen Fry - introductions", "B000APAGVS")],
|
Authors = [new("Arthur Conan Doyle", "B000AQ43GQ"), new("Stephen Fry - introductions", "B000APAGVS")],
|
||||||
Narrators = [new("Stephen Fry", null)],
|
Narrators = [new("Stephen Fry", null)],
|
||||||
Series = [new("Sherlock Holmes", 1, "B08376S3R2"), new("Some Other Series", 1, "B000000000")],
|
Series = [new("Sherlock Holmes", "1-6", "B08376S3R2"), new("Book Collection", "1", "B000000000")],
|
||||||
Codec = "AAC-LC",
|
Codec = "AAC-LC",
|
||||||
LibationVersion = Configuration.LibationVersion?.ToVersionString(),
|
LibationVersion = Configuration.LibationVersion?.ToVersionString(),
|
||||||
FileVersion = "36217811",
|
FileVersion = "36217811",
|
||||||
|
|||||||
@ -157,7 +157,7 @@ namespace LibationFileManager.Templates
|
|||||||
var maxFilenameLength = LongPath.MaxFilenameLength -
|
var maxFilenameLength = LongPath.MaxFilenameLength -
|
||||||
(i < pathParts.Count - 1 || string.IsNullOrEmpty(fileExtension) ? 0 : fileExtension.Length + 5);
|
(i < pathParts.Count - 1 || string.IsNullOrEmpty(fileExtension) ? 0 : fileExtension.Length + 5);
|
||||||
|
|
||||||
while (part.Sum(LongPath.GetFilesystemStringLength) > maxFilenameLength)
|
while (part.Sum(GetFilenameLength) > maxFilenameLength)
|
||||||
{
|
{
|
||||||
int maxLength = part.Max(p => p.Length);
|
int maxLength = part.Max(p => p.Length);
|
||||||
var maxEntry = part.First(p => p.Length == maxLength);
|
var maxEntry = part.First(p => p.Length == maxLength);
|
||||||
@ -173,6 +173,10 @@ namespace LibationFileManager.Templates
|
|||||||
return FileUtility.GetValidFilename(fullPath, replacements, fileExtension, returnFirstExisting);
|
return FileUtility.GetValidFilename(fullPath, replacements, fileExtension, returnFirstExisting);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int GetFilenameLength(string filename)
|
||||||
|
=> Configuration.Instance.BooksCanWrite255UnicodeChars ? filename.Length
|
||||||
|
: System.Text.Encoding.UTF8.GetByteCount(filename);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Organize template parts into directories. Any Extra slashes will be
|
/// Organize template parts into directories. Any Extra slashes will be
|
||||||
/// returned as empty directories and are taken care of by Path.Combine()
|
/// returned as empty directories and are taken care of by Path.Combine()
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
|
using LibationFileManager;
|
||||||
using LibationUiBase.Forms;
|
using LibationUiBase.Forms;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -95,6 +96,9 @@ public class ProcessQueueViewModel : ReactiveObject
|
|||||||
|
|
||||||
public bool QueueDownloadPdf(IList<LibraryBook> libraryBooks)
|
public bool QueueDownloadPdf(IList<LibraryBook> libraryBooks)
|
||||||
{
|
{
|
||||||
|
if (!IsBooksDirectoryValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
var needsPdf = libraryBooks.Where(lb => lb.NeedsPdfDownload()).ToArray();
|
var needsPdf = libraryBooks.Where(lb => lb.NeedsPdfDownload()).ToArray();
|
||||||
if (needsPdf.Length > 0)
|
if (needsPdf.Length > 0)
|
||||||
{
|
{
|
||||||
@ -107,6 +111,9 @@ public class ProcessQueueViewModel : ReactiveObject
|
|||||||
|
|
||||||
public bool QueueConvertToMp3(IList<LibraryBook> libraryBooks)
|
public bool QueueConvertToMp3(IList<LibraryBook> libraryBooks)
|
||||||
{
|
{
|
||||||
|
if (!IsBooksDirectoryValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
//Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing.
|
//Only Queue Liberated books for conversion. This isn't a perfect filter, but it's better than nothing.
|
||||||
var preLiberated = libraryBooks.Where(lb => !lb.AbsentFromLastScan && lb.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated && lb.Book.ContentType is DataLayer.ContentType.Product).ToArray();
|
var preLiberated = libraryBooks.Where(lb => !lb.AbsentFromLastScan && lb.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated && lb.Book.ContentType is DataLayer.ContentType.Product).ToArray();
|
||||||
if (preLiberated.Length > 0)
|
if (preLiberated.Length > 0)
|
||||||
@ -122,6 +129,9 @@ public class ProcessQueueViewModel : ReactiveObject
|
|||||||
|
|
||||||
public bool QueueDownloadDecrypt(IList<LibraryBook> libraryBooks)
|
public bool QueueDownloadDecrypt(IList<LibraryBook> libraryBooks)
|
||||||
{
|
{
|
||||||
|
if (!IsBooksDirectoryValid())
|
||||||
|
return false;
|
||||||
|
|
||||||
if (libraryBooks.Count == 1)
|
if (libraryBooks.Count == 1)
|
||||||
{
|
{
|
||||||
var item = libraryBooks[0];
|
var item = libraryBooks[0];
|
||||||
@ -157,6 +167,32 @@ public class ProcessQueueViewModel : ReactiveObject
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsBooksDirectoryValid()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(Configuration.Instance.Books))
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Error("Books location is not set in configuration.");
|
||||||
|
MessageBoxBase.Show(
|
||||||
|
"Please choose a \"Books location\" folder in the Settings menu.",
|
||||||
|
"Books Directory Not Set",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (AudibleFileStorage.BooksDirectory is null)
|
||||||
|
{
|
||||||
|
Serilog.Log.Logger.Error("Failed to create books directory: {@booksDir}", Configuration.Instance.Books);
|
||||||
|
MessageBoxBase.Show(
|
||||||
|
$"Libation was unable to create the \"Books location\" folder at:\n{Configuration.Instance.Books}\n\nPlease change the Books location in the settings menu.",
|
||||||
|
"Failed to Create Books Directory",
|
||||||
|
MessageBoxButtons.OK,
|
||||||
|
MessageBoxIcon.Error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsBookInQueue(LibraryBook libraryBook)
|
private bool IsBookInQueue(LibraryBook libraryBook)
|
||||||
=> Queue.FirstOrDefault(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId) is not ProcessBookViewModel entry ? false
|
=> Queue.FirstOrDefault(b => b?.LibraryBook?.Book?.AudibleProductId == libraryBook.Book.AudibleProductId) is not ProcessBookViewModel entry ? false
|
||||||
: entry.Status is ProcessBookStatus.Cancelled or ProcessBookStatus.Failed ? !Queue.RemoveCompleted(entry)
|
: entry.Status is ProcessBookStatus.Cancelled or ProcessBookStatus.Failed ? !Queue.RemoveCompleted(entry)
|
||||||
|
|||||||
@ -50,13 +50,13 @@ namespace LibationWinForms.Dialogs
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loFiDefaultsBtn_Click(object sender, EventArgs e)
|
private void loFiDefaultsBtn_Click(object sender, EventArgs e)
|
||||||
=> LoadTable(ReplacementCharacters.LoFiDefault.Replacements);
|
=> LoadTable(ReplacementCharacters.LoFiDefault(ntfs: true).Replacements);
|
||||||
|
|
||||||
private void defaultsBtn_Click(object sender, EventArgs e)
|
private void defaultsBtn_Click(object sender, EventArgs e)
|
||||||
=> LoadTable(ReplacementCharacters.Default.Replacements);
|
=> LoadTable(ReplacementCharacters.Default(ntfs: true).Replacements);
|
||||||
|
|
||||||
private void minDefaultBtn_Click(object sender, EventArgs e)
|
private void minDefaultBtn_Click(object sender, EventArgs e)
|
||||||
=> LoadTable(ReplacementCharacters.Barebones.Replacements);
|
=> LoadTable(ReplacementCharacters.Barebones(ntfs: true).Replacements);
|
||||||
|
|
||||||
|
|
||||||
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
|
||||||
|
|||||||
@ -28,161 +28,168 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
this.saveBtn = new System.Windows.Forms.Button();
|
saveBtn = new System.Windows.Forms.Button();
|
||||||
this.cancelBtn = new System.Windows.Forms.Button();
|
cancelBtn = new System.Windows.Forms.Button();
|
||||||
this.templateTb = new System.Windows.Forms.TextBox();
|
templateTb = new System.Windows.Forms.TextBox();
|
||||||
this.templateLbl = new System.Windows.Forms.Label();
|
templateLbl = new System.Windows.Forms.Label();
|
||||||
this.resetToDefaultBtn = new System.Windows.Forms.Button();
|
resetToDefaultBtn = new System.Windows.Forms.Button();
|
||||||
this.listView1 = new System.Windows.Forms.ListView();
|
listView1 = new System.Windows.Forms.ListView();
|
||||||
this.columnHeader1 = new System.Windows.Forms.ColumnHeader();
|
columnHeader1 = new System.Windows.Forms.ColumnHeader();
|
||||||
this.columnHeader2 = new System.Windows.Forms.ColumnHeader();
|
columnHeader2 = new System.Windows.Forms.ColumnHeader();
|
||||||
this.richTextBox1 = new System.Windows.Forms.RichTextBox();
|
richTextBox1 = new System.Windows.Forms.RichTextBox();
|
||||||
this.warningsLbl = new System.Windows.Forms.Label();
|
warningsLbl = new System.Windows.Forms.Label();
|
||||||
this.exampleLbl = new System.Windows.Forms.Label();
|
exampleLbl = new System.Windows.Forms.Label();
|
||||||
this.SuspendLayout();
|
llblGoToWiki = new System.Windows.Forms.LinkLabel();
|
||||||
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// saveBtn
|
// saveBtn
|
||||||
//
|
//
|
||||||
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
saveBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||||
this.saveBtn.Location = new System.Drawing.Point(714, 345);
|
saveBtn.Location = new System.Drawing.Point(714, 345);
|
||||||
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.saveBtn.Name = "saveBtn";
|
saveBtn.Name = "saveBtn";
|
||||||
this.saveBtn.Size = new System.Drawing.Size(88, 27);
|
saveBtn.Size = new System.Drawing.Size(88, 27);
|
||||||
this.saveBtn.TabIndex = 98;
|
saveBtn.TabIndex = 98;
|
||||||
this.saveBtn.Text = "Save";
|
saveBtn.Text = "Save";
|
||||||
this.saveBtn.UseVisualStyleBackColor = true;
|
saveBtn.UseVisualStyleBackColor = true;
|
||||||
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
|
saveBtn.Click += saveBtn_Click;
|
||||||
//
|
//
|
||||||
// cancelBtn
|
// cancelBtn
|
||||||
//
|
//
|
||||||
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
cancelBtn.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
|
||||||
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
|
||||||
this.cancelBtn.Location = new System.Drawing.Point(832, 345);
|
cancelBtn.Location = new System.Drawing.Point(832, 345);
|
||||||
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.cancelBtn.Name = "cancelBtn";
|
cancelBtn.Name = "cancelBtn";
|
||||||
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
|
cancelBtn.Size = new System.Drawing.Size(88, 27);
|
||||||
this.cancelBtn.TabIndex = 99;
|
cancelBtn.TabIndex = 99;
|
||||||
this.cancelBtn.Text = "Cancel";
|
cancelBtn.Text = "Cancel";
|
||||||
this.cancelBtn.UseVisualStyleBackColor = true;
|
cancelBtn.UseVisualStyleBackColor = true;
|
||||||
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
|
cancelBtn.Click += cancelBtn_Click;
|
||||||
//
|
//
|
||||||
// templateTb
|
// templateTb
|
||||||
//
|
//
|
||||||
this.templateTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
templateTb.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
templateTb.Location = new System.Drawing.Point(12, 27);
|
||||||
this.templateTb.Location = new System.Drawing.Point(12, 27);
|
templateTb.Name = "templateTb";
|
||||||
this.templateTb.Name = "templateTb";
|
templateTb.Size = new System.Drawing.Size(779, 23);
|
||||||
this.templateTb.Size = new System.Drawing.Size(779, 23);
|
templateTb.TabIndex = 1;
|
||||||
this.templateTb.TabIndex = 1;
|
templateTb.TextChanged += templateTb_TextChanged;
|
||||||
this.templateTb.TextChanged += new System.EventHandler(this.templateTb_TextChanged);
|
|
||||||
//
|
//
|
||||||
// templateLbl
|
// templateLbl
|
||||||
//
|
//
|
||||||
this.templateLbl.AutoSize = true;
|
templateLbl.AutoSize = true;
|
||||||
this.templateLbl.Location = new System.Drawing.Point(12, 9);
|
templateLbl.Location = new System.Drawing.Point(12, 9);
|
||||||
this.templateLbl.Name = "templateLbl";
|
templateLbl.Name = "templateLbl";
|
||||||
this.templateLbl.Size = new System.Drawing.Size(89, 15);
|
templateLbl.Size = new System.Drawing.Size(89, 15);
|
||||||
this.templateLbl.TabIndex = 0;
|
templateLbl.TabIndex = 0;
|
||||||
this.templateLbl.Text = "[template desc]";
|
templateLbl.Text = "[template desc]";
|
||||||
//
|
//
|
||||||
// resetToDefaultBtn
|
// resetToDefaultBtn
|
||||||
//
|
//
|
||||||
this.resetToDefaultBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
resetToDefaultBtn.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
|
||||||
this.resetToDefaultBtn.Location = new System.Drawing.Point(797, 26);
|
resetToDefaultBtn.Location = new System.Drawing.Point(797, 26);
|
||||||
this.resetToDefaultBtn.Name = "resetToDefaultBtn";
|
resetToDefaultBtn.Name = "resetToDefaultBtn";
|
||||||
this.resetToDefaultBtn.Size = new System.Drawing.Size(124, 23);
|
resetToDefaultBtn.Size = new System.Drawing.Size(124, 23);
|
||||||
this.resetToDefaultBtn.TabIndex = 2;
|
resetToDefaultBtn.TabIndex = 2;
|
||||||
this.resetToDefaultBtn.Text = "Reset to default";
|
resetToDefaultBtn.Text = "Reset to default";
|
||||||
this.resetToDefaultBtn.UseVisualStyleBackColor = true;
|
resetToDefaultBtn.UseVisualStyleBackColor = true;
|
||||||
this.resetToDefaultBtn.Click += new System.EventHandler(this.resetToDefaultBtn_Click);
|
resetToDefaultBtn.Click += resetToDefaultBtn_Click;
|
||||||
//
|
//
|
||||||
// listView1
|
// listView1
|
||||||
//
|
//
|
||||||
this.listView1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
listView1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
|
||||||
| System.Windows.Forms.AnchorStyles.Left)));
|
listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { columnHeader1, columnHeader2 });
|
||||||
this.listView1.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
|
listView1.FullRowSelect = true;
|
||||||
this.columnHeader1,
|
listView1.GridLines = true;
|
||||||
this.columnHeader2});
|
listView1.Location = new System.Drawing.Point(12, 56);
|
||||||
this.listView1.FullRowSelect = true;
|
listView1.MultiSelect = false;
|
||||||
this.listView1.GridLines = true;
|
listView1.Name = "listView1";
|
||||||
this.listView1.Location = new System.Drawing.Point(12, 56);
|
listView1.Size = new System.Drawing.Size(328, 283);
|
||||||
this.listView1.MultiSelect = false;
|
listView1.TabIndex = 3;
|
||||||
this.listView1.Name = "listView1";
|
listView1.UseCompatibleStateImageBehavior = false;
|
||||||
this.listView1.Size = new System.Drawing.Size(328, 283);
|
listView1.View = System.Windows.Forms.View.Details;
|
||||||
this.listView1.TabIndex = 3;
|
listView1.DoubleClick += listView1_DoubleClick;
|
||||||
this.listView1.UseCompatibleStateImageBehavior = false;
|
|
||||||
this.listView1.View = System.Windows.Forms.View.Details;
|
|
||||||
this.listView1.DoubleClick += new System.EventHandler(this.listView1_DoubleClick);
|
|
||||||
//
|
//
|
||||||
// columnHeader1
|
// columnHeader1
|
||||||
//
|
//
|
||||||
this.columnHeader1.Text = "Tag";
|
columnHeader1.Text = "Tag";
|
||||||
this.columnHeader1.Width = 137;
|
columnHeader1.Width = 137;
|
||||||
//
|
//
|
||||||
// columnHeader2
|
// columnHeader2
|
||||||
//
|
//
|
||||||
this.columnHeader2.Text = "Description";
|
columnHeader2.Text = "Description";
|
||||||
this.columnHeader2.Width = 170;
|
columnHeader2.Width = 170;
|
||||||
//
|
//
|
||||||
// richTextBox1
|
// richTextBox1
|
||||||
//
|
//
|
||||||
this.richTextBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
richTextBox1.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||||
| System.Windows.Forms.AnchorStyles.Left)
|
richTextBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
||||||
| System.Windows.Forms.AnchorStyles.Right)));
|
richTextBox1.Location = new System.Drawing.Point(346, 74);
|
||||||
this.richTextBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
|
richTextBox1.Name = "richTextBox1";
|
||||||
this.richTextBox1.Location = new System.Drawing.Point(346, 74);
|
richTextBox1.ReadOnly = true;
|
||||||
this.richTextBox1.Name = "richTextBox1";
|
richTextBox1.Size = new System.Drawing.Size(574, 185);
|
||||||
this.richTextBox1.ReadOnly = true;
|
richTextBox1.TabIndex = 5;
|
||||||
this.richTextBox1.Size = new System.Drawing.Size(574, 185);
|
richTextBox1.Text = "";
|
||||||
this.richTextBox1.TabIndex = 5;
|
|
||||||
this.richTextBox1.Text = "";
|
|
||||||
//
|
//
|
||||||
// warningsLbl
|
// warningsLbl
|
||||||
//
|
//
|
||||||
this.warningsLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
warningsLbl.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left;
|
||||||
this.warningsLbl.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
|
warningsLbl.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold);
|
||||||
this.warningsLbl.ForeColor = System.Drawing.Color.Firebrick;
|
warningsLbl.ForeColor = System.Drawing.Color.Firebrick;
|
||||||
this.warningsLbl.Location = new System.Drawing.Point(346, 262);
|
warningsLbl.Location = new System.Drawing.Point(346, 262);
|
||||||
this.warningsLbl.Name = "warningsLbl";
|
warningsLbl.Name = "warningsLbl";
|
||||||
this.warningsLbl.Size = new System.Drawing.Size(574, 77);
|
warningsLbl.Size = new System.Drawing.Size(574, 77);
|
||||||
this.warningsLbl.TabIndex = 6;
|
warningsLbl.TabIndex = 6;
|
||||||
this.warningsLbl.Text = "[warnings]";
|
warningsLbl.Text = "[warnings]";
|
||||||
//
|
//
|
||||||
// exampleLbl
|
// exampleLbl
|
||||||
//
|
//
|
||||||
this.exampleLbl.AutoSize = true;
|
exampleLbl.AutoSize = true;
|
||||||
this.exampleLbl.Location = new System.Drawing.Point(346, 56);
|
exampleLbl.Location = new System.Drawing.Point(346, 56);
|
||||||
this.exampleLbl.Name = "exampleLbl";
|
exampleLbl.Name = "exampleLbl";
|
||||||
this.exampleLbl.Size = new System.Drawing.Size(55, 15);
|
exampleLbl.Size = new System.Drawing.Size(54, 15);
|
||||||
this.exampleLbl.TabIndex = 4;
|
exampleLbl.TabIndex = 4;
|
||||||
this.exampleLbl.Text = "Example:";
|
exampleLbl.Text = "Example:";
|
||||||
|
//
|
||||||
|
// llblGoToWiki
|
||||||
|
//
|
||||||
|
llblGoToWiki.AutoSize = true;
|
||||||
|
llblGoToWiki.Location = new System.Drawing.Point(12, 357);
|
||||||
|
llblGoToWiki.Name = "llblGoToWiki";
|
||||||
|
llblGoToWiki.Size = new System.Drawing.Size(229, 15);
|
||||||
|
llblGoToWiki.TabIndex = 100;
|
||||||
|
llblGoToWiki.TabStop = true;
|
||||||
|
llblGoToWiki.Text = "Read about naming templates on the Wiki";
|
||||||
|
llblGoToWiki.LinkClicked += llblGoToWiki_LinkClicked;
|
||||||
//
|
//
|
||||||
// EditTemplateDialog
|
// EditTemplateDialog
|
||||||
//
|
//
|
||||||
this.AcceptButton = this.saveBtn;
|
AcceptButton = saveBtn;
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
|
||||||
this.CancelButton = this.cancelBtn;
|
CancelButton = cancelBtn;
|
||||||
this.ClientSize = new System.Drawing.Size(933, 388);
|
ClientSize = new System.Drawing.Size(933, 388);
|
||||||
this.Controls.Add(this.exampleLbl);
|
Controls.Add(llblGoToWiki);
|
||||||
this.Controls.Add(this.warningsLbl);
|
Controls.Add(exampleLbl);
|
||||||
this.Controls.Add(this.richTextBox1);
|
Controls.Add(warningsLbl);
|
||||||
this.Controls.Add(this.listView1);
|
Controls.Add(richTextBox1);
|
||||||
this.Controls.Add(this.resetToDefaultBtn);
|
Controls.Add(listView1);
|
||||||
this.Controls.Add(this.templateLbl);
|
Controls.Add(resetToDefaultBtn);
|
||||||
this.Controls.Add(this.templateTb);
|
Controls.Add(templateLbl);
|
||||||
this.Controls.Add(this.cancelBtn);
|
Controls.Add(templateTb);
|
||||||
this.Controls.Add(this.saveBtn);
|
Controls.Add(cancelBtn);
|
||||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
Controls.Add(saveBtn);
|
||||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
|
||||||
this.MaximizeBox = false;
|
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||||
this.MinimizeBox = false;
|
MaximizeBox = false;
|
||||||
this.Name = "EditTemplateDialog";
|
MinimizeBox = false;
|
||||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
Name = "EditTemplateDialog";
|
||||||
this.Text = "Edit Template";
|
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||||
this.Load += new System.EventHandler(this.EditTemplateDialog_Load);
|
Text = "Edit Template";
|
||||||
this.ResumeLayout(false);
|
Load += EditTemplateDialog_Load;
|
||||||
this.PerformLayout();
|
ResumeLayout(false);
|
||||||
|
PerformLayout();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,5 +205,6 @@
|
|||||||
private System.Windows.Forms.RichTextBox richTextBox1;
|
private System.Windows.Forms.RichTextBox richTextBox1;
|
||||||
private System.Windows.Forms.Label warningsLbl;
|
private System.Windows.Forms.Label warningsLbl;
|
||||||
private System.Windows.Forms.Label exampleLbl;
|
private System.Windows.Forms.Label exampleLbl;
|
||||||
|
private System.Windows.Forms.LinkLabel llblGoToWiki;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -12,7 +12,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
{
|
{
|
||||||
private void resetTextBox(string value) => this.templateTb.Text = value;
|
private void resetTextBox(string value) => this.templateTb.Text = value;
|
||||||
private Configuration config { get; } = Configuration.Instance;
|
private Configuration config { get; } = Configuration.Instance;
|
||||||
private ITemplateEditor templateEditor { get;}
|
private ITemplateEditor templateEditor { get; }
|
||||||
|
|
||||||
public EditTemplateDialog()
|
public EditTemplateDialog()
|
||||||
{
|
{
|
||||||
@ -150,5 +150,11 @@ namespace LibationWinForms.Dialogs
|
|||||||
templateTb.Text = text.Insert(selStart, itemText);
|
templateTb.Text = text.Insert(selStart, itemText);
|
||||||
templateTb.SelectionStart = selStart + itemText.Length;
|
templateTb.SelectionStart = selStart + itemText.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void llblGoToWiki_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
|
||||||
|
{
|
||||||
|
Go.To.Url(@"ht" + "tps://github.com/rmcrackan/Libation/blob/master/Documentation/NamingTemplates.md");
|
||||||
|
e.Link.Visited = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,64 @@
|
|||||||
<root>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
<xsd:element name="root" msdata:IsDataSet="true">
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
|||||||
@ -83,9 +83,11 @@ namespace LibationWinForms.Dialogs
|
|||||||
if (lb.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated)
|
if (lb.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Liberated)
|
||||||
await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated));
|
await Task.Run(() => lb.UpdateBookStatus(LiberatedStatus.Liberated));
|
||||||
|
|
||||||
|
tokenSource.Token.ThrowIfCancellationRequested();
|
||||||
this.Invoke(FileFound, this, book);
|
this.Invoke(FileFound, this, book);
|
||||||
}
|
}
|
||||||
catch(Exception ex)
|
catch (OperationCanceledException) { }
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Serilog.Log.Error(ex, "Error adding found audiobook file to Libation. {@audioFile}", book);
|
Serilog.Log.Error(ex, "Error adding found audiobook file to Libation. {@audioFile}", book);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace LibationWinForms.Dialogs
|
namespace LibationWinForms.Dialogs
|
||||||
{
|
{
|
||||||
public partial class SettingsDialog
|
public partial class SettingsDialog
|
||||||
@ -55,7 +56,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
},
|
},
|
||||||
Configuration.KnownDirectories.UserProfile,
|
Configuration.KnownDirectories.UserProfile,
|
||||||
"Books");
|
"Books");
|
||||||
booksSelectControl.SelectDirectory(config.Books.PathWithoutPrefix);
|
booksSelectControl.SelectDirectory(config.Books?.PathWithoutPrefix ?? "");
|
||||||
|
|
||||||
saveEpisodesToSeriesFolderCbox.Checked = config.SavePodcastsToParentFolder;
|
saveEpisodesToSeriesFolderCbox.Checked = config.SavePodcastsToParentFolder;
|
||||||
overwriteExistingCbox.Checked = config.OverwriteExisting;
|
overwriteExistingCbox.Checked = config.OverwriteExisting;
|
||||||
@ -63,7 +64,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
gridFontScaleFactorTbar.Value = scaleFactorToLinearRange(config.GridFontScaleFactor);
|
gridFontScaleFactorTbar.Value = scaleFactorToLinearRange(config.GridFontScaleFactor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Save_Important(Configuration config)
|
private bool Save_Important(Configuration config)
|
||||||
{
|
{
|
||||||
var newBooks = booksSelectControl.SelectedDirectory;
|
var newBooks = booksSelectControl.SelectedDirectory;
|
||||||
|
|
||||||
@ -73,19 +74,29 @@ namespace LibationWinForms.Dialogs
|
|||||||
if (string.IsNullOrWhiteSpace(newBooks))
|
if (string.IsNullOrWhiteSpace(newBooks))
|
||||||
{
|
{
|
||||||
validationError("Cannot set Books Location to blank", "Location is blank");
|
validationError("Cannot set Books Location to blank", "Location is blank");
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
LongPath lonNewBooks = newBooks;
|
||||||
|
if (!Directory.Exists(lonNewBooks))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(lonNewBooks);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
validationError($"Error creating Books Location:\r\n{ex.Message}", "Error creating directory");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
LongPath lonNewBooks = newBooks;
|
|
||||||
if (!Directory.Exists(lonNewBooks))
|
|
||||||
Directory.CreateDirectory(lonNewBooks);
|
|
||||||
|
|
||||||
config.Books = newBooks;
|
config.Books = newBooks;
|
||||||
|
|
||||||
{
|
{
|
||||||
var logLevelOld = config.LogLevel;
|
var logLevelOld = config.LogLevel;
|
||||||
var logLevelNew = (Serilog.Events.LogEventLevel)loggingLevelCb.SelectedItem;
|
var logLevelNew = (loggingLevelCb.SelectedItem as Serilog.Events.LogEventLevel?) ?? Serilog.Events.LogEventLevel.Information;
|
||||||
|
|
||||||
config.LogLevel = logLevelNew;
|
config.LogLevel = logLevelNew;
|
||||||
|
|
||||||
@ -97,9 +108,9 @@ namespace LibationWinForms.Dialogs
|
|||||||
config.SavePodcastsToParentFolder = saveEpisodesToSeriesFolderCbox.Checked;
|
config.SavePodcastsToParentFolder = saveEpisodesToSeriesFolderCbox.Checked;
|
||||||
config.OverwriteExisting = overwriteExistingCbox.Checked;
|
config.OverwriteExisting = overwriteExistingCbox.Checked;
|
||||||
|
|
||||||
|
config.CreationTime = (creationTimeCb.SelectedItem as EnumDisplay<Configuration.DateTimeSource>)?.Value ?? Configuration.DateTimeSource.File;
|
||||||
config.CreationTime = ((EnumDisplay<Configuration.DateTimeSource>)creationTimeCb.SelectedItem).Value;
|
config.LastWriteTime = (lastWriteTimeCb.SelectedItem as EnumDisplay<Configuration.DateTimeSource>)?.Value ?? Configuration.DateTimeSource.File;
|
||||||
config.LastWriteTime = ((EnumDisplay<Configuration.DateTimeSource>)lastWriteTimeCb.SelectedItem).Value;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int scaleFactorToLinearRange(float scaleFactor)
|
private static int scaleFactorToLinearRange(float scaleFactor)
|
||||||
|
|||||||
@ -43,7 +43,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
private void saveBtn_Click(object sender, EventArgs e)
|
private void saveBtn_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Save_Important(config);
|
if (!Save_Important(config)) return;
|
||||||
Save_ImportLibrary(config);
|
Save_ImportLibrary(config);
|
||||||
Save_DownloadDecrypt(config);
|
Save_DownloadDecrypt(config);
|
||||||
Save_AudioSettings(config);
|
Save_AudioSettings(config);
|
||||||
|
|||||||
@ -16,7 +16,8 @@ namespace LibationWinForms
|
|||||||
private void Configure_ScanManual()
|
private void Configure_ScanManual()
|
||||||
{
|
{
|
||||||
this.Load += refreshImportMenu;
|
this.Load += refreshImportMenu;
|
||||||
AccountsSettingsPersister.Saved += refreshImportMenu;
|
AccountsSettingsPersister.Saved += (_, _) => Invoke(refreshImportMenu, null, null);
|
||||||
|
locateAudiobooksToolStripMenuItem.ToolTipText = Configuration.GetHelpText("LocateAudiobooks");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshImportMenu(object _, EventArgs __)
|
private void refreshImportMenu(object _, EventArgs __)
|
||||||
@ -96,7 +97,16 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
private void locateAudiobooksToolStripMenuItem_Click(object sender, EventArgs e)
|
private void locateAudiobooksToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
new LocateAudiobooksDialog().ShowDialog();
|
var result = MessageBox.Show(
|
||||||
|
this,
|
||||||
|
Configuration.GetHelpText(nameof(LocateAudiobooksDialog)),
|
||||||
|
"Locate Previously-Liberated Audiobook Files",
|
||||||
|
MessageBoxButtons.OKCancel,
|
||||||
|
MessageBoxIcon.Information,
|
||||||
|
MessageBoxDefaultButton.Button1);
|
||||||
|
|
||||||
|
if (result is DialogResult.OK)
|
||||||
|
new LocateAudiobooksDialog().ShowDialog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,9 +6,29 @@ namespace LibationWinForms
|
|||||||
{
|
{
|
||||||
public partial class Form1
|
public partial class Form1
|
||||||
{
|
{
|
||||||
private void Configure_Settings() { }
|
private void Configure_Settings()
|
||||||
|
{
|
||||||
|
Shown += FormShown_Settings;
|
||||||
|
}
|
||||||
|
|
||||||
private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog().ShowDialog();
|
private void FormShown_Settings(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (LibationFileManager.AudibleFileStorage.BooksDirectory is null)
|
||||||
|
{
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
this,
|
||||||
|
"Please set a valid Books location in the settings dialog.",
|
||||||
|
"Books Directory Not Set",
|
||||||
|
MessageBoxButtons.OKCancel,
|
||||||
|
MessageBoxIcon.Warning,
|
||||||
|
MessageBoxDefaultButton.Button1);
|
||||||
|
|
||||||
|
if (result is DialogResult.OK)
|
||||||
|
new SettingsDialog().ShowDialog(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog().ShowDialog();
|
||||||
|
|
||||||
private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog();
|
private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog();
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,7 @@ namespace LibationWinForms
|
|||||||
FileUtility.SaferMoveToValidPath(
|
FileUtility.SaferMoveToValidPath(
|
||||||
e.SettingsFilePath,
|
e.SettingsFilePath,
|
||||||
e.SettingsFilePath,
|
e.SettingsFilePath,
|
||||||
ReplacementCharacters.Barebones,
|
Configuration.Instance.ReplacementCharacters,
|
||||||
"bak");
|
"bak");
|
||||||
|
|
||||||
AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
AudibleApiStorage.EnsureAccountsSettingsFileExists();
|
||||||
|
|||||||
@ -148,7 +148,10 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setupDialog.IsNewUser)
|
if (setupDialog.IsNewUser)
|
||||||
|
{
|
||||||
Configuration.SetLibationFiles(defaultLibationFilesDir);
|
Configuration.SetLibationFiles(defaultLibationFilesDir);
|
||||||
|
config.Books = Configuration.DefaultBooksDirectory;
|
||||||
|
}
|
||||||
else if (setupDialog.IsReturningUser)
|
else if (setupDialog.IsReturningUser)
|
||||||
{
|
{
|
||||||
var libationFilesDialog = new LibationFilesDialog();
|
var libationFilesDialog = new LibationFilesDialog();
|
||||||
@ -175,16 +178,11 @@ namespace LibationWinForms
|
|||||||
CancelInstallation();
|
CancelInstallation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
config.Books = Configuration.DefaultBooksDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
// INIT DEFAULT SETTINGS
|
if (!config.LibationSettingsAreValid)
|
||||||
// if 'new user' was clicked, or if 'returning user' chose new install: show basic settings dialog
|
CancelInstallation();
|
||||||
config.Books ??= Configuration.DefaultBooksDirectory;
|
|
||||||
|
|
||||||
if (config.LibationSettingsAreValid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CancelInstallation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>migrations which require Forms or are long-running</summary>
|
/// <summary>migrations which require Forms or are long-running</summary>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
using AudibleUtilities;
|
using AudibleUtilities;
|
||||||
|
using Dinah.Core;
|
||||||
using Dinah.Core.StepRunner;
|
using Dinah.Core.StepRunner;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using LibationWinForms.Dialogs;
|
using LibationWinForms.Dialogs;
|
||||||
@ -163,7 +164,7 @@ namespace LibationWinForms
|
|||||||
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
var books = DbContexts.GetLibrary_Flat_NoTracking();
|
||||||
if (books.Count == 0) return true;
|
if (books.Count == 0) return true;
|
||||||
|
|
||||||
var firstAuthor = getFirstAuthor();
|
var firstAuthor = getFirstAuthor()?.SurroundWithQuotes();
|
||||||
if (firstAuthor == null) return true;
|
if (firstAuthor == null) return true;
|
||||||
|
|
||||||
if (!ProceedMessageBox("You can filter the grid entries by searching", "Searching"))
|
if (!ProceedMessageBox("You can filter the grid entries by searching", "Searching"))
|
||||||
@ -196,7 +197,7 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
private async Task<bool> ShowQuickFilters()
|
private async Task<bool> ShowQuickFilters()
|
||||||
{
|
{
|
||||||
var firstAuthor = getFirstAuthor();
|
var firstAuthor = getFirstAuthor()?.SurroundWithQuotes();
|
||||||
if (firstAuthor == null) return true;
|
if (firstAuthor == null) return true;
|
||||||
|
|
||||||
if (!ProceedMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters"))
|
if (!ProceedMessageBox("Queries that you perform regularly can be added to 'Quick Filters'", "Quick Filters"))
|
||||||
|
|||||||
@ -12,9 +12,9 @@ namespace FileUtilityTests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetSafePath
|
public class GetSafePath
|
||||||
{
|
{
|
||||||
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
|
static readonly ReplacementCharacters Default = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault;
|
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones;
|
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
public void null_path_throws() => Assert.ThrowsException<ArgumentNullException>(() => FileUtility.GetSafePath(null, Default));
|
public void null_path_throws() => Assert.ThrowsException<ArgumentNullException>(() => FileUtility.GetSafePath(null, Default));
|
||||||
@ -98,9 +98,9 @@ namespace FileUtilityTests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetSafeFileName
|
public class GetSafeFileName
|
||||||
{
|
{
|
||||||
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
|
static readonly ReplacementCharacters Default = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault;
|
static readonly ReplacementCharacters LoFiDefault = ReplacementCharacters.LoFiDefault(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones;
|
static readonly ReplacementCharacters Barebones = ReplacementCharacters.Barebones(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
|
|
||||||
// needs separate method. middle null param not running correctly in TestExplorer when used in DataRow()
|
// needs separate method. middle null param not running correctly in TestExplorer when used in DataRow()
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@ -193,7 +193,7 @@ namespace FileUtilityTests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetValidFilename
|
public class GetValidFilename
|
||||||
{
|
{
|
||||||
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
|
static ReplacementCharacters Replacements = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
// dot-files
|
// dot-files
|
||||||
|
|||||||
@ -23,8 +23,17 @@ namespace TemplatesTests
|
|||||||
|
|
||||||
public static class Shared
|
public static class Shared
|
||||||
{
|
{
|
||||||
|
[System.Runtime.CompilerServices.ModuleInitializer]
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
var thisDir = Path.GetDirectoryName(Environment.ProcessPath);
|
||||||
|
LibationFileManager.Configuration.SetLibationFiles(thisDir);
|
||||||
|
if (!LibationFileManager.Configuration.Instance.LibationSettingsAreValid)
|
||||||
|
LibationFileManager.Configuration.Instance.Books = Path.Combine(thisDir, "Books");
|
||||||
|
}
|
||||||
|
|
||||||
public static LibraryBookDto GetLibraryBook()
|
public static LibraryBookDto GetLibraryBook()
|
||||||
=> GetLibraryBook([new SeriesDto("Sherlock Holmes", 1, "B08376S3R2")]);
|
=> GetLibraryBook([new SeriesDto("Sherlock Holmes", "1", "B08376S3R2")]);
|
||||||
|
|
||||||
public static LibraryBookDto GetLibraryBook(IEnumerable<SeriesDto> series)
|
public static LibraryBookDto GetLibraryBook(IEnumerable<SeriesDto> series)
|
||||||
=> new()
|
=> new()
|
||||||
@ -66,7 +75,7 @@ namespace TemplatesTests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class getFileNamingTemplate
|
public class getFileNamingTemplate
|
||||||
{
|
{
|
||||||
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
|
static ReplacementCharacters Replacements = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow(null)]
|
[DataRow(null)]
|
||||||
@ -367,12 +376,13 @@ namespace TemplatesTests
|
|||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow("<series>", "Series A, Series B, Series C")]
|
[DataRow("<series>", "Series A, Series B, Series C, Series D")]
|
||||||
[DataRow("<series[]>", "Series A, Series B, Series C")]
|
[DataRow("<series[]>", "Series A, Series B, Series C, Series D")]
|
||||||
[DataRow("<series[max(1)]>", "Series A")]
|
[DataRow("<series[max(1)]>", "Series A")]
|
||||||
[DataRow("<series[max(2)]>", "Series A, Series B")]
|
[DataRow("<series[max(2)]>", "Series A, Series B")]
|
||||||
[DataRow("<series[max(3)]>", "Series A, Series B, Series C")]
|
[DataRow("<series[max(3)]>", "Series A, Series B, Series C")]
|
||||||
[DataRow("<series[format({N}, {#}, {ID}) separator(; )]>", "Series A, 1, B1; Series B, 6, B2; Series C, 2, B3")]
|
[DataRow("<series[max(4)]>", "Series A, Series B, Series C, Series D")]
|
||||||
|
[DataRow("<series[format({N}, {#}, {ID}) separator(; )]>", "Series A, 1, B1; Series B, 6, B2; Series C, 2, B3; Series D, 1-5, B4")]
|
||||||
[DataRow("<series[format({N}, {#}, {ID}) separator(; ) max(3)]>", "Series A, 1, B1; Series B, 6, B2; Series C, 2, B3")]
|
[DataRow("<series[format({N}, {#}, {ID}) separator(; ) max(3)]>", "Series A, 1, B1; Series B, 6, B2; Series C, 2, B3")]
|
||||||
[DataRow("<series[format({N}, {#}, {ID}) separator(; ) max(2)]>", "Series A, 1, B1; Series B, 6, B2")]
|
[DataRow("<series[format({N}, {#}, {ID}) separator(; ) max(2)]>", "Series A, 1, B1; Series B, 6, B2")]
|
||||||
[DataRow("<first series>", "Series A")]
|
[DataRow("<first series>", "Series A")]
|
||||||
@ -383,9 +393,10 @@ namespace TemplatesTests
|
|||||||
var bookDto = GetLibraryBook();
|
var bookDto = GetLibraryBook();
|
||||||
bookDto.Series =
|
bookDto.Series =
|
||||||
[
|
[
|
||||||
new("Series A", 1, "B1"),
|
new("Series A", "1", "B1"),
|
||||||
new("Series B", 6, "B2"),
|
new("Series B", "6", "B2"),
|
||||||
new("Series C", 2, "B3")
|
new("Series C", "2", "B3"),
|
||||||
|
new("Series D", "1-5", "B4"),
|
||||||
];
|
];
|
||||||
|
|
||||||
Templates.TryGetTemplate<Templates.FileTemplate>(template, out var fileTemplate).Should().BeTrue();
|
Templates.TryGetTemplate<Templates.FileTemplate>(template, out var fileTemplate).Should().BeTrue();
|
||||||
@ -451,7 +462,7 @@ namespace Templates_Other
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetFilePath
|
public class GetFilePath
|
||||||
{
|
{
|
||||||
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
|
static ReplacementCharacters Replacements = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow(@"C:\foo\bar", @"\\Folder\<title>\[<id>]\\", @"C:\foo\bar\Folder\my_ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\[ID123456].txt", PlatformID.Win32NT)]
|
[DataRow(@"C:\foo\bar", @"\\Folder\<title>\[<id>]\\", @"C:\foo\bar\Folder\my_ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\[ID123456].txt", PlatformID.Win32NT)]
|
||||||
@ -887,7 +898,7 @@ namespace Templates_ChapterFile_Tests
|
|||||||
[TestClass]
|
[TestClass]
|
||||||
public class GetFilename
|
public class GetFilename
|
||||||
{
|
{
|
||||||
static readonly ReplacementCharacters Default = ReplacementCharacters.Default;
|
static readonly ReplacementCharacters Default = ReplacementCharacters.Default(Environment.OSVersion.Platform == PlatformID.Win32NT);
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow("[<id>] <ch# 0> of <ch count> - <ch title>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\[asin] 06 of 10 - chap.txt", PlatformID.Win32NT)]
|
[DataRow("[<id>] <ch# 0> of <ch count> - <ch title>", @"C:\foo\", "txt", 6, 10, "chap", @"C:\foo\[asin] 06 of 10 - chap.txt", PlatformID.Win32NT)]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user