Next iterative step toward replacing live scanning with db state. FilePaths.json => db

This commit is contained in:
Robert McRackan 2021-07-28 09:40:27 -04:00
parent 5c6bf300c6
commit 75f1d987fc
7 changed files with 85 additions and 14 deletions

View File

@ -204,7 +204,7 @@ namespace AaxDecrypter
_networkStream = response.GetResponseStream(); _networkStream = response.GetResponseStream();
//Download the file in the background. //Download the file in the background.
Thread downloadThread = new Thread(() => DownloadFile()); Thread downloadThread = new Thread(() => DownloadFile()) { IsBackground = true };
downloadThread.Start(); downloadThread.Start();
hasBegunDownloading = true; hasBegunDownloading = true;

View File

@ -25,6 +25,7 @@ namespace DataLayer
.GetBooks() .GetBooks()
.Where(predicate); .Where(predicate);
/// <summary>This is still IQueryable. YOU MUST CALL ToList() YOURSELF</summary>
public static IQueryable<Book> GetBooks(this IQueryable<Book> books) public static IQueryable<Book> GetBooks(this IQueryable<Book> books)
=> books => books
// owned items are always loaded. eg: book.UserDefinedItem, book.Supplements // owned items are always loaded. eg: book.UserDefinedItem, book.Supplements

View File

@ -16,16 +16,16 @@ namespace FileManager
public string Path { get; set; } public string Path { get; set; }
} }
static Cache<CacheEntry> cache { get; } = new Cache<CacheEntry>(); private static Cache<CacheEntry> cache { get; } = new Cache<CacheEntry>();
public static string JsonFile => Path.Combine(Configuration.Instance.LibationFiles, "FilePaths.json"); private static string jsonFile => Path.Combine(Configuration.Instance.LibationFiles, "FilePaths.json");
static FilePathCache() static FilePathCache()
{ {
// load json into memory. if file doesn't exist, nothing to do. save() will create if needed // 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<List<CacheEntry>>(File.ReadAllText(JsonFile)); var list = JsonConvert.DeserializeObject<List<CacheEntry>>(File.ReadAllText(jsonFile));
cache = new Cache<CacheEntry>(list); cache = new Cache<CacheEntry>(list);
} }
} }
@ -74,7 +74,7 @@ namespace FileManager
private static void save() private static void save()
{ {
// create json if not exists // 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) lock (locker)
{ {

View File

@ -13,7 +13,7 @@
<!-- <PublishSingleFile>true</PublishSingleFile> --> <!-- <PublishSingleFile>true</PublishSingleFile> -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Version>5.4.0.5</Version> <Version>5.4.1.1</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using AudibleApi; using AudibleApi;
using AudibleApi.Authorization; using AudibleApi.Authorization;
using DataLayer;
using Microsoft.EntityFrameworkCore;
using Dinah.Core.IO; using Dinah.Core.IO;
using Dinah.Core.Logging; using Dinah.Core.Logging;
using FileManager; using FileManager;
@ -51,6 +54,7 @@ namespace LibationLauncher
migrate_to_v5_0_0(config); migrate_to_v5_0_0(config);
migrate_to_v5_2_0__post_config(config); migrate_to_v5_2_0__post_config(config);
migrate_to_v5_4_1(config);
ensureSerilogConfig(config); ensureSerilogConfig(config);
configureLogging(config); configureLogging(config);
@ -141,7 +145,7 @@ namespace LibationLauncher
CancelInstallation(); 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) private static void migrate_to_v5_0_0(Configuration config)
{ {
if (!config.Exists(nameof(config.AllowLibationFixup))) if (!config.Exists(nameof(config.AllowLibationFixup)))
@ -229,6 +233,72 @@ namespace LibationLauncher
} }
#endregion #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<string>();
var fileType = (FileType)fileTypeToken.Value<int>();
var path = pathToken.Value<string>();
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) private static void ensureSerilogConfig(Configuration config)
{ {
if (config.GetObject("Serilog") != null) if (config.GetObject("Serilog") != null)

View File

@ -176,7 +176,8 @@ namespace LibationWinForms.BookLiberation
downloadDialog.UpdateFilename(destination); downloadDialog.UpdateFilename(destination);
downloadDialog.Show(); downloadDialog.Show();
new System.Threading.Thread(() => { new System.Threading.Thread(() =>
{
var downloadFile = new DownloadFile(); var downloadFile = new DownloadFile();
downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.UIThread(() => downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.UIThread(() =>
@ -190,7 +191,9 @@ namespace LibationWinForms.BookLiberation
}); });
downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult(); 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 // subscribed to Begin event because a new form should be created+processed+closed on each iteration

View File

@ -9,14 +9,11 @@ namespace LibationWinForms
{ {
internal class GridEntry internal class GridEntry
{ {
private LibraryBook libraryBook; private LibraryBook libraryBook { get; }
private Book book => libraryBook.Book; private Book book => libraryBook.Book;
public Book GetBook() => 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; public GridEntry(LibraryBook libraryBook) => this.libraryBook = libraryBook;
// hide from public fields from Data Source GUI with [Browsable(false)] // hide from public fields from Data Source GUI with [Browsable(false)]