Compare commits

...

28 Commits

Author SHA1 Message Date
Robert McRackan
d64e043fe8 #367 : New template option "year": year published to audible 2022-10-21 13:41:44 -04:00
Robert McRackan
99564d9c25 update dependencies 2022-10-18 08:33:43 -04:00
rmcrackan
29bccd3e33 Update InstallOnMac.md
Gatekeeper instructions
2022-10-05 22:25:40 -04:00
Robert McRackan
20f65f6534 Fix description of poorly named AutoDownloadEpisodes 2022-09-28 15:49:16 -04:00
Robert McRackan
8ca72b2e2d incr ver 2022-09-28 13:27:04 -04:00
Robert McRackan
75429f288f update dependencies 2022-09-28 13:25:41 -04:00
Robert McRackan
d1bb921346 Cache assembly fetches/resolution so that repeat errors aren't clogging the log 2022-09-24 09:48:44 -04:00
rmcrackan
b979b6ddad Update README.md 2022-09-24 07:54:48 -04:00
rmcrackan
4eba41ddbb Update GettingStarted.md 2022-09-23 21:48:16 -04:00
rmcrackan
418f5062ff Update README.md
remove unofficial linux instructions
2022-09-23 21:46:45 -04:00
rmcrackan
f736f7f909 Update GettingStarted.md
remove unofficial linux instructions
2022-09-23 21:46:06 -04:00
rmcrackan
96ead28246 Update Advanced.md
remove unofficial linux instructions
2022-09-23 21:45:28 -04:00
Robert McRackan
34bad7a53d fix string template 2022-09-19 14:08:46 -04:00
Robert McRackan
7ac1fff3a0 update dependencies 2022-09-09 11:53:59 -04:00
Robert McRackan
a4c5c53df3 incr ver 2022-09-09 11:10:56 -04:00
Robert McRackan
87db5cfd94 revert accidental re-name of button text 2022-09-09 11:04:14 -04:00
Robert McRackan
85e7bbf366 Chardonnay readonly textboxes should be grey (as they are in Classic) 2022-09-07 13:29:41 -04:00
Robert McRackan
c55c5fac23 typo 2022-09-06 12:47:44 -04:00
Robert McRackan
e25e2f7211 Update documentaion for macos 2022-08-31 15:44:12 -04:00
Robert McRackan
f310d583d8 Bug fix #364 - app was crashing on attempt to download PDF to which the user no longer had ownership. Eg: returned or Plus catalog 2022-08-29 15:05:56 -04:00
Robert McRackan
f05465b29b incr ver 2022-08-18 13:38:23 -04:00
rmcrackan
959e31972e Merge pull request #363 from Mbucari/master
Change assembly loadig
2022-08-18 13:36:08 -04:00
Michael Bucari-Tovo
17181811f0 Remove assembly hot loading 2022-08-18 11:21:40 -06:00
Michael Bucari-Tovo
6d2624d52b Fix comment 2022-08-18 10:59:37 -06:00
Michael Bucari-Tovo
9dd5940c8c Remove trailing wild 2022-08-18 10:59:00 -06:00
Michael Bucari-Tovo
1927d19961 comments 2022-08-18 10:47:53 -06:00
Michael Bucari-Tovo
09cc838bb4 Checks 2022-08-18 10:45:07 -06:00
Michael Bucari-Tovo
8af4c71101 Change assembly loadig 2022-08-18 10:29:30 -06:00
38 changed files with 397 additions and 263 deletions

View File

@@ -8,7 +8,6 @@
# Advanced: Table of Contents
- [Files and folders](#files-and-folders)
- [Linux and Mac (unofficial)](#linux-and-mac)
- [Settings](#settings)
- [Custom File Naming](#custom-file-naming)
- [Command Line Interface](#command-line-interface)
@@ -25,10 +24,6 @@ To make upgrades and reinstalls easier, Libation separates all of its responsibi
* The last important folder is the "books location." This is where Libation looks for your downloaded and decrypted books. This is how it knows which books still need to be downloaded. The Audible id must be somewhere in the book's file or folder name for Libation to detect your downloaded book.
### Linux and Mac
Although Libation only currently officially supports Windows, some users have had success with WINE. ([Linux](https://github.com/rmcrackan/Libation/issues/28#issuecomment-890594158), [OSX Crossover and WINE](https://github.com/rmcrackan/Libation/issues/150#issuecomment-1004918592), [Linux and WINE](https://github.com/rmcrackan/Libation/issues/28#issuecomment-1161111014))
### Settings
* Allow Libation to fix up audiobook metadata. After decrypting a title, Libation attempts to fix details like chapters and cover art. Some power users and/or control freaks prefer to manage this themselves. By unchecking this setting, Libation will only decrypt the book and will leave metadata as-is, warts and all.

View File

@@ -22,7 +22,12 @@
### Installation
To install Libation, extract the zip file to a folder, for example `C:\Libation`, and then run Libation.exe from that folder to begin the configuration process and configure your account(s).
* Windows
Extract the zip file to a folder and then run `Libation.exe` from inside of that folder.
* [Ubuntu Linux (beta)](InstallOnLinux.md)
* [MacOS (beta)](InstallOnMac.md)
### Create Accounts

View File

@@ -1,4 +1,11 @@
# Run Libation on Ubuntu
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
# Run Libation on Ubuntu (Beta)
This walkthrough should get you up and running with Libation on your Ubuntu machine.
Some limitations of the linux release are:

View File

@@ -0,0 +1,40 @@
## [Download Libation](https://github.com/rmcrackan/Libation/releases/latest)
### If you found this useful, tell a friend. If you found this REALLY useful, you can click here to [PalPal.me](https://paypal.me/mcrackan?locale.x=en_us)
...or just tell more friends. As long as I'm maintaining this software, it will remain **free** and **open source**.
# Run Libation on MacOS (Beta)
This walkthrough should get you up and running with Libation on your Mac.
## Install Libation
- Download latest MacOS zip to downloads folder
- Extract and rename folder to Libation
- in terminal type cd and then drag your folder of libation to terminal so it looks like `cd/users/YourName/Downloads/Libation`
- Type following commands
```console
chmod +x ./Libation
sudo spctl --add --label "Libation" ./Libation
./Libation
```
## Trouble with Gatekeeper?
If Gatekeeper is giving you trouble with Libation:
Disable the block
`sudo spctl --master-disable`
Launch Libation and login, etc. and allow the rules to update then re-enable the block.
`sudo spctl --master-enable`
Once Gatekeeper reenabled, you can open Libation again without it being blocked.
Thanks [joseph-holland](https://github.com/rmcrackan/Libation/issues/327#issuecomment-1268993349)!
Report bugs to https://github.com/rmcrackan/Libation/issues

View File

@@ -14,7 +14,6 @@
- [Getting started](Documentation/GettingStarted.md)
- [Download Libation](Documentation/GettingStarted.md#download-libation-1)
- [Installation](Documentation/GettingStarted.md#installation)
- [Installation on Ubuntu](Source/LibationAvalonia/README.md)
- [Create Accounts](Documentation/GettingStarted.md#create-accounts)
- [Import your library](Documentation/GettingStarted.md#import-your-library)
- [Download your books -- DRM-free!](Documentation/GettingStarted.md#download-your-books----drm-free)
@@ -28,7 +27,6 @@
- [Filters](Documentation/SearchingAndFiltering.md#filters)
- [Advanced](Documentation/Advanced.md)
- [Files and folders](Documentation/Advanced.md#files-and-folders)
- [Linux and Mac (unofficial)](Documentation/Advanced.md#linux-and-mac)
- [Settings](Documentation/Advanced.md#settings)
- [Custom File Naming](Documentation/Advanced.md#custom-file-naming)
- [Command Line Interface](Documentation/Advanced.md#command-line-interface)
@@ -55,7 +53,7 @@
### The bad
* Windows only
* Only fully supported in Windows. (Mac and Linux are in beta)
* Large file size
* Made by a programmer, not a designer so the goals are function rather than beauty. And it shows

View File

@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AAXClean.Codecs" Version="0.2.12" />
<PackageReference Include="AAXClean.Codecs" Version="0.2.14" />
</ItemGroup>
<ItemGroup>

View File

@@ -2,10 +2,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>8.4.2.1</Version>
<Version>8.5.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="2.0.0" />
<PackageReference Include="Octokit" Version="4.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="28.0.1" />
<PackageReference Include="CsvHelper" Version="29.0.0" />
<PackageReference Include="NPOI" Version="2.5.6" />
</ItemGroup>

View File

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

View File

@@ -10,14 +10,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="5.1.0.1" />
<PackageReference Include="Dinah.EntityFrameworkCore" Version="5.0.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
<PackageReference Include="Dinah.Core" Version="5.2.1.1" />
<PackageReference Include="Dinah.EntityFrameworkCore" Version="5.0.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.7">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="6.0.10">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>

View File

@@ -24,7 +24,7 @@ namespace FileLiberator
{
OnBegin(libraryBook);
try
try
{
var proposedDownloadFilePath = getProposedDownloadFilePath(libraryBook);
var actualDownloadedFilePath = await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
@@ -32,13 +32,22 @@ namespace FileLiberator
libraryBook.Book.UpdatePdfStatus(result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated);
return result;
}
return result;
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "Error downloading PDF");
var result = new StatusHandler();
result.AddError($"Error downloading PDF. See log for details. Error summary: {ex.Message}");
return result;
}
finally
{
OnCompleted(libraryBook);
}
}
}
}
private static string getProposedDownloadFilePath(LibraryBook libraryBook)
{

View File

@@ -31,6 +31,7 @@ namespace FileLiberator
AudibleProductId = libraryBook.Book.AudibleProductId,
Title = libraryBook.Book.Title ?? "",
Locale = libraryBook.Book.Locale,
YearPublished = libraryBook.Book.DatePublished?.Year,
Authors = libraryBook.Book.Authors.Select(c => c.Name).ToList(),

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="5.1.0.1" />
<PackageReference Include="Dinah.Core" Version="5.2.1.1" />
<PackageReference Include="Polly" Version="7.2.3" />
</ItemGroup>

View File

@@ -63,11 +63,11 @@
<TrimmableAssembly Include="Avalonia.Themes.Default" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.17" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.17" />
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.17" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.17" />
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
</ItemGroup>
<ItemGroup>

View File

@@ -9,4 +9,8 @@
<SolidColorBrush x:Key="ProcessQueueBookDefaultBrush" Color="{StaticResource SystemAltHighColor}" />
<SolidColorBrush x:Key="ProcessQueueBookBorderBrush" Color="Gray" />
</Styles.Resources>
<Style Selector="TextBox[IsReadOnly=true]">
<Setter Property="Background" Value="LightGray" />
<Setter Property="CaretBrush" Value="#00000000" />
</Style>
</Styles>

View File

@@ -66,7 +66,7 @@
<DataGridTextColumn
Width="2*"
Binding="{Binding AccountId, Mode=TwoWay}"
Header="Autible&#xa;email/login"/>
Header="Audible&#xa;email/login"/>
<DataGridTemplateColumn Width="Auto" Header="Locale">
<DataGridTemplateColumn.CellTemplate>

View File

@@ -138,6 +138,7 @@ namespace LibationAvalonia.Dialogs
AudibleProductId = "123456789",
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
Locale = "us",
YearPublished = 2017,
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
Narrators = new List<string> { "Stephen Fry" },
SeriesName = "Sherlock Holmes",

View File

@@ -124,11 +124,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.17" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.17" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.17" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.17" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.17" />
<PackageReference Include="Avalonia" Version="0.10.18" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="0.10.18" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.18" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.18" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.18" />
<PackageReference Include="XamlNameReferenceGenerator" Version="1.3.4" />
</ItemGroup>
@@ -145,7 +145,7 @@
</ItemGroup>
<Target Name="SpicNSpan" AfterTargets="Clean">
<Target Name="SpicNSpan" AfterTargets="Clean">
<!-- Remove obj folder -->
<RemoveDir Directories="$(BaseIntermediateOutputPath)" />
<!-- Remove bin folder -->

View File

@@ -7,6 +7,7 @@ using Avalonia.Media;
using Avalonia.Media.Imaging;
using DataLayer;
using Dinah.Core;
using Dinah.Core.ErrorHandling;
using FileLiberator;
using LibationFileManager;
using ReactiveUI;
@@ -289,25 +290,36 @@ namespace LibationAvalonia.ViewModels
Logger.Info($"{((Processable)sender).Name} Step, Completed: {libraryBook.Book}");
UnlinkProcessable((Processable)sender);
if (Processes.Count > 0)
{
NextProcessable();
LinkProcessable(CurrentProcessable);
var result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true);
if (Processes.Count == 0)
{
Completed?.Invoke(this, EventArgs.Empty);
return;
}
if (result.HasErrors)
{
foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed"))
Logger.Error(errorMessage);
NextProcessable();
LinkProcessable(CurrentProcessable);
Completed?.Invoke(this, EventArgs.Empty);
}
}
else
StatusHandler result;
try
{
Completed?.Invoke(this, EventArgs.Empty);
}
}
result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true);
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, $"{nameof(Processable_Completed)} error");
result = new StatusHandler();
result.AddError($"{nameof(Processable_Completed)} error. See log for details. Error summary: {ex.Message}");
}
if (result.HasErrors)
{
foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed"))
Logger.Error(errorMessage);
Completed?.Invoke(this, EventArgs.Empty);
}
}
#endregion

View File

@@ -259,7 +259,8 @@ namespace LibationFileManager
}
}
[Description("Auto download episodes? After scan, download new books in 'checked' accounts.")]
[Description("Auto download books? After scan, download new books in 'checked' accounts.")]
// poorly named setting. Should just be 'AutoDownload'. It is NOT episode specific
public bool AutoDownloadEpisodes
{
get => persistentDictionary.GetNonString<bool>(nameof(AutoDownloadEpisodes));

View File

@@ -21,7 +21,7 @@ namespace LibationFileManager
private static IInteropFunctions _create(params object[] values)
=> InteropFunctionsType is null ? new NullInteropFunctions()
//: values is null || values.Length == 0 ? Activator.CreateInstance(InteropFunctionsType) as IInteropFunctions
//: values is null || values.Length == 0 ? Activator.CreateInstance(InteropFunctionsType) as IInteropFunctions
: Activator.CreateInstance(InteropFunctionsType, values) as IInteropFunctions;
#region load types
@@ -32,7 +32,7 @@ namespace LibationFileManager
: Configuration.IsMacOs ? a => Path.GetFileName(a).StartsWithInsensitive("mac") || Path.GetFileName(a).StartsWithInsensitive("osx")
: _ => false;
private const string CONFIG_APP_ENDING = "ConfigApp.exe";
private const string CONFIG_APP_ENDING = "ConfigApp.dll";
private static List<ProcessModule> ModuleList { get; } = new();
static InteropFactory()
{
@@ -41,16 +41,26 @@ namespace LibationFileManager
// nothing to load
if (configApp is null)
{
Serilog.Log.Logger.Error($"Unable to locate *{CONFIG_APP_ENDING}");
return;
}
// runs the exe and gets the exe's loaded modules
ModuleList = LoadModuleList(Path.GetFileNameWithoutExtension(configApp))
.OrderBy(x => x.ModuleName)
.ToList();
/*
* Commented code used to locate assemblies from the *ConfigApp.exe's module list.
* Use this method to locate dependencies when they are not in Libation's program files directory.
#if DEBUG
// runs the exe and gets the exe's loaded modules
ModuleList = LoadModuleList(Path.GetFileNameWithoutExtension(configApp))
.OrderBy(x => x.ModuleName)
.ToList();
#endif
*/
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
var configAppAssembly = Assembly.LoadFrom(Path.ChangeExtension(configApp, "dll"));
var configAppAssembly = Assembly.LoadFrom(configApp);
var type = typeof(IInteropFunctions);
InteropFunctionsType = configAppAssembly
.GetTypes()
@@ -60,67 +70,100 @@ namespace LibationFileManager
{
var here = Path.GetDirectoryName(Environment.ProcessPath);
// find '*ConfigApp.exe' files
var exes =
// find '*ConfigApp.dll' files
var appName =
Directory.EnumerateFiles(here, $"*{CONFIG_APP_ENDING}", SearchOption.TopDirectoryOnly)
// sanity check. shouldn't ever be true
.Except(new[] { Environment.ProcessPath })
.Where(exe =>
// has a corresponding dll
File.Exists(Path.ChangeExtension(exe, "dll"))
&& MatchesOS(exe)
)
.ToList();
var exeName = exes.FirstOrDefault();
return exeName;
.FirstOrDefault(exe => MatchesOS(exe));
return appName;
}
private static List<ProcessModule> LoadModuleList(string exeName)
{
var proc = new Process
{
StartInfo = new()
{
FileName = exeName,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
}
};
/*
* Use this method to locate dependencies when they are not in Libation's program files directory.
*
private static List<ProcessModule> LoadModuleList(string exeName)
{
var proc = new Process
{
StartInfo = new()
{
FileName = exeName,
RedirectStandardInput = true,
RedirectStandardOutput = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false
}
};
var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
var waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
proc.OutputDataReceived += (_, _) => waitHandle.Set();
proc.Start();
proc.BeginOutputReadLine();
proc.OutputDataReceived += (_, _) => waitHandle.Set();
proc.Start();
proc.BeginOutputReadLine();
//Let the win process know we're ready to receive its standard output
proc.StandardInput.WriteLine();
//Let the win process know we're ready to receive its standard output
proc.StandardInput.WriteLine();
if (!waitHandle.WaitOne(2000))
throw new Exception("Failed to start program");
if (!waitHandle.WaitOne(2000))
throw new Exception("Failed to start program");
//The win process has finished loading and is now waiting inside Main().
//Copy it process module list.
var modules = proc.Modules.Cast<ProcessModule>().ToList();
//The win process has finished loading and is now waiting inside Main().
//Copy it process module list.
var modules = proc.Modules.Cast<ProcessModule>().ToList();
//Let the win process know we're done reading its module list
proc.StandardInput.WriteLine();
//Let the win process know we're done reading its module list
proc.StandardInput.WriteLine();
return modules;
}
return modules;
}
*/
private static Dictionary<string, Assembly> lowEffortCache { get; } = new();
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
// e.g. "System.Windows.Forms, Version=6.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
var asmName = args.Name.Split(',')[0];
var asmName = args.Name.Split(',')[0] + ".dll";
var here = Path.GetDirectoryName(Environment.ProcessPath);
// `First` instead of `FirstOrDefault`. If it's not present we're going to fail anyway. May as well be here
var module = ModuleList.First(m => m.ModuleName.StartsWith(asmName));
var key = $"{asmName}|{here}";
return Assembly.LoadFrom(module.FileName);
if (lowEffortCache.TryGetValue(key, out var value))
return value;
var assembly = CurrentDomain_AssemblyResolve_internal(asmName: asmName, here: here);
lowEffortCache[key] = assembly;
//Let the runtime handle any dll not found exceptions.
if (assembly is null)
Serilog.Log.Logger.Error($"Unable to load module {args.Name}");
return assembly;
}
private static Assembly CurrentDomain_AssemblyResolve_internal(string asmName, string here)
{
/*
* Commented code used to locate assemblies from the *ConfigApp.exe's module list.
* Use this method to locate dependencies when they are not in Libation's program files directory.
#if DEBUG
var modulePath = ModuleList.SingleOrDefault(m => m.ModuleName.EqualsInsensitive(asmName))?.FileName;
#else
*/
// find the requested assembly in the program files directory
var modulePath =
Directory.EnumerateFiles(here, asmName, SearchOption.TopDirectoryOnly)
.SingleOrDefault();
//#endif
if (modulePath is null)
return null;
return Assembly.LoadFrom(modulePath);
}
#endregion

View File

@@ -6,7 +6,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.3.0" />
<PackageReference Include="Serilog.Exceptions" Version="8.4.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -9,6 +9,7 @@ namespace LibationFileManager
public string AudibleProductId { get; set; }
public string Title { get; set; }
public string Locale { get; set; }
public int? YearPublished { get; set; }
public IEnumerable<string> Authors { get; set; }
public string AuthorNames => string.Join(", ", Authors);

View File

@@ -39,6 +39,7 @@ namespace LibationFileManager
public static TemplateTags Channels { get; } = new TemplateTags("channels", "Number of audio channels");
public static TemplateTags Account { get; } = new TemplateTags("account", "Audible account of this book");
public static TemplateTags Locale { get; } = new TemplateTags("locale", "Region/country");
public static TemplateTags YearPublished { get; } = new TemplateTags("year", "Year published");
// Special case. Isn't mapped to a replacement in Templates.cs
// Included here for display by EditTemplateDialog

View File

@@ -142,8 +142,9 @@ namespace LibationFileManager
fileNamingTemplate.AddParameterReplacement(TemplateTags.Channels, libraryBookDto.Channels);
fileNamingTemplate.AddParameterReplacement(TemplateTags.Account, libraryBookDto.Account);
fileNamingTemplate.AddParameterReplacement(TemplateTags.Locale, libraryBookDto.Locale);
fileNamingTemplate.AddParameterReplacement(TemplateTags.YearPublished, libraryBookDto.YearPublished?.ToString() ?? "1900");
return fileNamingTemplate;
return fileNamingTemplate;
}
#endregion

View File

@@ -28,134 +28,134 @@
/// </summary>
private void InitializeComponent()
{
this.cancelBtn = new System.Windows.Forms.Button();
this.saveBtn = new System.Windows.Forms.Button();
this.dataGridView1 = new System.Windows.Forms.DataGridView();
this.DeleteAccount = new System.Windows.Forms.DataGridViewButtonColumn();
this.ExportAccount = new System.Windows.Forms.DataGridViewButtonColumn();
this.LibraryScan = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.AccountId = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Locale = new System.Windows.Forms.DataGridViewComboBoxColumn();
this.AccountName = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.importBtn = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
this.SuspendLayout();
//
// cancelBtn
//
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cancelBtn.Location = new System.Drawing.Point(832, 479);
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.cancelBtn.Name = "cancelBtn";
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
this.cancelBtn.TabIndex = 2;
this.cancelBtn.Text = "Cancel";
this.cancelBtn.UseVisualStyleBackColor = true;
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
//
// saveBtn
//
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.saveBtn.Location = new System.Drawing.Point(714, 479);
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.saveBtn.Name = "saveBtn";
this.saveBtn.Size = new System.Drawing.Size(88, 27);
this.saveBtn.TabIndex = 1;
this.saveBtn.Text = "pub ";
this.saveBtn.UseVisualStyleBackColor = true;
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
//
// dataGridView1
//
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
this.cancelBtn = new System.Windows.Forms.Button();
this.saveBtn = new System.Windows.Forms.Button();
this.dataGridView1 = new System.Windows.Forms.DataGridView();
this.DeleteAccount = new System.Windows.Forms.DataGridViewButtonColumn();
this.ExportAccount = new System.Windows.Forms.DataGridViewButtonColumn();
this.LibraryScan = new System.Windows.Forms.DataGridViewCheckBoxColumn();
this.AccountId = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.Locale = new System.Windows.Forms.DataGridViewComboBoxColumn();
this.AccountName = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.importBtn = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit();
this.SuspendLayout();
//
// cancelBtn
//
this.cancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.cancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cancelBtn.Location = new System.Drawing.Point(832, 479);
this.cancelBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.cancelBtn.Name = "cancelBtn";
this.cancelBtn.Size = new System.Drawing.Size(88, 27);
this.cancelBtn.TabIndex = 2;
this.cancelBtn.Text = "Cancel";
this.cancelBtn.UseVisualStyleBackColor = true;
this.cancelBtn.Click += new System.EventHandler(this.cancelBtn_Click);
//
// saveBtn
//
this.saveBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.saveBtn.Location = new System.Drawing.Point(714, 479);
this.saveBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.saveBtn.Name = "saveBtn";
this.saveBtn.Size = new System.Drawing.Size(88, 27);
this.saveBtn.TabIndex = 1;
this.saveBtn.Text = "Save";
this.saveBtn.UseVisualStyleBackColor = true;
this.saveBtn.Click += new System.EventHandler(this.saveBtn_Click);
//
// dataGridView1
//
this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.dataGridView1.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.dataGridView1.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.AllCells;
this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.DeleteAccount,
this.ExportAccount,
this.LibraryScan,
this.AccountId,
this.Locale,
this.AccountName});
this.dataGridView1.Location = new System.Drawing.Point(14, 14);
this.dataGridView1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.dataGridView1.MultiSelect = false;
this.dataGridView1.Name = "dataGridView1";
this.dataGridView1.Size = new System.Drawing.Size(905, 458);
this.dataGridView1.TabIndex = 0;
this.dataGridView1.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView1_CellContentClick);
this.dataGridView1.DefaultValuesNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(this.dataGridView1_DefaultValuesNeeded);
//
// DeleteAccount
//
this.DeleteAccount.HeaderText = "Delete";
this.DeleteAccount.Name = "DeleteAccount";
this.DeleteAccount.ReadOnly = true;
this.DeleteAccount.Text = "x";
this.DeleteAccount.Width = 46;
//
// ExportAccount
//
this.ExportAccount.HeaderText = "Export";
this.ExportAccount.Name = "ExportAccount";
this.ExportAccount.Text = "Export to audible-cli";
this.ExportAccount.Width = 47;
//
// LibraryScan
//
this.LibraryScan.HeaderText = "Include in library scan?";
this.LibraryScan.Name = "LibraryScan";
this.LibraryScan.Width = 94;
//
// AccountId
//
this.AccountId.HeaderText = "Audible email/login";
this.AccountId.Name = "AccountId";
this.AccountId.Width = 125;
//
// Locale
//
this.Locale.HeaderText = "Locale";
this.Locale.Name = "Locale";
this.Locale.Width = 47;
//
// AccountName
//
this.AccountName.HeaderText = "Account nickname (optional)";
this.AccountName.Name = "AccountName";
this.AccountName.Width = 170;
//
// importBtn
//
this.importBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.importBtn.Location = new System.Drawing.Point(14, 480);
this.importBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.importBtn.Name = "importBtn";
this.importBtn.Size = new System.Drawing.Size(156, 27);
this.importBtn.TabIndex = 1;
this.importBtn.Text = "Import from audible-cli";
this.importBtn.UseVisualStyleBackColor = true;
this.importBtn.Click += new System.EventHandler(this.importBtn_Click);
//
// AccountsDialog
//
this.AcceptButton = this.saveBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cancelBtn;
this.ClientSize = new System.Drawing.Size(933, 519);
this.Controls.Add(this.dataGridView1);
this.Controls.Add(this.importBtn);
this.Controls.Add(this.saveBtn);
this.Controls.Add(this.cancelBtn);
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.Name = "AccountsDialog";
this.Text = "Audible Accounts";
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
this.ResumeLayout(false);
this.dataGridView1.Location = new System.Drawing.Point(14, 14);
this.dataGridView1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.dataGridView1.MultiSelect = false;
this.dataGridView1.Name = "dataGridView1";
this.dataGridView1.Size = new System.Drawing.Size(905, 458);
this.dataGridView1.TabIndex = 0;
this.dataGridView1.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView1_CellContentClick);
this.dataGridView1.DefaultValuesNeeded += new System.Windows.Forms.DataGridViewRowEventHandler(this.dataGridView1_DefaultValuesNeeded);
//
// DeleteAccount
//
this.DeleteAccount.HeaderText = "Delete";
this.DeleteAccount.Name = "DeleteAccount";
this.DeleteAccount.ReadOnly = true;
this.DeleteAccount.Text = "x";
this.DeleteAccount.Width = 46;
//
// ExportAccount
//
this.ExportAccount.HeaderText = "Export";
this.ExportAccount.Name = "ExportAccount";
this.ExportAccount.Text = "Export to audible-cli";
this.ExportAccount.Width = 47;
//
// LibraryScan
//
this.LibraryScan.HeaderText = "Include in library scan?";
this.LibraryScan.Name = "LibraryScan";
this.LibraryScan.Width = 94;
//
// AccountId
//
this.AccountId.HeaderText = "Audible email/login";
this.AccountId.Name = "AccountId";
this.AccountId.Width = 125;
//
// Locale
//
this.Locale.HeaderText = "Locale";
this.Locale.Name = "Locale";
this.Locale.Width = 47;
//
// AccountName
//
this.AccountName.HeaderText = "Account nickname (optional)";
this.AccountName.Name = "AccountName";
this.AccountName.Width = 170;
//
// importBtn
//
this.importBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.importBtn.Location = new System.Drawing.Point(14, 480);
this.importBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.importBtn.Name = "importBtn";
this.importBtn.Size = new System.Drawing.Size(156, 27);
this.importBtn.TabIndex = 1;
this.importBtn.Text = "Import from audible-cli";
this.importBtn.UseVisualStyleBackColor = true;
this.importBtn.Click += new System.EventHandler(this.importBtn_Click);
//
// AccountsDialog
//
this.AcceptButton = this.saveBtn;
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.cancelBtn;
this.ClientSize = new System.Drawing.Size(933, 519);
this.Controls.Add(this.dataGridView1);
this.Controls.Add(this.importBtn);
this.Controls.Add(this.saveBtn);
this.Controls.Add(this.cancelBtn);
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.Name = "AccountsDialog";
this.Text = "Audible Accounts";
((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit();
this.ResumeLayout(false);
}

View File

@@ -76,7 +76,8 @@ namespace LibationWinForms.Dialogs
AudibleProductId = "123456789",
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
Locale = "us",
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
YearPublished = 2017,
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
Narrators = new List<string> { "Stephen Fry" },
SeriesName = "Sherlock Holmes",
SeriesNumber = "1",

View File

@@ -80,7 +80,7 @@ namespace LibationWinForms
var confirmationResult = MessageBoxLib.ShowConfirmationDialog(
visibleLibraryBooks,
$"Are you sure you want to replace tags in {0}?",
"Are you sure you want to replace tags in {0}?",
"Replace tags?");
if (confirmationResult != DialogResult.Yes)

View File

@@ -43,8 +43,8 @@
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.4" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="5.2.1.1" />
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.5" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="5.2.3.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -9,6 +9,7 @@ using System.Windows.Forms;
using ApplicationServices;
using DataLayer;
using Dinah.Core;
using Dinah.Core.ErrorHandling;
using Dinah.Core.WindowsDesktop.Drawing;
using FileLiberator;
using LibationFileManager;
@@ -279,36 +280,47 @@ namespace LibationWinForms.ProcessQueue
updateBookInfo();
}
private async void Processable_Completed(object sender, LibraryBook libraryBook)
{
Logger.Info($"{((Processable)sender).Name} Step, Completed: {libraryBook.Book}");
UnlinkProcessable((Processable)sender);
private async void Processable_Completed(object sender, LibraryBook libraryBook)
{
Logger.Info($"{((Processable)sender).Name} Step, Completed: {libraryBook.Book}");
UnlinkProcessable((Processable)sender);
if (Processes.Count > 0)
{
NextProcessable();
LinkProcessable(CurrentProcessable);
var result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true);
if (Processes.Count == 0)
{
Completed?.Invoke(this, EventArgs.Empty);
return;
}
if (result.HasErrors)
{
foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed"))
Logger.Error(errorMessage);
NextProcessable();
LinkProcessable(CurrentProcessable);
Completed?.Invoke(this, EventArgs.Empty);
}
}
else
{
Completed?.Invoke(this, EventArgs.Empty);
}
}
StatusHandler result;
try
{
result = await CurrentProcessable.ProcessSingleAsync(libraryBook, validate: true);
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, $"{nameof(Processable_Completed)} error");
#endregion
result = new StatusHandler();
result.AddError($"{nameof(Processable_Completed)} error. See log for details. Error summary: {ex.Message}");
}
#region Failure Handler
if (result.HasErrors)
{
foreach (var errorMessage in result.Errors.Where(e => e != "Validation failed"))
Logger.Error(errorMessage);
private ProcessBookResult showRetry(LibraryBook libraryBook)
Completed?.Invoke(this, EventArgs.Empty);
}
}
#endregion
#region Failure Handler
private ProcessBookResult showRetry(LibraryBook libraryBook)
{
Logger.Error("ERROR. All books have not been processed. Most recent book: processing failed");

View File

@@ -32,7 +32,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="5.2.1.1" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="5.2.3.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -17,7 +17,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="5.1.0.1" />
<PackageReference Include="Dinah.Core" Version="5.2.1.1" />
</ItemGroup>
</Project>

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />

View File

@@ -7,7 +7,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="coverlet.collector" Version="3.1.2">

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="coverlet.collector" Version="3.1.2">

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />
<PackageReference Include="coverlet.collector" Version="3.1.2">

View File

@@ -29,7 +29,8 @@ namespace TemplatesTests
AudibleProductId = "asin",
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
Locale = "us",
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
YearPublished = 2017,
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
Narrators = new List<string> { "Stephen Fry" },
SeriesName = seriesName ?? "",
SeriesNumber = "1",
@@ -87,7 +88,8 @@ namespace TemplatesTests
[DataRow("f", @"C:\foo\bar", "ext", @"C:\foo\bar\f.ext")]
[DataRow("<id>", @"C:\foo\bar", "ext", @"C:\foo\bar\asin.ext")]
[DataRow("<bitrate> - <samplerate> - <channels>", @"C:\foo\bar", "ext", @"C:\foo\bar\128 - 44100 - 2.ext")]
public void Tests(string template, string dirFullPath, string extension, string expected)
[DataRow("<year> - <channels>", @"C:\foo\bar", "ext", @"C:\foo\bar\2017 - 2.ext")]
public void Tests(string template, string dirFullPath, string extension, string expected)
=> Templates.getFileNamingTemplate(GetLibraryBook(), template, dirFullPath, extension)
.GetFilePath(Replacements)
.PathWithoutPrefix

View File

@@ -8,7 +8,7 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.10" />
<PackageReference Include="MSTest.TestFramework" Version="2.2.10" />