mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-02-06 20:23:03 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7e844a54c | ||
|
|
178cc82d65 | ||
|
|
054f7437d1 | ||
|
|
99051b6975 | ||
|
|
dc1919f411 | ||
|
|
fdfae2f806 | ||
|
|
d58f5abe35 | ||
|
|
d957d6d5d7 | ||
|
|
cb159336a6 | ||
|
|
807bb56c49 | ||
|
|
3d56554aa5 | ||
|
|
471fd1e757 |
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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("©", "©");
|
||||
if (!string.IsNullOrWhiteSpace(AaxFile.MetadataItems.Copyright))
|
||||
AaxFile.MetadataItems.Copyright = AaxFile.MetadataItems.Copyright.Replace("(P)", "℗").Replace("©", "©");
|
||||
|
||||
//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;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using AAXClean;
|
||||
using AAXClean.Codecs;
|
||||
using FileManager;
|
||||
using Mpeg4Lib;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using AAXClean;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core;
|
||||
using Mpeg4Lib;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using AAXClean;
|
||||
using Mpeg4Lib;
|
||||
using System;
|
||||
|
||||
#nullable enable
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Version>13.1.7.1</Version>
|
||||
<Version>13.1.8.1</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Octokit" Version="14.0.0" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
|
||||
#nullable enable
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using LibationWinForms.Dialogs;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
#nullable enable
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public partial class Form1
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
31
docs/advanced/troubleshoot.md
Normal file
31
docs/advanced/troubleshoot.md
Normal 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;"`
|
||||
Reference in New Issue
Block a user