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)]