Compare commits

...

20 Commits

Author SHA1 Message Date
Robert
c7e844a54c incr ver 2026-02-03 09:38:30 -05:00
rmcrackan
178cc82d65 Merge pull request #1587 from Mbucari/master
Documentation and bug fixes
2026-02-03 09:05:06 -05:00
Michael Bucari-Tovo
054f7437d1 Fix importer NRE 2026-02-02 16:02:44 -07:00
Mbucari
99051b6975 Merge branch 'rmcrackan:master' into master 2026-01-31 23:52:05 -07:00
Robert
eed7f1811b incr ver 2026-01-31 23:33:29 -05:00
rmcrackan
33cd6b8639 Merge pull request #1585 from thoughtbox/patch-2
Fix typos
2026-01-31 23:31:16 -05:00
rmcrackan
06014f467e Merge pull request #1581 from scagood/patch-1
Remove progress bar in non interactive mode (#1481)
2026-01-31 23:29:04 -05:00
Tor Houghton
b2eef18217 Fix typos
buit-in -> built-in
broswer -> browser
2026-01-31 22:19:53 +01:00
Robert
443d1f64ca incr ver 2026-01-31 12:36:46 -05:00
Robert
c43e88d269 verbose null checking to debug #1578 2026-01-31 12:36:29 -05:00
Michael Bucari-Tovo
dc1919f411 Update AAXClean to 3.0.2 and fix deprecation warnings 2026-01-30 14:46:11 -07:00
Mbucari
fdfae2f806 Merge branch 'rmcrackan:master' into master 2026-01-30 14:42:13 -07:00
Sebastian Good
5c56e1d39b Correct small spelling mistake 2026-01-30 16:12:48 +00:00
Sebastian Good
b39e2c3e0b Remove progress bar in non interactive mode (#1481) 2026-01-30 16:12:44 +00:00
Mbucari
d58f5abe35 Add Troubleshooting link to Advanced topics
Added 'Troubleshooting' link to the Advanced section.
2026-01-27 14:58:37 -07:00
Mbucari
d957d6d5d7 Update troubleshooting guide with Hangover app details
Added troubleshooting information for the Hangover app and SQLite Error 10.
2026-01-27 14:53:08 -07:00
Michael Bucari-Tovo
cb159336a6 Enable NTRs on main form and fix resulting warnings. 2026-01-27 12:42:21 -07:00
Michael Bucari-Tovo
807bb56c49 Refactor Chardonnay startup to prevent loading library twice. 2026-01-27 12:31:17 -07:00
Mbucari
3d56554aa5 Merge branch 'rmcrackan:master' into master 2026-01-27 12:29:03 -07:00
Michael Bucari-Tovo
471fd1e757 Remove unwanted meta box from dash files 2026-01-22 16:33:12 -07:00
38 changed files with 205 additions and 128 deletions

View File

@@ -77,7 +77,10 @@ export default defineConfig({
{
text: "Advanced",
collapsed: false,
items: [{ text: "Advanced Topics", link: "/docs/advanced/advanced" }],
items: [
{ text: "Advanced Topics", link: "/docs/advanced/advanced" },
{ text: "Troubleshooting", link: "/docs/advanced/troubleshoot" }
],
},
{
text: "Development",

View File

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

View File

@@ -1,4 +1,5 @@
using AAXClean;
using Mpeg4Lib;
using System;
using System.IO;
using System.Linq;
@@ -9,7 +10,7 @@ namespace AaxDecrypter
{
public abstract class AaxcDownloadConvertBase : AudiobookDownloadBase
{
public event EventHandler<AppleTags>? RetrievedMetadata;
public event EventHandler<MetadataItems>? RetrievedMetadata;
public Mp4File? AaxFile { get; private set; }
protected Mp4Operation? AaxConversion { get; set; }
@@ -21,8 +22,8 @@ namespace AaxDecrypter
public override void SetCoverArt(byte[] coverArt)
{
base.SetCoverArt(coverArt);
if (coverArt is not null && AaxFile?.AppleTags is not null)
AaxFile.AppleTags.Cover = coverArt;
if (coverArt is not null && AaxFile?.MetadataItems is not null)
AaxFile.MetadataItems.Cover = coverArt;
}
public override async Task CancelAsync()
@@ -42,8 +43,10 @@ namespace AaxDecrypter
var keyIds = keys.Select(k => new Guid(k.KeyPart1, bigEndian: true)).ToArray();
var dash = new DashFile(InputFileStream);
var kidIndex = Array.IndexOf(keyIds, dash.Tenc.DefaultKID);
if (dash.Tenc is null)
throw new InvalidOperationException("The DASH file does not contain 'tenc' box, indicating that it is unencrypted.");
var kidIndex = Array.IndexOf(keyIds, dash.Tenc.DefaultKID);
if (kidIndex == -1)
throw new InvalidOperationException($"None of the {keyIds.Length} key IDs match the dash file's default KeyID of {dash.Tenc.DefaultKID}");
@@ -52,6 +55,11 @@ namespace AaxDecrypter
var key = keys[kidIndex].KeyPart2 ?? throw new InvalidOperationException($"{nameof(DownloadOptions.DecryptionKeys)} for '{DownloadOptions.InputType}' must have a non-null decryption key (KeyPart2).");
dash.SetDecryptionKey(keyId, key);
WriteKeyFile($"KeyId={Convert.ToHexString(keyId)}{Environment.NewLine}Key={Convert.ToHexString(key)}");
//Remove meta box containing DRM info
if (DownloadOptions.FixupFile && dash.Moov.GetChild<Mpeg4Lib.Boxes.MetaBox>() is { } meta)
dash.Moov.Children.Remove(meta);
return dash;
}
else if (DownloadOptions.InputType is FileType.Aax)
@@ -85,55 +93,55 @@ namespace AaxDecrypter
{
AaxFile = Open();
RetrievedMetadata?.Invoke(this, AaxFile.AppleTags);
RetrievedMetadata?.Invoke(this, AaxFile.MetadataItems);
if (DownloadOptions.StripUnabridged)
{
AaxFile.AppleTags.Title = AaxFile.AppleTags.TitleSansUnabridged;
AaxFile.AppleTags.Album = AaxFile.AppleTags.Album?.Replace(" (Unabridged)", "");
AaxFile.MetadataItems.Title = AaxFile.MetadataItems.TitleSansUnabridged;
AaxFile.MetadataItems.Album = AaxFile.MetadataItems.Album?.Replace(" (Unabridged)", "");
}
if (DownloadOptions.FixupFile)
{
if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Narrator))
AaxFile.AppleTags.AppleListBox.EditOrAddTag("©wrt", AaxFile.AppleTags.Narrator);
if (!string.IsNullOrWhiteSpace(AaxFile.MetadataItems.Narrator))
AaxFile.MetadataItems.AppleListBox.EditOrAddTag("©wrt", AaxFile.MetadataItems.Narrator);
if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Copyright))
AaxFile.AppleTags.Copyright = AaxFile.AppleTags.Copyright.Replace("(P)", "℗").Replace("&#169;", "©");
if (!string.IsNullOrWhiteSpace(AaxFile.MetadataItems.Copyright))
AaxFile.MetadataItems.Copyright = AaxFile.MetadataItems.Copyright.Replace("(P)", "℗").Replace("&#169;", "©");
//Add audiobook shelf tags
//https://github.com/advplyr/audiobookshelf/issues/1794#issuecomment-1565050213
const string tagDomain = "com.pilabor.tone";
AaxFile.AppleTags.Title = DownloadOptions.Title;
AaxFile.MetadataItems.Title = DownloadOptions.Title;
if (DownloadOptions.Subtitle is string subtitle)
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "SUBTITLE", subtitle);
AaxFile.MetadataItems.AppleListBox.EditOrAddFreeformTag(tagDomain, "SUBTITLE", subtitle);
if (DownloadOptions.Publisher is string publisher)
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "PUBLISHER", publisher);
AaxFile.MetadataItems.AppleListBox.EditOrAddFreeformTag(tagDomain, "PUBLISHER", publisher);
if (DownloadOptions.Language is string language)
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "LANGUAGE", language);
AaxFile.MetadataItems.AppleListBox.EditOrAddFreeformTag(tagDomain, "LANGUAGE", language);
if (DownloadOptions.AudibleProductId is string asin)
{
AaxFile.AppleTags.Asin = asin;
AaxFile.AppleTags.AppleListBox.EditOrAddTag("asin", asin);
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_ASIN", asin);
AaxFile.MetadataItems.Asin = asin;
AaxFile.MetadataItems.AppleListBox.EditOrAddTag("asin", asin);
AaxFile.MetadataItems.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_ASIN", asin);
}
if (DownloadOptions.SeriesName is string series)
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "SERIES", series);
AaxFile.MetadataItems.AppleListBox.EditOrAddFreeformTag(tagDomain, "SERIES", series);
if (DownloadOptions.SeriesNumber is string part)
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "PART", part);
AaxFile.MetadataItems.AppleListBox.EditOrAddFreeformTag(tagDomain, "PART", part);
}
OnRetrievedTitle(AaxFile.AppleTags.TitleSansUnabridged);
OnRetrievedAuthors(AaxFile.AppleTags.FirstAuthor);
OnRetrievedNarrators(AaxFile.AppleTags.Narrator);
OnRetrievedCoverArt(AaxFile.AppleTags.Cover);
OnRetrievedTitle(AaxFile.MetadataItems.TitleSansUnabridged);
OnRetrievedAuthors(AaxFile.MetadataItems.FirstAuthor);
OnRetrievedNarrators(AaxFile.MetadataItems.Narrator);
OnRetrievedCoverArt(AaxFile.MetadataItems.Cover);
OnInitialized();
return !IsCanceled;

View File

@@ -1,6 +1,7 @@
using AAXClean;
using AAXClean.Codecs;
using FileManager;
using Mpeg4Lib;
using System;
using System.IO;
using System.Threading.Tasks;

View File

@@ -1,5 +1,5 @@
using AAXClean;
using Dinah.Core;
using Dinah.Core;
using Mpeg4Lib;
using System.IO;
using System.Text;

View File

@@ -1,4 +1,5 @@
using AAXClean;
using Mpeg4Lib;
using System;
#nullable enable

View File

@@ -1,5 +1,5 @@
using AAXClean;
using AAXClean.Codecs;
using AAXClean.Codecs;
using Mpeg4Lib;
using NAudio.Lame;
using System;
using System.Linq;
@@ -11,7 +11,7 @@ namespace AaxDecrypter
{
private const string TagDomain = "com.pilabor.tone";
public static void ConfigureLameOptions(
Mp4File mp4File,
Mpeg4File mp4File,
LameConfig lameConfig,
bool downsample,
bool matchSourceBitrate,
@@ -47,9 +47,9 @@ namespace AaxDecrypter
}
//Setup metadata tags
lameConfig.ID3 = mp4File.AppleTags.ToIDTags();
lameConfig.ID3 = mp4File.MetadataItems.ToIDTags();
if (mp4File.AppleTags.AppleListBox.GetFreeformTagString(TagDomain, "SUBTITLE") is string subtitle)
if (mp4File.MetadataItems.AppleListBox.GetFreeformTagString(TagDomain, "SUBTITLE") is string subtitle)
lameConfig.ID3.Subtitle = subtitle;
if (chapters?.Count > 0)
@@ -59,12 +59,12 @@ namespace AaxDecrypter
}
//Copy over all other freeform tags
foreach (var t in mp4File.AppleTags.AppleListBox.Tags.OfType<Mpeg4Lib.Boxes.FreeformTagBox>())
foreach (var t in mp4File.MetadataItems.AppleListBox.Tags.OfType<Mpeg4Lib.Boxes.FreeformTagBox>())
{
if (t.Name?.Name is string name &&
t.Mean?.ReverseDnsDomain is string domain &&
!lameConfig.ID3.UserDefinedText.ContainsKey(name) &&
mp4File.AppleTags.AppleListBox.GetFreeformTagString(domain, name) is string tagStr &&
mp4File.MetadataItems.AppleListBox.GetFreeformTagString(domain, name) is string tagStr &&
!string.IsNullOrWhiteSpace(tagStr))
lameConfig.ID3.UserDefinedText.Add(name, tagStr);
}

View File

@@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Version>13.1.5.1</Version>
<Version>13.1.8.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="14.0.0" />

View File

@@ -95,7 +95,7 @@ public class LibraryBookImporter : ItemsImporterBase
{
//If the entire library was loaded, we can be sure that all existing LibraryBooks have their Book property populated.
//Find LibraryBooks which have a Book but weren't found in the import, and mark them as absent.
foreach (var absentBook in allInScannedAccounts.Where(lb => !uniqueImportItems.ContainsKey(lb.Book.AudibleProductId)))
foreach (var absentBook in allInScannedAccounts.Where(lb => lb.Book?.AudibleProductId is not string asin || !uniqueImportItems.ContainsKey(asin)))
absentBook.AbsentFromLastScan = true;
}
else

View File

@@ -2,8 +2,8 @@
using DataLayer;
using FileManager;
using Mpeg4Lib.Boxes;
using Mpeg4Lib.ID3;
using Mpeg4Lib.Util;
using NAudio.Lame.ID3;
using System;
using System.Collections.Generic;
using System.IO;

View File

@@ -206,12 +206,12 @@ namespace FileLiberator
}
#region Decryptor event handlers
private void Converter_RetrievedMetadata(object? sender, AAXClean.AppleTags tags)
private void Converter_RetrievedMetadata(object? sender, Mpeg4Lib.MetadataItems tags)
{
if (sender is not AaxcDownloadConvertBase converter ||
converter.AaxFile is not AAXClean.Mp4File aaxFile ||
converter.AaxFile is not Mpeg4Lib.Mpeg4File aaxFile ||
converter.DownloadOptions is not DownloadOptions options ||
options.ChapterInfo.Chapters is not List<AAXClean.Chapter> chapters)
options.ChapterInfo.Chapters is not List<Mpeg4Lib.Chapter> chapters)
return;
#region Prevent erroneous truncation due to incorrect chapter info
@@ -240,7 +240,7 @@ namespace FileLiberator
tags.Album ??= tags.Title;
tags.Artist ??= string.Join("; ", options.LibraryBook.Book.Authors.Select(a => a.Name));
tags.AlbumArtists ??= tags.Artist;
tags.Generes = string.Join(", ", options.LibraryBook.Book.LowestCategoryNames());
tags.Genres = string.Join(", ", options.LibraryBook.Book.LowestCategoryNames());
tags.ProductID ??= options.ContentMetadata.ContentReference.Sku;
tags.Comment ??= options.LibraryBook.Book.Description;
tags.LongDescription ??= tags.Comment;
@@ -256,9 +256,9 @@ namespace FileLiberator
}
const string tagDomain = "org.libation";
aaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_ACR", tags.Acr);
aaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_DRM_TYPE", options.DrmType.ToString());
aaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_LOCALE", options.LibraryBook.Book.Locale);
tags.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_ACR", tags.Acr);
tags.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_DRM_TYPE", options.DrmType.ToString());
tags.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_LOCALE", options.LibraryBook.Book.Locale);
}
private void AaxcDownloader_RetrievedCoverArt(object? sender, byte[]? e)

View File

@@ -187,7 +187,7 @@ public partial class DownloadOptions
var dlOptions = new DownloadOptions(config, libraryBook, licInfo)
{
ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapters[0].StartOffsetMs)),
ChapterInfo = new Mpeg4Lib.ChapterInfo(TimeSpan.FromMilliseconds(chapters[0].StartOffsetMs)),
RuntimeLength = TimeSpan.FromMilliseconds(licInfo.ContentMetadata.ChapterInfo.RuntimeLengthMs),
};

View File

@@ -1,5 +1,4 @@
using AaxDecrypter;
using AAXClean;
using Dinah.Core;
using DataLayer;
using LibationFileManager;
@@ -19,7 +18,7 @@ namespace FileLiberator
public KeyData[]? DecryptionKeys { get; }
public required TimeSpan RuntimeLength { get; init; }
public OutputFormat OutputFormat { get; }
public required ChapterInfo ChapterInfo { get; init; }
public required Mpeg4Lib.ChapterInfo ChapterInfo { get; init; }
public string Title => LibraryBook.Book.Title;
public string Subtitle => LibraryBook.Book.Subtitle;
public string Publisher => LibraryBook.Book.Publisher;

View File

@@ -50,7 +50,16 @@ public class App : Application
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
DisableAvaloniaDataAnnotationValidation();
RunSetupIfNeededAsync(desktop, Configuration.Instance);
if (LibraryTask is null)
{
RunSetupIfNeededAsync(desktop, Configuration.Instance);
}
else
{
//LibraryTask was already started early in Program.Main(),
//which means config is valid and migrations have already run.
ShowMainWindow(desktop);
}
}
base.OnFrameworkInitializationCompleted();
@@ -66,7 +75,7 @@ public class App : Application
if (await setup.RunSetupIfNeededAsync())
{
// setup succeeded or wasn't needed and LibationFiles are valid
await RunMigrationsAsync(config);
RunMigrations(config);
LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
ShowMainWindow(desktop);
}
@@ -97,11 +106,10 @@ public class App : Application
return await tcs.Task;
}
private static async Task RunMigrationsAsync(Configuration config)
public static void RunMigrations(Configuration config)
{
// most migrations go in here
LibationScaffolding.RunPostConfigMigrations(config);
await MessageBox.VerboseLoggingWarning_ShowIfTrue();
// logging is init'd here
LibationScaffolding.RunPostMigrationScaffolding(Variety.Chardonnay, config);
}

View File

@@ -40,7 +40,7 @@
<TextBlock
Grid.Column="0"
Text="If you'd like to report this error to an advinistrator:&#xa;&#xa;Step 1: Go to Libation's &quot;issues&quot; page on github&#xa;Step 2: Find your log files&#xa;Setp 3: Click &quot;New issue&quot; button&#xa;Step 4: Drag/drop your log files" />
Text="If you'd like to report this error to an administrator:&#xa;&#xa;Step 1: Go to Libation's &quot;issues&quot; page on github&#xa;Step 2: Find your log files&#xa;Setp 3: Click &quot;New issue&quot; button&#xa;Step 4: Drag/drop your log files" />
<StackPanel
Margin="50,0,0,0"

View File

@@ -51,10 +51,7 @@ namespace LibationAvalonia
var config = LibationScaffolding.RunPreConfigMigrations();
if (config.LibationFiles.SettingsAreValid)
{
// most migrations go in here
LibationScaffolding.RunPostConfigMigrations(config);
LibationScaffolding.RunPostMigrationScaffolding(Variety.Chardonnay, config);
App.RunMigrations(config);
//Start loading the library before loading the main form
App.LibraryTask = Task.Run(() => DbContexts.GetLibrary_Flat_NoTracking(includeParents: true));
}

View File

@@ -119,6 +119,8 @@ namespace LibationAvalonia.Views
private async void MainWindow_Opened(object? sender, EventArgs e)
{
await MessageBox.VerboseLoggingWarning_ShowIfTrue();
if (AudibleFileStorage.BooksDirectory is null)
{
var result = await MessageBox.Show(

View File

@@ -20,10 +20,16 @@ namespace LibationCli
protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook>? completedAction = null)
where TProcessable : Processable, IProcessable<TProcessable>
{
var progressBar = new ConsoleProgressBar(Console.Out);
var strProc = TProcessable.Create(Configuration.Instance);
LibraryBook? currentLibraryBook = null;
if (Environment.UserInteractive && !Console.IsOutputRedirected && !Console.IsErrorRedirected) {
var progressBar = new ConsoleProgressBar(Console.Out);
strProc.Completed += (_, e) => progressBar.Clear();
strProc.StreamingTimeRemaining += (_, e) => progressBar.RemainingTime = e;
strProc.StreamingProgressChanged += (_, e) => progressBar.Progress = e.ProgressPercentage;
}
strProc.Begin += (o, e) =>
{
currentLibraryBook = e;
@@ -32,7 +38,6 @@ namespace LibationCli
strProc.Completed += (o, e) =>
{
progressBar.Clear();
Console.WriteLine($"{typeof(TProcessable).Name} Completed: {e}");
};
@@ -49,9 +54,6 @@ namespace LibationCli
}
};
strProc.StreamingTimeRemaining += (_, e) => progressBar.RemainingTime = e;
strProc.StreamingProgressChanged += (_, e) => progressBar.Progress = e.ProgressPercentage;
if (strProc is AudioDecodable audDec)
{
audDec.RequestCoverArt += (_,_) =>

View File

@@ -251,7 +251,7 @@ namespace LibationFileManager
{
if (format is OutputFormat.M4b)
{
var tags = await Task.Run(() => AAXClean.AppleTags.FromFile(path));
var tags = await Task.Run(() => Mpeg4Lib.MetadataItems.FromFile(path));
if (tags?.Asin is not null)
audioFile = new FilePathCache.CacheEntry(tags.Asin, FileType.Audio, path);
@@ -259,10 +259,10 @@ namespace LibationFileManager
else
{
using var fileStream = File.OpenRead(path);
var id3 = await Task.Run(() => NAudio.Lame.ID3.Id3Tag.Create(fileStream));
var id3 = await Task.Run(() => Mpeg4Lib.ID3.Id3Tag.Create(fileStream));
var asin = id3?.Children
.OfType<NAudio.Lame.ID3.TXXXFrame>()
.OfType<Mpeg4Lib.ID3.TXXXFrame>()
.FirstOrDefault(f => f.FieldName == "AUDIBLE_ASIN")
?.FieldValue;

View File

@@ -342,7 +342,7 @@ namespace LibationFileManager
[Description("Automatically run periodic scans in the background?")]
public bool AutoScan { get => GetNonString(defaultValue: true); set => SetNonString(value); }
[Description("Use Libation's buit-in web broswer to log into Audible?")]
[Description("Use Libation's built-in web browser to log into Audible?")]
public bool UseWebView { get => GetNonString(defaultValue: true); set => SetNonString(value); }
[Description("Auto download books? After scan, download new books in 'checked' accounts.")]

View File

@@ -12,7 +12,7 @@ using System.Threading.Tasks;
namespace LibationUiBase.GridView;
public delegate void LiberateClickedHandler(object sender, System.Collections.Generic.IList<LibraryBook> libraryBooks, Configuration config);
public delegate void LiberateClickedHandler(object? sender, System.Collections.Generic.IList<LibraryBook> libraryBooks, Configuration config);
public class GridContextMenu
{
public string CopyCellText => $"{Accelerator}Copy Cell Contents";

View File

@@ -59,7 +59,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="label1.Text" xml:space="preserve">
<value>If you'd like to report this error to an advinistrator:
<value>If you'd like to report this error to an administrator:
Step 1: Go to Libation's "issues" page on github
Step 2: Find your log files

View File

@@ -4,6 +4,7 @@ using Dinah.Core;
using Dinah.Core.Threading;
using System.Collections.Generic;
#nullable enable
namespace LibationWinForms
{
public partial class Form1
@@ -30,7 +31,7 @@ namespace LibationWinForms
private bool runBackupCountsAgain;
private void setBackupCounts(object _, List<LibraryBook> libraryBooks)
private void setBackupCounts(object? _, List<LibraryBook>? libraryBooks)
{
runBackupCountsAgain = true;
@@ -38,7 +39,7 @@ namespace LibationWinForms
updateCountsBw.RunWorkerAsync(libraryBooks);
}
private void UpdateCountsBw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
private void UpdateCountsBw_DoWork(object? sender, System.ComponentModel.DoWorkEventArgs e)
{
while (runBackupCountsAgain)
{
@@ -47,47 +48,47 @@ namespace LibationWinForms
}
}
private void exportMenuEnable(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
private void exportMenuEnable(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
var libraryStats = e.Result as LibraryCommands.LibraryStats;
Invoke(() => exportLibraryToolStripMenuItem.Enabled = libraryStats.HasBookResults);
Invoke(() => exportLibraryToolStripMenuItem.Enabled = libraryStats?.HasBookResults is true);
}
private void updateBottomStats(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
private void updateBottomStats(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
var libraryStats = e.Result as LibraryCommands.LibraryStats;
statusStrip1.UIThreadAsync(() => backupsCountsLbl.Text = libraryStats.StatusString);
statusStrip1.UIThreadAsync(() => backupsCountsLbl.Text = libraryStats?.StatusString ?? "ERROR GETTING STATUS");
}
// update 'begin book and pdf backups' menu item
private void update_BeginBookBackups_menuItem(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
private void update_BeginBookBackups_menuItem(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
var libraryStats = e.Result as LibraryCommands.LibraryStats;
var menuItemText
= libraryStats.HasPendingBooks
= libraryStats?.HasPendingBooks is true
? $"{libraryStats.PendingBooks} remaining"
: "All books have been liberated";
menuStrip1.UIThreadAsync(() =>
{
beginBookBackupsToolStripMenuItem.Format(menuItemText);
beginBookBackupsToolStripMenuItem.Enabled = libraryStats.HasPendingBooks;
beginBookBackupsToolStripMenuItem.Enabled = libraryStats?.HasPendingBooks is true;
});
}
// update 'begin pdf only backups' menu item
private void udpate_BeginPdfOnlyBackups_menuItem(object _, System.ComponentModel.RunWorkerCompletedEventArgs e)
private void udpate_BeginPdfOnlyBackups_menuItem(object? _, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
var libraryStats = e.Result as LibraryCommands.LibraryStats;
var menuItemText
= libraryStats.pdfsNotDownloaded > 0
= libraryStats?.pdfsNotDownloaded > 0
? $"{libraryStats.pdfsNotDownloaded} remaining"
: "All PDFs have been downloaded";
menuStrip1.UIThreadAsync(() =>
{
beginPdfBackupsToolStripMenuItem.Format(menuItemText);
beginPdfBackupsToolStripMenuItem.Enabled = libraryStats.pdfsNotDownloaded > 0;
beginPdfBackupsToolStripMenuItem.Enabled = libraryStats?.pdfsNotDownloaded > 0;
});
}
}

View File

@@ -2,6 +2,7 @@
using System.Windows.Forms;
using ApplicationServices;
#nullable enable
namespace LibationWinForms
{
public partial class Form1

View File

@@ -2,6 +2,7 @@
using System.Windows.Forms;
using LibationWinForms.Dialogs;
#nullable enable
namespace LibationWinForms
{
public partial class Form1
@@ -27,8 +28,8 @@ namespace LibationWinForms
private void filterBtn_Click(object sender, EventArgs e) => performFilter(this.filterSearchTb.Text);
private string lastGoodFilter = "";
private void performFilter(string filterString)
private string? lastGoodFilter = null;
private void performFilter(string? filterString)
{
this.filterSearchTb.Text = filterString;
@@ -55,12 +56,12 @@ namespace LibationWinForms
dialog.Show(this);
return dialog;
void Dialog_Closed(object sender, FormClosedEventArgs e)
void Dialog_Closed(object? sender, FormClosedEventArgs e)
{
dialog.TagDoubleClicked -= Dialog_TagDoubleClicked;
filterHelpBtn.Enabled = true;
}
void Dialog_TagDoubleClicked(object sender, string tag)
void Dialog_TagDoubleClicked(object? sender, string tag)
{
if (string.IsNullOrEmpty(tag)) return;

View File

@@ -6,6 +6,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
#nullable enable
namespace LibationWinForms
{
public partial class Form1
@@ -13,7 +14,7 @@ namespace LibationWinForms
private void Configure_Liberate() { }
//GetLibrary_Flat_NoTracking() may take a long time on a hugh library. so run in new thread
private async void beginBookBackupsToolStripMenuItem_Click(object _ = null, EventArgs __ = null)
private async void beginBookBackupsToolStripMenuItem_Click(object? _ = null, EventArgs? __ = null)
{
var library = await Task.Run(DbContexts.GetUnliberated_Flat_NoTracking);
BackupAllBooks(library);

View File

@@ -1,14 +1,13 @@
using DataLayer;
using Dinah.Core;
using LibationFileManager;
using LibationUiBase;
using LibationUiBase.Forms;
using LibationUiBase.GridView;
using LibationWinForms.ProcessQueue;
using System;
using System.Linq;
using System.Windows.Forms;
#nullable enable
namespace LibationWinForms
{
public partial class Form1
@@ -106,7 +105,7 @@ namespace LibationWinForms
SetQueueCollapseState(!splitContainer1.Panel2Collapsed);
}
private void ProcessBookQueue1_PopOut(object sender, EventArgs e)
private void ProcessBookQueue1_PopOut(object? sender, EventArgs e)
{
ProcessBookForm dockForm = new();
dockForm.WidthChange = splitContainer1.Panel2.Width + splitContainer1.SplitterWidth;
@@ -124,7 +123,7 @@ namespace LibationWinForms
filterSearchTb.Location = new System.Drawing.Point(filterSearchTb.Location.X + deltax, filterSearchTb.Location.Y);
}
private void DockForm_FormClosing(object sender, FormClosingEventArgs e)
private void DockForm_FormClosing(object? sender, FormClosingEventArgs e)
{
if (sender is ProcessBookForm dockForm)
{

View File

@@ -4,6 +4,7 @@ using System.Windows.Forms;
using LibationFileManager;
using LibationWinForms.Dialogs;
#nullable enable
namespace LibationWinForms
{
public partial class Form1
@@ -17,7 +18,7 @@ namespace LibationWinForms
}
private object quickFilterTag { get; } = new();
private void updateFiltersMenu(object _ = null, object __ = null)
private void updateFiltersMenu(object? _ = null, object? __ = null)
{
// remove old
var removeUs = quickFiltersToolStripMenuItem.DropDownItems
@@ -41,7 +42,7 @@ namespace LibationWinForms
}
}
private void updateFirstFilterIsDefaultToolStripMenuItem(object sender, EventArgs e)
private void updateFirstFilterIsDefaultToolStripMenuItem(object? sender, EventArgs e)
=> firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault;
private void firstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e)
@@ -57,7 +58,23 @@ namespace LibationWinForms
private void productsDisplay_InitialLoaded(object sender, EventArgs e)
{
if (QuickFilters.UseDefault)
performFilter(QuickFilters.Filters.FirstOrDefault()?.Filter);
{
// begin verbose null checking. shouldn't be possible, yet NRE in #1578
var f = QuickFilters.Filters;
if (f is null)
Serilog.Log.Logger.Error("Unexpected exception. QuickFilters.Filters is null");
var first = f.FirstOrDefault();
if (first is null)
Serilog.Log.Logger.Information("QuickFilters.Filters.FirstOrDefault() is null");
var filter = first?.Filter;
if (filter is null)
Serilog.Log.Logger.Information("QuickFilters.Filters.FirstOrDefault()?.Filter is null");
// end verbose null checking
performFilter(filter);
}
}
}
}

View File

@@ -1,12 +1,10 @@
using AudibleUtilities;
using LibationWinForms.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
#nullable enable
namespace LibationWinForms
{
public partial class Form1

View File

@@ -7,6 +7,7 @@ using AudibleUtilities;
using Dinah.Core;
using LibationFileManager;
#nullable enable
namespace LibationWinForms
{
// This is for the auto-scanner. It is unrelated to manual scanning/import
@@ -58,7 +59,7 @@ namespace LibationWinForms
[PropertyChangeFilter(nameof(Configuration.AutoScan))]
private void Configuration_PropertyChanged(object sender, PropertyChangedEventArgsEx e)
private void Configuration_PropertyChanged(object? sender, PropertyChangedEventArgsEx e)
{
// when autoscan setting is changed, update menu checkbox and run autoscan
updateAutoScanLibraryToolStripMenuItem(sender, e);
@@ -75,9 +76,9 @@ namespace LibationWinForms
.Select(a => (a.AccountId, a.Locale.Name))
.ToList();
}
private void accountsPreSave(object sender = null, EventArgs e = null)
private void accountsPreSave(object? sender = null, EventArgs? e = null)
=> preSaveDefaultAccounts = getDefaultAccounts();
private void accountsPostSave(object sender = null, EventArgs e = null)
private void accountsPostSave(object? sender = null, EventArgs? e = null)
{
var postSaveDefaultAccounts = getDefaultAccounts();
var newDefaultAccounts = postSaveDefaultAccounts.Except(preSaveDefaultAccounts).ToList();
@@ -86,7 +87,7 @@ namespace LibationWinForms
startAutoScan();
}
private void startAutoScan(object sender = null, EventArgs e = null)
private void startAutoScan(object? sender = null, EventArgs? e = null)
{
if (Configuration.Instance.AutoScan)
autoScanTimer.PerformNow();
@@ -94,8 +95,8 @@ namespace LibationWinForms
autoScanTimer.Stop();
}
private void updateAutoScanLibraryToolStripMenuItem(object sender, EventArgs e) => autoScanLibraryToolStripMenuItem.Checked = Configuration.Instance.AutoScan;
private void updateAutoScanLibraryToolStripMenuItem(object? sender, EventArgs e) => autoScanLibraryToolStripMenuItem.Checked = Configuration.Instance.AutoScan;
private void autoScanLibraryToolStripMenuItem_Click(object sender, EventArgs e) => Configuration.Instance.AutoScan = !autoScanLibraryToolStripMenuItem.Checked;
private void autoScanLibraryToolStripMenuItem_Click(object? sender, EventArgs e) => Configuration.Instance.AutoScan = !autoScanLibraryToolStripMenuItem.Checked;
}
}

View File

@@ -8,6 +8,7 @@ using AudibleUtilities;
using LibationFileManager;
using LibationWinForms.Dialogs;
#nullable enable
namespace LibationWinForms
{
// this is for manual scan/import. Unrelated to auto-scan
@@ -20,7 +21,7 @@ namespace LibationWinForms
locateAudiobooksToolStripMenuItem.ToolTipText = Configuration.GetHelpText("LocateAudiobooks");
}
private void refreshImportMenu(object _, EventArgs __)
private void refreshImportMenu(object? _, EventArgs? __)
{
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var count = persister.AccountsSettings.Accounts.Count;
@@ -46,8 +47,8 @@ namespace LibationWinForms
private async void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e)
{
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault();
await scanLibrariesAsync(firstAccount);
if (persister.AccountsSettings.GetAll().FirstOrDefault() is { } firstAccount)
await scanLibrariesAsync(firstAccount);
}
private async void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)

View File

@@ -1,6 +1,7 @@
using System;
using ApplicationServices;
#nullable enable
namespace LibationWinForms
{
// This is for the Scanning notification in the upper right. This shown for manual scanning and auto-scan
@@ -12,7 +13,7 @@ namespace LibationWinForms
LibraryCommands.ScanEnd += LibraryCommands_ScanEnd;
}
private void LibraryCommands_ScanBegin(object sender, int accountsLength)
private void LibraryCommands_ScanBegin(object? sender, int accountsLength)
{
removeLibraryBooksToolStripMenuItem.Enabled = false;
removeAllAccountsToolStripMenuItem.Enabled = false;
@@ -29,7 +30,7 @@ namespace LibationWinForms
: $"Scanning {accountsLength} accounts...";
}
private void LibraryCommands_ScanEnd(object sender, int newCount)
private void LibraryCommands_ScanEnd(object? sender, int newCount)
{
removeLibraryBooksToolStripMenuItem.Enabled = true;
removeAllAccountsToolStripMenuItem.Enabled = true;

View File

@@ -2,6 +2,7 @@
using System.Windows.Forms;
using LibationWinForms.Dialogs;
#nullable enable
namespace LibationWinForms
{
public partial class Form1
@@ -11,7 +12,7 @@ namespace LibationWinForms
Shown += FormShown_Settings;
}
private void FormShown_Settings(object sender, EventArgs e)
private void FormShown_Settings(object? sender, EventArgs e)
{
if (LibationFileManager.AudibleFileStorage.BooksDirectory is null)
{

View File

@@ -3,6 +3,7 @@ using LibationWinForms.Dialogs;
using System.Threading.Tasks;
using System.Windows.Forms;
#nullable enable
namespace LibationWinForms
{
public partial class Form1

View File

@@ -8,6 +8,7 @@ using Dinah.Core.Threading;
using LibationUiBase;
using LibationWinForms.Dialogs;
#nullable enable
namespace LibationWinForms
{
public partial class Form1
@@ -24,7 +25,7 @@ namespace LibationWinForms
LibraryCommands.BookUserDefinedItemCommitted += setLiberatedVisibleMenuItemAsync;
}
private async void setLiberatedVisibleMenuItemAsync(object _, object __)
private async void setLiberatedVisibleMenuItemAsync(object? _, object __)
=> await Task.Run(setLiberatedVisibleMenuItem);
private static DateTime lastVisibleCountUpdated;

View File

@@ -6,6 +6,7 @@ using FileManager;
using LibationFileManager;
using LibationUiBase;
#nullable enable
namespace LibationWinForms
{
public partial class Form1
@@ -26,7 +27,7 @@ namespace LibationWinForms
// wire-up event to automatically download after scan.
// winforms only. this should NOT be allowed in cli
updateCountsBw.RunWorkerCompleted += (object sender, System.ComponentModel.RunWorkerCompletedEventArgs e) =>
updateCountsBw.RunWorkerCompleted += (object? sender, System.ComponentModel.RunWorkerCompletedEventArgs e) =>
{
if (!Configuration.Instance.AutoDownloadEpisodes || e.Result is not LibraryCommands.LibraryStats libraryStats)
return;
@@ -36,14 +37,14 @@ namespace LibationWinForms
};
}
private static object LoadResourceImage(string resourceName)
private static object? LoadResourceImage(string resourceName)
{
if (Application.IsDarkModeEnabled)
resourceName += "_dark";
return Properties.Resources.ResourceManager.GetObject(resourceName);
}
private void AudibleApiStorage_LoadError(object sender, AccountSettingsLoadErrorEventArgs e)
private void AudibleApiStorage_LoadError(object? sender, AccountSettingsLoadErrorEventArgs e)
{
try
{

View File

@@ -14,18 +14,18 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
#nullable enable
namespace LibationWinForms.GridView
{
public partial class ProductsDisplay : UserControl
{
/// <summary>Number of visible rows has changed</summary>
public event EventHandler<int> VisibleCountChanged;
public event EventHandler<int> RemovableCountChanged;
public event LiberateClickedHandler LiberateClicked;
public event EventHandler<SeriesEntry> LiberateSeriesClicked;
public event EventHandler<LibraryBook[]> ConvertToMp3Clicked;
public event EventHandler InitialLoaded;
public event EventHandler<int>? VisibleCountChanged;
public event EventHandler<int>? RemovableCountChanged;
public event LiberateClickedHandler? LiberateClicked;
public event EventHandler<SeriesEntry>? LiberateSeriesClicked;
public event EventHandler<LibraryBook[]>? ConvertToMp3Clicked;
public event EventHandler? InitialLoaded;
private bool hasBeenDisplayed;
@@ -37,15 +37,15 @@ namespace LibationWinForms.GridView
#region Button controls
private ImageDisplay imageDisplay;
private ImageDisplay? imageDisplay;
private void productsGrid_CoverClicked(GridEntry liveGridEntry)
{
var picDef = new PictureDefinition(liveGridEntry.LibraryBook.Book.PictureLarge ?? liveGridEntry.LibraryBook.Book.PictureId, PictureSize.Native);
void PictureCached(object sender, PictureCachedEventArgs e)
void PictureCached(object? sender, PictureCachedEventArgs e)
{
if (e.Definition.PictureId == picDef.PictureId)
imageDisplay.SetCoverArt(e.Picture);
imageDisplay?.SetCoverArt(e.Picture);
PictureStorage.PictureCached -= PictureCached;
}
@@ -82,7 +82,7 @@ namespace LibationWinForms.GridView
BorderThickness = 2,
};
void CloseWindow(object o, EventArgs e)
void CloseWindow(object? o, EventArgs e)
{
displayWindow.Close();
}
@@ -92,7 +92,7 @@ namespace LibationWinForms.GridView
displayWindow.Show(this);
}
private BookDetailsDialog bookDetailsForm;
private BookDetailsDialog? bookDetailsForm;
private void productsGrid_DetailsClicked(LibraryBookEntry liveGridEntry)
{
if (bookDetailsForm is null || bookDetailsForm.IsDisposed || !bookDetailsForm.Visible)
@@ -106,7 +106,7 @@ namespace LibationWinForms.GridView
if (!bookDetailsForm.Visible)
bookDetailsForm.Show(this);
async void bookDetailsForm_FormClosed(object sender, FormClosedEventArgs e)
async void bookDetailsForm_FormClosed(object? sender, FormClosedEventArgs e)
{
bookDetailsForm.FormClosed -= bookDetailsForm_FormClosed;
bookDetailsForm.SaveSizeAndLocation(Configuration.Instance);
@@ -381,7 +381,7 @@ namespace LibationWinForms.GridView
foreach (var r in removable)
r.Remove = true;
productsGrid_RemovableCountChanged(this, null);
productsGrid_RemovableCountChanged(this, EventArgs.Empty);
}
catch (OperationCanceledException)
{
@@ -401,7 +401,7 @@ namespace LibationWinForms.GridView
#region UI display functions
public async Task DisplayAsync(List<LibraryBook> libraryBooks = null)
public async Task DisplayAsync(List<LibraryBook>? libraryBooks = null)
{
try
{
@@ -428,7 +428,7 @@ namespace LibationWinForms.GridView
#region Filter
public void Filter(string searchString)
public void Filter(string? searchString)
=> productsGrid.Filter(searchString);
#endregion
@@ -443,7 +443,7 @@ namespace LibationWinForms.GridView
private void productsGrid_LiberateClicked(LibraryBookEntry liveGridEntry)
{
if (liveGridEntry.LibraryBook.Book.UserDefinedItem.BookStatus is not LiberatedStatus.Error
&& !liveGridEntry.Liberate.IsUnavailable)
&& liveGridEntry.Liberate?.IsUnavailable is false)
LiberateClicked?.Invoke(this, [liveGridEntry.LibraryBook], Configuration.Instance);
}

View File

@@ -0,0 +1,31 @@
# Troubleshooting Common Libation Errors
## How to run the Hangover App
When troubleshooting, you may be asked to run 'Hangover'. Hangover is a debugging app to help diagnose and solve some problems with Libation.
It is located alongside the Libation app (though not included in the docker container).
### Windows
Hangover.exe is located in the folder containing Libation.exe. Double-click it to rune it.
### macOS
Hangover is located inside the app bundle. Either:
1. From a terminal, run this command: `open -a Libation.app --args hangover`
2. Run it from within the app bundle.
1. In finder, right-click the Libation app bundle and "Show Package Contents"
2. Open folders "Contents" > "MacOS"
3. Find the file named "Hangover" and double-click it to run it.
### Linux (either the .deb or .rpm installers)
The installer creates shortcuts for `libation`, `libationcli`, and `hangover`. From a terminal, run `hangover`.
## SQLite Error 10: 'disk I/O error'.
There are two possible causes of this error.
1. Your hard disk is full. Check that you have space on the storage device containing your Libation Files (where the LibationContext.db and log files are). If that device still has available space, move on to #2 below.
2. The database's journaling mode is incompatible with your environment. Change the journaling mode to `DELETE` by one of two methods.
1. [Run hangover](#how-to-run-the-hangover-app) and execute the following command in the "Database" tab: `PRAGMA journal_mode=DELETE`
2. run this command in your terminal: `sqlite3 "path/to/libation/files/LibationContext.db" "PRAGMA journal_mode=DELETE;"`