Remove old migrations
This commit is contained in:
parent
a7bf30954d
commit
1ac825919a
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
{
|
{
|
||||||
//***********************************************//
|
//***********************************************//
|
||||||
|
|||||||
@ -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,12 +135,10 @@ 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)
|
||||||
{
|
{
|
||||||
Serilog.Log.Logger.Error(ex, "Failed to download the update: {pdate}", upgradeProperties.ZipUrl);
|
Serilog.Log.Logger.Error(ex, "Failed to download the update: {pdate}", upgradeProperties.ZipUrl);
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 { }
|
||||||
|
|||||||
39
Source/LoadByOS/WindowsConfigApp/Form1.Designer.cs
generated
39
Source/LoadByOS/WindowsConfigApp/Form1.Designer.cs
generated
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
namespace WindowsConfigApp
|
|
||||||
{
|
|
||||||
public partial class Form1 : Form
|
|
||||||
{
|
|
||||||
public Form1()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user