diff --git a/AaxDecrypter/NetworkFileStream.cs b/AaxDecrypter/NetworkFileStream.cs
index f3d6091b..d3849992 100644
--- a/AaxDecrypter/NetworkFileStream.cs
+++ b/AaxDecrypter/NetworkFileStream.cs
@@ -204,7 +204,7 @@ namespace AaxDecrypter
_networkStream = response.GetResponseStream();
//Download the file in the background.
- Thread downloadThread = new Thread(() => DownloadFile());
+ Thread downloadThread = new Thread(() => DownloadFile()) { IsBackground = true };
downloadThread.Start();
hasBegunDownloading = true;
diff --git a/DataLayer/QueryObjects/BookQueries.cs b/DataLayer/QueryObjects/BookQueries.cs
index 65c09a9b..d4a83263 100644
--- a/DataLayer/QueryObjects/BookQueries.cs
+++ b/DataLayer/QueryObjects/BookQueries.cs
@@ -25,6 +25,7 @@ namespace DataLayer
.GetBooks()
.Where(predicate);
+ /// This is still IQueryable. YOU MUST CALL ToList() YOURSELF
public static IQueryable GetBooks(this IQueryable books)
=> books
// owned items are always loaded. eg: book.UserDefinedItem, book.Supplements
diff --git a/FileManager/FilePathCache.cs b/FileManager/FilePathCache.cs
index 6af720a9..bfd175a2 100644
--- a/FileManager/FilePathCache.cs
+++ b/FileManager/FilePathCache.cs
@@ -16,16 +16,16 @@ namespace FileManager
public string Path { get; set; }
}
- static Cache cache { get; } = new Cache();
+ private static Cache cache { get; } = new Cache();
- public static string JsonFile => Path.Combine(Configuration.Instance.LibationFiles, "FilePaths.json");
+ private static string jsonFile => Path.Combine(Configuration.Instance.LibationFiles, "FilePaths.json");
static FilePathCache()
{
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed
- if (File.Exists(JsonFile))
+ if (File.Exists(jsonFile))
{
- var list = JsonConvert.DeserializeObject>(File.ReadAllText(JsonFile));
+ var list = JsonConvert.DeserializeObject>(File.ReadAllText(jsonFile));
cache = new Cache(list);
}
}
@@ -74,7 +74,7 @@ namespace FileManager
private static void save()
{
// create json if not exists
- static void resave() => File.WriteAllText(JsonFile, JsonConvert.SerializeObject(cache.ToList(), Formatting.Indented));
+ static void resave() => File.WriteAllText(jsonFile, JsonConvert.SerializeObject(cache.ToList(), Formatting.Indented));
lock (locker)
{
diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj
index e7b3cdf9..d1e53f45 100644
--- a/LibationLauncher/LibationLauncher.csproj
+++ b/LibationLauncher/LibationLauncher.csproj
@@ -13,7 +13,7 @@
win-x64
- 5.4.0.5
+ 5.4.1.1
diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs
index f4222d0a..8a120c0c 100644
--- a/LibationLauncher/Program.cs
+++ b/LibationLauncher/Program.cs
@@ -1,9 +1,12 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using AudibleApi;
using AudibleApi.Authorization;
+using DataLayer;
+using Microsoft.EntityFrameworkCore;
using Dinah.Core.IO;
using Dinah.Core.Logging;
using FileManager;
@@ -51,6 +54,7 @@ namespace LibationLauncher
migrate_to_v5_0_0(config);
migrate_to_v5_2_0__post_config(config);
+ migrate_to_v5_4_1(config);
ensureSerilogConfig(config);
configureLogging(config);
@@ -141,7 +145,7 @@ namespace LibationLauncher
CancelInstallation();
}
- #region migrate_to_v5_0_0 re-register device if device info not in settings
+ #region migrate to v5.0.0 re-register device if device info not in settings
private static void migrate_to_v5_0_0(Configuration config)
{
if (!config.Exists(nameof(config.AllowLibationFixup)))
@@ -229,6 +233,72 @@ namespace LibationLauncher
}
#endregion
+ #region migrate to v5.4.1 see comment
+ // this 'migration' is a bit different. it intentionally runs each time Libation is started. its job will be fulfilled when I eventually
+ // implement the portion which removes FilePaths.json, at which time this method will be a proper migration
+ //
+ // I'm iterating through safe steps toward getting rid of the live scanner except to track audiobook files as a convenience
+ // such as clicking the stop light to open its location. live scanning will be replaced with state tracking in the database.
+
+ // FilePaths.json => db. long running. fire and forget
+ private static void migrate_to_v5_4_1(Configuration config)
+ => new System.Threading.Thread(() => migrate_to_v5_4_1_thread(config)) { IsBackground = true }.Start();
+ private static void migrate_to_v5_4_1_thread(Configuration config)
+ {
+ var debugStopwatch = System.Diagnostics.Stopwatch.StartNew();
+ try
+ {
+ var filePaths = Path.Combine(config.LibationFiles, "FilePaths.json");
+ if (!File.Exists(filePaths))
+ return;
+
+ using var context = ApplicationServices.DbContexts.GetContext();
+ context.Books.Load();
+
+ var jArr = JArray.Parse(File.ReadAllText(filePaths));
+
+ foreach (var jToken in jArr)
+ {
+ var asinToken = jToken["Id"];
+ var fileTypeToken = jToken["FileType"];
+ var pathToken = jToken["Path"];
+ if (asinToken is null || fileTypeToken is null || pathToken is null ||
+ asinToken.Type != JTokenType.String || fileTypeToken.Type != JTokenType.Integer || pathToken.Type != JTokenType.String)
+ continue;
+
+ var asin = asinToken.Value();
+ var fileType = (FileType)fileTypeToken.Value();
+ var path = pathToken.Value();
+
+ if (fileType == FileType.Unknown || fileType == FileType.AAXC)
+ continue;
+
+ var book = context.Books.Local.FirstOrDefault(b => b.AudibleProductId == asin);
+ if (book is null)
+ continue;
+
+ // assign these strings and enums/ints unconditionally. EFCore will only update if changed
+ if (fileType == FileType.PDF)
+ book.UserDefinedItem.PdfStatus = LiberatedStatus.Liberated;
+
+ if (fileType == FileType.Audio)
+ {
+ book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
+ book.UserDefinedItem.BookLocation = path;
+ }
+ }
+
+ context.SaveChanges();
+ }
+ catch (Exception ex)
+ {
+ Log.Logger.Error(ex, "Error attempting to insert FilePaths into db");
+ }
+ debugStopwatch.Stop();
+ var debugTotal = debugStopwatch.Elapsed;
+ }
+ #endregion
+
private static void ensureSerilogConfig(Configuration config)
{
if (config.GetObject("Serilog") != null)
diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs
index 14e22bb9..82352854 100644
--- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs
+++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs
@@ -176,7 +176,8 @@ namespace LibationWinForms.BookLiberation
downloadDialog.UpdateFilename(destination);
downloadDialog.Show();
- new System.Threading.Thread(() => {
+ new System.Threading.Thread(() =>
+ {
var downloadFile = new DownloadFile();
downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.UIThread(() =>
@@ -190,7 +191,9 @@ namespace LibationWinForms.BookLiberation
});
downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult();
- }).Start();
+ })
+ { IsBackground = true }
+ .Start();
}
// subscribed to Begin event because a new form should be created+processed+closed on each iteration
diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs
index bac23cca..3d8c46ea 100644
--- a/LibationWinForms/GridEntry.cs
+++ b/LibationWinForms/GridEntry.cs
@@ -9,14 +9,11 @@ namespace LibationWinForms
{
internal class GridEntry
{
- private LibraryBook libraryBook;
+ private LibraryBook libraryBook { get; }
private Book book => libraryBook.Book;
public Book GetBook() => book;
- // this special case is obvious and ugly
- public void REPLACE_Library_Book(LibraryBook libraryBook) => this.libraryBook = libraryBook;
-
public GridEntry(LibraryBook libraryBook) => this.libraryBook = libraryBook;
// hide from public fields from Data Source GUI with [Browsable(false)]