Remove old migrations

This commit is contained in:
Michael Bucari-Tovo 2022-12-31 22:39:16 -07:00
parent a7bf30954d
commit 1ac825919a
10 changed files with 29 additions and 244 deletions

View File

@ -81,7 +81,6 @@ namespace AppScaffolding
// //
Migrations.migrate_to_v6_6_9(config); Migrations.migrate_to_v6_6_9(config);
Migrations.migrate_from_7_10_1(config);
} }
public static void PopulateMissingConfigValues(Configuration config) public static void PopulateMissingConfigValues(Configuration config)
@ -457,74 +456,5 @@ namespace AppScaffolding
UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Enrich", "WithExceptionDetails"); UNSAFE_MigrationHelper.Settings_AddUniqueToArray("Serilog.Enrich", "WithExceptionDetails");
} }
} }
public static void migrate_from_7_10_1(Configuration config)
{
var lastMigrationThrew = config.GetNonString<bool>($"{nameof(migrate_from_7_10_1)}_ThrewError");
if (lastMigrationThrew) return;
try
{
//https://github.com/rmcrackan/Libation/issues/270#issuecomment-1152863629
//This migration helps fix databases contaminated with the 7.10.1 hack workaround
//and those with improperly identified or missing series. This does not solve cases
//where individual episodes are in the db with a valid series link, but said series'
//parents have not been imported into the database. For those cases, Libation will
//attempt fixup by retrieving parents from the catalog endpoint
using var context = DbContexts.GetContext();
//This migration removes books and series with SERIES_ prefix that were created
//as a hack workaround in 7.10.1. Said workaround was removed in 7.10.2
string removeHackSeries = "delete " +
"from series " +
"where AudibleSeriesId like 'SERIES%'";
string removeHackBooks = "delete " +
"from books " +
"where AudibleProductId like 'SERIES%'";
//Detect series parents that were added to the database as books with ContentType.Episode,
//and change them to ContentType.Parent
string updateContentType =
"UPDATE books " +
"SET contenttype = 4 " +
"WHERE audibleproductid IN (SELECT books.audibleproductid " +
"FROM books " +
"INNER JOIN series " +
"ON ( books.audibleproductid = " +
"series.audibleseriesid) " +
"WHERE books.contenttype = 2)";
//Then detect series parents that were added to the database as books with ContentType.Parent
//but are missing a series link, and add the link (don't know how this happened)
string addMissingSeriesLink =
"INSERT INTO seriesbook " +
"SELECT series.seriesid, " +
"books.bookid, " +
"'- 1' " +
"FROM books " +
"LEFT OUTER JOIN seriesbook " +
"ON books.bookid = seriesbook.bookid " +
"INNER JOIN series " +
"ON books.audibleproductid = series.audibleseriesid " +
"WHERE books.contenttype = 4 " +
"AND seriesbook.seriesid IS NULL";
context.Database.ExecuteSqlRaw(removeHackSeries);
context.Database.ExecuteSqlRaw(removeHackBooks);
context.Database.ExecuteSqlRaw(updateContentType);
context.Database.ExecuteSqlRaw(addMissingSeriesLink);
LibraryCommands.SaveContext(context);
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occurred while running database migrations in {0}", nameof(migrate_from_7_10_1));
config.SetObject($"{nameof(migrate_from_7_10_1)}_ThrewError", true);
}
}
} }
} }

View File

@ -126,22 +126,6 @@ namespace ApplicationServices
if (totalCount == 0) if (totalCount == 0)
return default; return default;
Log.Logger.Information("Begin scan for orphaned episode parents");
var newParents = await findAndAddMissingParents(accounts);
Log.Logger.Information($"Orphan episode scan complete. New parents count {newParents}");
if (newParents >= 0)
{
//If any episodes are still orphaned, their series have been
//removed from the catalog and we'll never be able to find them.
//only do this if findAndAddMissingParents returned >= 0. If it
//returned < 0, an error happened and there's still a chance that
//a future successful run will find missing parents.
removedOrphanedEpisodes();
}
Log.Logger.Information("Begin long-running import"); Log.Logger.Information("Begin long-running import");
logTime($"pre {nameof(importIntoDbAsync)}"); logTime($"pre {nameof(importIntoDbAsync)}");
var newCount = await importIntoDbAsync(importItems); var newCount = await importIntoDbAsync(importItems);
@ -235,84 +219,6 @@ namespace ApplicationServices
return newCount; return newCount;
} }
static void removedOrphanedEpisodes()
{
using var context = DbContexts.GetContext();
try
{
var orphanedEpisodes =
context
.GetLibrary_Flat_NoTracking(includeParents: true)
.FindOrphanedEpisodes();
context.LibraryBooks.RemoveRange(orphanedEpisodes);
context.Books.RemoveRange(orphanedEpisodes.Select(lb => lb.Book));
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occurred while trying to remove orphaned episodes from the database");
}
}
static async Task<int> findAndAddMissingParents(Account[] accounts)
{
using var context = DbContexts.GetContext();
var library = context.GetLibrary_Flat_NoTracking(includeParents: true);
try
{
var orphanedEpisodes = library.FindOrphanedEpisodes().ToList();
if (!orphanedEpisodes.Any())
return -1;
var orphanedSeries =
orphanedEpisodes
.SelectMany(lb => lb.Book.SeriesLink)
.DistinctBy(s => s.Series.AudibleSeriesId)
.ToList();
// The Catalog endpoint does not require authentication.
var api = new ApiUnauthenticated(accounts[0].Locale);
var seriesParents = orphanedSeries.Select(o => o.Series.AudibleSeriesId).ToList();
var items = await api.GetCatalogProductsAsync(seriesParents, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
List<ImportItem> newParentsImportItems = new();
foreach (var sp in orphanedSeries)
{
var seriesItem = items.First(i => i.Asin == sp.Series.AudibleSeriesId);
if (seriesItem.Relationships is null)
continue;
var episode = orphanedEpisodes.First(l => l.Book.AudibleProductId == sp.Book.AudibleProductId);
seriesItem.PurchaseDate = new DateTimeOffset(episode.DateAdded);
seriesItem.Series = new AudibleApi.Common.Series[]
{
new AudibleApi.Common.Series{ Asin = seriesItem.Asin, Title = seriesItem.TitleWithSubtitle, Sequence = "-1"}
};
newParentsImportItems.Add(new ImportItem { DtoItem = seriesItem, AccountId = episode.Account, LocaleName = episode.Book.Locale });
}
var newCount = new LibraryBookImporter(context)
.Import(newParentsImportItems);
await context.SaveChangesAsync();
return newCount;
}
catch (Exception ex)
{
Serilog.Log.Logger.Error(ex, "An error occurred while trying to scan for orphaned episode parents.");
return -1;
}
}
public static int SaveContext(LibationContext context) public static int SaveContext(LibationContext context)
{ {
try try

View File

@ -6,25 +6,6 @@ using System.Reflection;
namespace LibationAvalonia.Controls namespace LibationAvalonia.Controls
{ {
public class DataGridCellContextMenuStripNeededEventArgs
{
private static readonly MethodInfo GetCellValueMethod;
static DataGridCellContextMenuStripNeededEventArgs()
{
GetCellValueMethod = typeof(DataGridColumn).GetMethod("GetCellValue", BindingFlags.NonPublic | BindingFlags.Instance);
}
private static string GetCellValue(DataGridColumn column, object item)
=> GetCellValueMethod.Invoke(column, new object[] { item, column.ClipboardContentBinding })?.ToString() ?? "";
public string CellClipboardContents => GetCellValue(Column, GridEntry);
public DataGridColumn Column { get; init; }
public GridEntry GridEntry { get; init; }
public ContextMenu ContextMenu { get; init; }
public AvaloniaList<MenuItem> ContextMenuItems
=> ContextMenu.Items as AvaloniaList<MenuItem>;
}
internal static class DataGridContextMenus internal static class DataGridContextMenus
{ {
public static event EventHandler<DataGridCellContextMenuStripNeededEventArgs> CellContextMenuStripNeeded; public static event EventHandler<DataGridCellContextMenuStripNeededEventArgs> CellContextMenuStripNeeded;
@ -40,7 +21,7 @@ namespace LibationAvalonia.Controls
public static void AttachContextMenuToCell(this DataGridCell cell) public static void AttachContextMenuToCell(this DataGridCell cell)
{ {
if (cell.ContextMenu is null) if (cell is not null && cell.ContextMenu is null)
{ {
cell.ContextRequested += Cell_ContextRequested; cell.ContextRequested += Cell_ContextRequested;
cell.ContextMenu = ContextMenu; cell.ContextMenu = ContextMenu;
@ -68,4 +49,23 @@ namespace LibationAvalonia.Controls
e.Handled = true; e.Handled = true;
} }
} }
public class DataGridCellContextMenuStripNeededEventArgs
{
private static readonly MethodInfo GetCellValueMethod;
static DataGridCellContextMenuStripNeededEventArgs()
{
GetCellValueMethod = typeof(DataGridColumn).GetMethod("GetCellValue", BindingFlags.NonPublic | BindingFlags.Instance);
}
private static string GetCellValue(DataGridColumn column, object item)
=> GetCellValueMethod.Invoke(column, new object[] { item, column.ClipboardContentBinding })?.ToString() ?? "";
public string CellClipboardContents => GetCellValue(Column, GridEntry);
public DataGridColumn Column { get; init; }
public GridEntry GridEntry { get; init; }
public ContextMenu ContextMenu { get; init; }
public AvaloniaList<MenuItem> ContextMenuItems
=> ContextMenu.Items as AvaloniaList<MenuItem>;
}
} }

View File

@ -13,8 +13,6 @@ namespace LibationAvalonia
{ {
static class Program static class Program
{ {
private static string EXE_DIR = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
static void Main() static void Main()
{ {
//***********************************************// //***********************************************//

View File

@ -20,7 +20,7 @@ namespace LibationAvalonia.Views
{ {
public event EventHandler Load; public event EventHandler Load;
public event EventHandler<List<LibraryBook>> LibraryLoaded; public event EventHandler<List<LibraryBook>> LibraryLoaded;
private MainWindowViewModel _viewModel; private readonly MainWindowViewModel _viewModel;
public MainWindow() public MainWindow()
{ {
@ -135,11 +135,9 @@ namespace LibationAvalonia.Views
try try
{ {
System.Net.Http.HttpClient cli = new(); System.Net.Http.HttpClient cli = new();
using (var fs = System.IO.File.OpenWrite(zipFile)) using var fs = System.IO.File.OpenWrite(zipFile);
{ using var dlStream = await cli.GetStreamAsync(new Uri(upgradeProperties.ZipUrl));
using (var dlStream = await cli.GetStreamAsync(new Uri(upgradeProperties.ZipUrl))) await dlStream.CopyToAsync(fs);
await dlStream.CopyToAsync(fs);
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -43,7 +43,7 @@ namespace LibationWinForms.GridView
protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates cellState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts)
{ {
if (value is Rating rating) if (value is Rating rating)
{ {
ToolTipText = "Click to change ratings"; ToolTipText = "Click to change ratings";
var starString = rating.ToStarString(); var starString = rating.ToStarString();
@ -53,6 +53,9 @@ namespace LibationWinForms.GridView
base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, string.Empty, string.Empty, errorText, cellStyle, advancedBorderStyle, paintParts); base.Paint(graphics, clipBounds, cellBounds, rowIndex, cellState, string.Empty, string.Empty, errorText, cellStyle, advancedBorderStyle, paintParts);
} }
protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context)
=> value is Rating rating ? rating.ToStarString() : value?.ToString();
public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter) public override object ParseFormattedValue(object formattedValue, DataGridViewCellStyle cellStyle, TypeConverter formattedValueTypeConverter, TypeConverter valueTypeConverter)
=> formattedValue; => formattedValue;
} }

View File

@ -120,7 +120,7 @@ namespace LibationWinForms.GridView
try try
{ {
var dgv = (DataGridView)sender; var dgv = (DataGridView)sender;
var text = dgv[e.ColumnIndex, e.RowIndex].Value.ToString(); var text = dgv[e.ColumnIndex, e.RowIndex].FormattedValue.ToString();
InteropFactory.Create().CopyTextToClipboard(text); InteropFactory.Create().CopyTextToClipboard(text);
} }
catch { } catch { }

View File

@ -1,39 +0,0 @@
namespace WindowsConfigApp
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Text = "Form1";
}
#endregion
}
}

View File

@ -1,10 +0,0 @@
namespace WindowsConfigApp
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}

View File

@ -7,7 +7,6 @@ namespace WindowsConfigApp
public override Type InteropFunctionsType => typeof(WinInterop); public override Type InteropFunctionsType => typeof(WinInterop);
public override Type[] ReferencedTypes => new Type[] public override Type[] ReferencedTypes => new Type[]
{ {
typeof(Form1),
typeof(Bitmap), typeof(Bitmap),
typeof(Dinah.Core.WindowsDesktop.GitClient), typeof(Dinah.Core.WindowsDesktop.GitClient),
typeof(Accessibility.IAccIdentity), typeof(Accessibility.IAccIdentity),