mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-01-02 19:08:39 -05:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f0a6b8526 | ||
|
|
c337c0b44e | ||
|
|
89207866f3 | ||
|
|
9e11086d49 | ||
|
|
58b172f816 | ||
|
|
0b8084bc03 | ||
|
|
37970222f3 | ||
|
|
bcab2dd440 | ||
|
|
d402128d1d | ||
|
|
3ae0f2daa2 | ||
|
|
126919d578 | ||
|
|
437e85fd12 | ||
|
|
de34e5c795 | ||
|
|
8ffcefd6ae | ||
|
|
e59ab9b483 | ||
|
|
57fa1bd763 | ||
|
|
dccb2d73d6 | ||
|
|
77fc865636 | ||
|
|
1040a347c6 | ||
|
|
6ed1307443 | ||
|
|
c2c732b2b1 | ||
|
|
9e0caf34d6 | ||
|
|
802763a4fb | ||
|
|
b4803c42a5 | ||
|
|
62c98c66a3 | ||
|
|
6b289445e2 | ||
|
|
52bf91f8aa | ||
|
|
6d2dff1a98 | ||
|
|
7c9970c0cb | ||
|
|
d2892f9076 | ||
|
|
89f60a7ca3 | ||
|
|
ea37c09081 | ||
|
|
76cb280933 | ||
|
|
0a54a8104c | ||
|
|
7464336535 | ||
|
|
dc0dd3474b | ||
|
|
7b9c5c0f4f | ||
|
|
ad87f1851e | ||
|
|
e8423341ef | ||
|
|
a9d3494af1 | ||
|
|
90731a8948 |
@@ -5,11 +5,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="1.0.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AAXClean\AAXClean.csproj" />
|
||||
<PackageReference Include="AAXClean" Version="0.1.8" />
|
||||
<PackageReference Include="Dinah.Core" Version="1.1.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DataLayer;
|
||||
using FileManager;
|
||||
|
||||
@@ -6,11 +7,15 @@ namespace ApplicationServices
|
||||
{
|
||||
public static class DbContexts
|
||||
{
|
||||
//// idea for future command/query separation
|
||||
// public static LibationContext GetCommandContext() { }
|
||||
// public static LibationContext GetQueryContext() { }
|
||||
|
||||
/// <summary>Use for fully functional context, incl. SaveChanges(). For query-only, use the other method</summary>
|
||||
public static LibationContext GetContext()
|
||||
=> LibationContext.Create(SqliteStorage.ConnectionString);
|
||||
|
||||
/// <summary>Use for full library querying. No lazy loading</summary>
|
||||
public static List<LibraryBook> GetLibrary_Flat_NoTracking()
|
||||
{
|
||||
using var context = GetContext();
|
||||
return context.GetLibrary_Flat_NoTracking();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using Dinah.Core;
|
||||
using DtoImporterService;
|
||||
using InternalUtilities;
|
||||
using Serilog;
|
||||
using static DtoImporterService.PerfLogger;
|
||||
|
||||
namespace ApplicationServices
|
||||
{
|
||||
@@ -16,12 +17,14 @@ namespace ApplicationServices
|
||||
private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
|
||||
|
||||
public static async Task<List<LibraryBook>> FindInactiveBooks(Func<Account, ILoginCallback> loginCallbackFactoryFunc, List<LibraryBook> existingLibrary, params Account[] accounts)
|
||||
{
|
||||
{
|
||||
logRestart();
|
||||
|
||||
//These are the minimum response groups required for the
|
||||
//library scanner to pass all validation and filtering.
|
||||
LibraryResponseGroups =
|
||||
LibraryResponseGroups =
|
||||
LibraryOptions.ResponseGroupOptions.ProductAttrs |
|
||||
LibraryOptions.ResponseGroupOptions.ProductDesc |
|
||||
LibraryOptions.ResponseGroupOptions.ProductDesc |
|
||||
LibraryOptions.ResponseGroupOptions.Relationships;
|
||||
|
||||
if (accounts is null || accounts.Length == 0)
|
||||
@@ -29,8 +32,12 @@ namespace ApplicationServices
|
||||
|
||||
try
|
||||
{
|
||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||
var libraryItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts);
|
||||
Log.Logger.Information($"GetAllLibraryItems: Total count {libraryItems.Count}");
|
||||
logTime($"post {nameof(scanAccountsAsync)} all");
|
||||
|
||||
var totalCount = libraryItems.Count;
|
||||
Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}");
|
||||
|
||||
var missingBookList = existingLibrary.Where(b => !libraryItems.Any(i => i.DtoItem.Asin == b.Book.AudibleProductId)).ToList();
|
||||
|
||||
@@ -56,33 +63,39 @@ namespace ApplicationServices
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Logger.Error(ex, "Error importing library");
|
||||
Log.Logger.Error(ex, "Error scanning library");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
finally
|
||||
{
|
||||
LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS;
|
||||
}
|
||||
stop();
|
||||
var putBreakPointHere = logOutput;
|
||||
}
|
||||
}
|
||||
|
||||
#region FULL LIBRARY scan and import
|
||||
public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, params Account[] accounts)
|
||||
{
|
||||
logRestart();
|
||||
|
||||
if (accounts is null || accounts.Length == 0)
|
||||
return (0, 0);
|
||||
|
||||
try
|
||||
{
|
||||
logTime($"pre {nameof(scanAccountsAsync)} all");
|
||||
var importItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts);
|
||||
logTime($"post {nameof(scanAccountsAsync)} all");
|
||||
|
||||
var totalCount = importItems.Count;
|
||||
Log.Logger.Information($"GetAllLibraryItems: Total count {totalCount}");
|
||||
|
||||
logTime($"pre {nameof(importIntoDbAsync)}");
|
||||
var newCount = await importIntoDbAsync(importItems);
|
||||
logTime($"post {nameof(importIntoDbAsync)}");
|
||||
Log.Logger.Information($"Import: New count {newCount}");
|
||||
|
||||
await Task.Run(() => SearchEngineCommands.FullReIndex());
|
||||
Log.Logger.Information("FullReIndex: success");
|
||||
|
||||
return (totalCount, newCount);
|
||||
}
|
||||
catch (AudibleApi.Authentication.LoginFailedException lfEx)
|
||||
@@ -108,6 +121,11 @@ namespace ApplicationServices
|
||||
Log.Logger.Error(ex, "Error importing library");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stop();
|
||||
var putBreakPointHere = logOutput;
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<List<ImportItem>> scanAccountsAsync(Func<Account, ILoginCallback> loginCallbackFactoryFunc, Account[] accounts)
|
||||
@@ -139,23 +157,68 @@ namespace ApplicationServices
|
||||
Account = account?.MaskedLogEntry ?? "[null]"
|
||||
});
|
||||
|
||||
logTime($"pre scanAccountAsync {account.AccountName}");
|
||||
|
||||
var dtoItems = await AudibleApiActions.GetLibraryValidatedAsync(api, LibraryResponseGroups);
|
||||
|
||||
logTime($"post scanAccountAsync {account.AccountName} qty: {dtoItems.Count}");
|
||||
|
||||
return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList();
|
||||
}
|
||||
|
||||
private static async Task<int> importIntoDbAsync(List<ImportItem> importItems)
|
||||
{
|
||||
logTime("importIntoDbAsync -- pre db");
|
||||
using var context = DbContexts.GetContext();
|
||||
var libraryImporter = new LibraryImporter(context);
|
||||
var libraryImporter = new LibraryBookImporter(context);
|
||||
var newCount = await Task.Run(() => libraryImporter.Import(importItems));
|
||||
context.SaveChanges();
|
||||
logTime("importIntoDbAsync -- post Import()");
|
||||
var qtyChanges = context.SaveChanges();
|
||||
logTime("importIntoDbAsync -- post SaveChanges");
|
||||
|
||||
if (qtyChanges > 0)
|
||||
await Task.Run(() => finalizeLibrarySizeChange());
|
||||
logTime("importIntoDbAsync -- post finalizeLibrarySizeChange");
|
||||
|
||||
return newCount;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region remove books
|
||||
public static Task<List<LibraryBook>> RemoveBooksAsync(List<string> idsToRemove) => Task.Run(() => removeBooks(idsToRemove));
|
||||
private static List<LibraryBook> removeBooks(List<string> idsToRemove)
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
var libBooks = context.GetLibrary_Flat_NoTracking();
|
||||
|
||||
var removeLibraryBooks = libBooks.Where(lb => idsToRemove.Contains(lb.Book.AudibleProductId)).ToList();
|
||||
context.LibraryBooks.RemoveRange(removeLibraryBooks);
|
||||
|
||||
var qtyChanges = context.SaveChanges();
|
||||
if (qtyChanges > 0)
|
||||
finalizeLibrarySizeChange();
|
||||
|
||||
return removeLibraryBooks;
|
||||
}
|
||||
#endregion
|
||||
|
||||
// call this whenever books are added or removed from library
|
||||
private static void finalizeLibrarySizeChange()
|
||||
{
|
||||
SearchEngineCommands.FullReIndex();
|
||||
LibrarySizeChanged?.Invoke(null, null);
|
||||
}
|
||||
|
||||
/// <summary>Occurs when books are added or removed from library</summary>
|
||||
public static event EventHandler LibrarySizeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when <see cref="UserDefinedItem.Tags"/>, <see cref="UserDefinedItem.BookStatus"/>, or <see cref="UserDefinedItem.PdfStatus"/>
|
||||
/// changed values are successfully persisted.
|
||||
/// </summary>
|
||||
public static event EventHandler<string> BookUserDefinedItemCommitted;
|
||||
|
||||
#region Update book details
|
||||
|
||||
public static int UpdateUserDefinedItem(Book book)
|
||||
{
|
||||
try
|
||||
@@ -166,7 +229,10 @@ namespace ApplicationServices
|
||||
context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||
var qtyChanges = context.SaveChanges();
|
||||
if (qtyChanges > 0)
|
||||
{
|
||||
SearchEngineCommands.UpdateLiberatedStatus(book);
|
||||
BookUserDefinedItemCommitted?.Invoke(null, book.AudibleProductId);
|
||||
}
|
||||
|
||||
return qtyChanges;
|
||||
}
|
||||
@@ -178,22 +244,21 @@ namespace ApplicationServices
|
||||
}
|
||||
#endregion
|
||||
|
||||
// must be here instead of in db layer due to AaxcExists
|
||||
public static LiberatedStatus Liberated_Status(Book book)
|
||||
=> book.Audio_Exists ? LiberatedStatus.Liberated
|
||||
=> book.Audio_Exists ? book.UserDefinedItem.BookStatus
|
||||
: FileManager.AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
|
||||
: LiberatedStatus.NotLiberated;
|
||||
|
||||
public static LiberatedStatus? Pdf_Status(Book book)
|
||||
=> !book.HasPdf ? null
|
||||
: book.PDF_Exists ? LiberatedStatus.Liberated
|
||||
: LiberatedStatus.NotLiberated;
|
||||
// exists here for feature predictability. It makes sense for this to be where Liberated_Status is
|
||||
public static LiberatedStatus? Pdf_Status(Book book) => book.UserDefinedItem.PdfStatus;
|
||||
|
||||
// below are queries, not commands. maybe I should make a LibraryQueries. except there's already one of those...
|
||||
|
||||
public record LibraryStats(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress, int booksError, int pdfsDownloaded, int pdfsNotDownloaded) { }
|
||||
public static LibraryStats GetCounts()
|
||||
{
|
||||
var libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking();
|
||||
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
|
||||
var results = libraryBooks
|
||||
.AsParallel()
|
||||
|
||||
@@ -89,11 +89,14 @@ namespace ApplicationServices
|
||||
[Name("My Libation Tags")]
|
||||
public string MyLibationTags { get; set; }
|
||||
|
||||
[Name("Book Liberation Status")]
|
||||
[Name("Book Liberated Status")]
|
||||
public string BookStatus { get; set; }
|
||||
|
||||
[Name("PDF Liberation Status")]
|
||||
[Name("PDF Liberated Status")]
|
||||
public string PdfStatus { get; set; }
|
||||
|
||||
[Name("Content Type")]
|
||||
public string ContentType { get; set; }
|
||||
}
|
||||
public static class LibToDtos
|
||||
{
|
||||
@@ -124,16 +127,15 @@ namespace ApplicationServices
|
||||
MyRatingStory = a.Book.UserDefinedItem.Rating.StoryRating,
|
||||
MyLibationTags = a.Book.UserDefinedItem.Tags,
|
||||
BookStatus = a.Book.UserDefinedItem.BookStatus.ToString(),
|
||||
PdfStatus = a.Book.UserDefinedItem.PdfStatus.ToString()
|
||||
PdfStatus = a.Book.UserDefinedItem.PdfStatus.ToString(),
|
||||
ContentType = a.Book.ContentType.ToString()
|
||||
}).ToList();
|
||||
}
|
||||
public static class LibraryExporter
|
||||
{
|
||||
public static void ToCsv(string saveFilePath)
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
var dtos = context.GetLibrary_Flat_NoTracking().ToDtos();
|
||||
|
||||
var dtos = DbContexts.GetLibrary_Flat_NoTracking().ToDtos();
|
||||
if (!dtos.Any())
|
||||
return;
|
||||
|
||||
@@ -147,17 +149,14 @@ namespace ApplicationServices
|
||||
|
||||
public static void ToJson(string saveFilePath)
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
var dtos = context.GetLibrary_Flat_NoTracking().ToDtos();
|
||||
|
||||
var dtos = DbContexts.GetLibrary_Flat_NoTracking().ToDtos();
|
||||
var json = Newtonsoft.Json.JsonConvert.SerializeObject(dtos, Newtonsoft.Json.Formatting.Indented);
|
||||
System.IO.File.WriteAllText(saveFilePath, json);
|
||||
}
|
||||
|
||||
public static void ToXlsx(string saveFilePath)
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
var dtos = context.GetLibrary_Flat_NoTracking().ToDtos();
|
||||
var dtos = DbContexts.GetLibrary_Flat_NoTracking().ToDtos();
|
||||
|
||||
var workbook = new XSSFWorkbook();
|
||||
var sheet = workbook.CreateSheet("Library");
|
||||
@@ -197,7 +196,8 @@ namespace ApplicationServices
|
||||
nameof (ExportDto.MyRatingStory),
|
||||
nameof (ExportDto.MyLibationTags),
|
||||
nameof (ExportDto.BookStatus),
|
||||
nameof (ExportDto.PdfStatus)
|
||||
nameof (ExportDto.PdfStatus),
|
||||
nameof (ExportDto.ContentType)
|
||||
};
|
||||
var col = 0;
|
||||
foreach (var c in columns)
|
||||
@@ -261,6 +261,7 @@ namespace ApplicationServices
|
||||
row.CreateCell(col++).SetCellValue(dto.MyLibationTags);
|
||||
row.CreateCell(col++).SetCellValue(dto.BookStatus);
|
||||
row.CreateCell(col++).SetCellValue(dto.PdfStatus);
|
||||
row.CreateCell(col++).SetCellValue(dto.ContentType);
|
||||
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace ApplicationServices
|
||||
public static void FullReIndex(SearchEngine engine = null)
|
||||
{
|
||||
engine ??= new SearchEngine();
|
||||
engine.CreateNewIndex(DbContexts.GetContext());
|
||||
var library = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
engine.CreateNewIndex(library);
|
||||
}
|
||||
|
||||
public static SearchResultSet Search(string searchString) => performSearchEngineFunc_safe(e =>
|
||||
@@ -39,17 +40,17 @@ namespace ApplicationServices
|
||||
}
|
||||
}
|
||||
|
||||
private static T performSearchEngineFunc_safe<T>(Func<SearchEngine, T> action)
|
||||
private static T performSearchEngineFunc_safe<T>(Func<SearchEngine, T> func)
|
||||
{
|
||||
var engine = new SearchEngine();
|
||||
try
|
||||
{
|
||||
return action(engine);
|
||||
return func(engine);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
FullReIndex(engine);
|
||||
return action(engine);
|
||||
return func(engine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,20 @@ namespace DataLayer.Configurations
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<LibraryBook> entity)
|
||||
{
|
||||
// to allow same book (incl region) with diff acct.s:
|
||||
//
|
||||
// this file:
|
||||
// - composite key:
|
||||
// entity.HasKey(b => new { b.BookId, b.Account });
|
||||
// entity.HasIndex(b => b.BookId);
|
||||
// entity.HasIndex(b => b.Account);
|
||||
// - change the below relationship since Book+LibraryBook would no longer be 1:1
|
||||
//
|
||||
// other files:
|
||||
// - change Book class since Book+LibraryBook would no longer be 1:1
|
||||
// - update LibraryBook import code
|
||||
// - would likely challenge assumptions throughout Libation which have been true up until now
|
||||
|
||||
entity.HasKey(b => b.BookId);
|
||||
|
||||
entity
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="1.0.2.2" />
|
||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="1.0.5.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.9">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace DataLayer
|
||||
Id = id;
|
||||
}
|
||||
}
|
||||
|
||||
// enum will be easier than bool to extend later
|
||||
public enum ContentType { Unknown = 0, Product = 1, Episode = 2 }
|
||||
|
||||
public class Book
|
||||
{
|
||||
// implementation detail. set by db only. only used by data layer
|
||||
@@ -25,8 +29,7 @@ namespace DataLayer
|
||||
public string Title { get; private set; }
|
||||
public string Description { get; private set; }
|
||||
public int LengthInMinutes { get; private set; }
|
||||
|
||||
// immutable-ish. should be immutable. mutability is necessary for v3 => v4 upgrades
|
||||
public ContentType ContentType { get; private set; }
|
||||
public string Locale { get; private set; }
|
||||
|
||||
// mutable
|
||||
@@ -52,22 +55,10 @@ namespace DataLayer
|
||||
public UserDefinedItem UserDefinedItem { get; private set; }
|
||||
|
||||
// UserDefinedItem convenience properties
|
||||
public bool Audio_Exists
|
||||
{
|
||||
get
|
||||
{
|
||||
var status = UserDefinedItem?.BookStatus;
|
||||
return status.HasValue && status.Value != LiberatedStatus.NotLiberated;
|
||||
}
|
||||
}
|
||||
public bool PDF_Exists
|
||||
{
|
||||
get
|
||||
{
|
||||
var status = UserDefinedItem?.PdfStatus;
|
||||
return (status.HasValue && status.Value == LiberatedStatus.Liberated);
|
||||
}
|
||||
}
|
||||
/// <summary>True if IsLiberated or Error. False if NotLiberated</summary>
|
||||
public bool Audio_Exists => UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated;
|
||||
/// <summary>True if exists and IsLiberated. Else false</summary>
|
||||
public bool PDF_Exists => UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
|
||||
|
||||
// is owned, not optional 1:1
|
||||
/// <summary>The product's aggregate community rating</summary>
|
||||
@@ -82,9 +73,11 @@ namespace DataLayer
|
||||
string title,
|
||||
string description,
|
||||
int lengthInMinutes,
|
||||
ContentType contentType,
|
||||
IEnumerable<Contributor> authors,
|
||||
IEnumerable<Contributor> narrators,
|
||||
Category category, string localeName)
|
||||
Category category,
|
||||
string localeName)
|
||||
{
|
||||
// validate
|
||||
ArgumentValidator.EnsureNotNull(audibleProductId, nameof(audibleProductId));
|
||||
@@ -109,6 +102,7 @@ namespace DataLayer
|
||||
Title = title.Trim();
|
||||
Description = description.Trim();
|
||||
LengthInMinutes = lengthInMinutes;
|
||||
ContentType = contentType;
|
||||
|
||||
// assigns with biz logic
|
||||
ReplaceAuthors(authors);
|
||||
@@ -265,10 +259,6 @@ namespace DataLayer
|
||||
Category = category;
|
||||
}
|
||||
|
||||
// needed for v3 => v4 upgrade
|
||||
public void UpdateLocale(string localeName)
|
||||
=> Locale ??= localeName;
|
||||
|
||||
public override string ToString() => $"[{AudibleProductId}] {Title}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ namespace DataLayer
|
||||
public Book Book { get; private set; }
|
||||
|
||||
public DateTime DateAdded { get; private set; }
|
||||
|
||||
// immutable-ish. should be immutable. mutability is necessary for v3 => v4 upgrades
|
||||
public string Account { get; private set; }
|
||||
|
||||
private LibraryBook() { }
|
||||
@@ -24,10 +22,6 @@ namespace DataLayer
|
||||
Account = account;
|
||||
}
|
||||
|
||||
// needed for v3 => v4 upgrade
|
||||
public void UpdateAccount(string account)
|
||||
=> Account ??= account;
|
||||
|
||||
public override string ToString() => $"{DateAdded:d} {Book}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ namespace DataLayer
|
||||
|
||||
/// <summary>Application-state only. Not a valid persistence state.</summary>
|
||||
PartialDownload = 0x1000
|
||||
|
||||
}
|
||||
|
||||
public class UserDefinedItem
|
||||
@@ -137,6 +136,7 @@ namespace DataLayer
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// Occurs when <see cref="Tags"/>, <see cref="BookStatus"/>, or <see cref="PdfStatus"/> values change.
|
||||
/// This signals the change of the in-memory value; it does not ensure that the new value has been persisted.
|
||||
/// </summary>
|
||||
public static event EventHandler<string> ItemChanged;
|
||||
public override string ToString() => $"{Book} {Rating} {Tags}";
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace DataLayer
|
||||
// // overwrite collection
|
||||
// Entry(product).Collection(x => x.Narrators).Load();
|
||||
// product.Narrators = narrators;
|
||||
public DbSet<LibraryBook> Library { get; private set; }
|
||||
public DbSet<LibraryBook> LibraryBooks { get; private set; }
|
||||
public DbSet<Book> Books { get; private set; }
|
||||
public DbSet<Contributor> Contributors { get; private set; }
|
||||
public DbSet<Series> Series { get; private set; }
|
||||
|
||||
390
DataLayer/Migrations/20210901205042_BookIsEpisode.Designer.cs
generated
Normal file
390
DataLayer/Migrations/20210901205042_BookIsEpisode.Designer.cs
generated
Normal file
@@ -0,0 +1,390 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DataLayer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
[DbContext(typeof(LibationContext))]
|
||||
[Migration("20210901205042_BookIsEpisode")]
|
||||
partial class BookIsEpisode
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.9");
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleProductId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContentType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DatePublished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAbridged")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LengthInMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Locale")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.HasIndex("AudibleProductId");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Books");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContributorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId", "ContributorId", "Role");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("ContributorId");
|
||||
|
||||
b.ToTable("BookContributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.Property<int>("CategoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleCategoryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ParentCategoryCategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CategoryId");
|
||||
|
||||
b.HasIndex("AudibleCategoryId");
|
||||
|
||||
b.HasIndex("ParentCategoryCategoryId");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
CategoryId = -1,
|
||||
AudibleCategoryId = "",
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Property<int>("ContributorId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleContributorId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ContributorId");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("Contributors");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
ContributorId = -1,
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.ToTable("Library");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleSeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId");
|
||||
|
||||
b.HasIndex("AudibleSeriesId");
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float?>("Index")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("SeriesId", "BookId");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("SeriesBook");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsOne("DataLayer.Rating", "Rating", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("Books");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("BookId");
|
||||
});
|
||||
|
||||
b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
|
||||
{
|
||||
b1.Property<int>("SupplementId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("SupplementId");
|
||||
|
||||
b1.HasIndex("BookId");
|
||||
|
||||
b1.ToTable("Supplement");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.Navigation("Book");
|
||||
});
|
||||
|
||||
b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int?>("PdfStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Tags")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("UserDefinedItem");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
|
||||
{
|
||||
b2.Property<int>("UserDefinedItemBookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b2.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.HasKey("UserDefinedItemBookId");
|
||||
|
||||
b2.ToTable("UserDefinedItem");
|
||||
|
||||
b2.WithOwner()
|
||||
.HasForeignKey("UserDefinedItemBookId");
|
||||
});
|
||||
|
||||
b1.Navigation("Book");
|
||||
|
||||
b1.Navigation("Rating");
|
||||
});
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Rating");
|
||||
|
||||
b.Navigation("Supplements");
|
||||
|
||||
b.Navigation("UserDefinedItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("ContributorsLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Contributor", "Contributor")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("ContributorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Contributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", "ParentCategory")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentCategoryCategoryId");
|
||||
|
||||
b.Navigation("ParentCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithOne()
|
||||
.HasForeignKey("DataLayer.LibraryBook", "BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("SeriesLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Series", "Series")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Navigation("ContributorsLink");
|
||||
|
||||
b.Navigation("SeriesLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
24
DataLayer/Migrations/20210901205042_BookIsEpisode.cs
Normal file
24
DataLayer/Migrations/20210901205042_BookIsEpisode.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
public partial class BookIsEpisode : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "ContentType",
|
||||
table: "Books",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ContentType",
|
||||
table: "Books");
|
||||
}
|
||||
}
|
||||
}
|
||||
390
DataLayer/Migrations/20210902192153_RenameLibraryBooks.Designer.cs
generated
Normal file
390
DataLayer/Migrations/20210902192153_RenameLibraryBooks.Designer.cs
generated
Normal file
@@ -0,0 +1,390 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using DataLayer;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
[DbContext(typeof(LibationContext))]
|
||||
[Migration("20210902192153_RenameLibraryBooks")]
|
||||
partial class RenameLibraryBooks
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.9");
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleProductId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContentType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DatePublished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsAbridged")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("LengthInMinutes")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Locale")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PictureId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.HasIndex("AudibleProductId");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.ToTable("Books");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContributorId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<byte>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId", "ContributorId", "Role");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("ContributorId");
|
||||
|
||||
b.ToTable("BookContributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.Property<int>("CategoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleCategoryId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ParentCategoryCategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("CategoryId");
|
||||
|
||||
b.HasIndex("AudibleCategoryId");
|
||||
|
||||
b.HasIndex("ParentCategoryCategoryId");
|
||||
|
||||
b.ToTable("Categories");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
CategoryId = -1,
|
||||
AudibleCategoryId = "",
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Property<int>("ContributorId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleContributorId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("ContributorId");
|
||||
|
||||
b.HasIndex("Name");
|
||||
|
||||
b.ToTable("Contributors");
|
||||
|
||||
b.HasData(
|
||||
new
|
||||
{
|
||||
ContributorId = -1,
|
||||
Name = ""
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Account")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.ToTable("LibraryBooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AudibleSeriesId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("SeriesId");
|
||||
|
||||
b.HasIndex("AudibleSeriesId");
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.Property<int>("SeriesId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<float?>("Index")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.HasKey("SeriesId", "BookId");
|
||||
|
||||
b.HasIndex("BookId");
|
||||
|
||||
b.HasIndex("SeriesId");
|
||||
|
||||
b.ToTable("SeriesBook");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.OwnsOne("DataLayer.Rating", "Rating", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("Books");
|
||||
|
||||
b1.WithOwner()
|
||||
.HasForeignKey("BookId");
|
||||
});
|
||||
|
||||
b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
|
||||
{
|
||||
b1.Property<int>("SupplementId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Url")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("SupplementId");
|
||||
|
||||
b1.HasIndex("BookId");
|
||||
|
||||
b1.ToTable("Supplement");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.Navigation("Book");
|
||||
});
|
||||
|
||||
b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 =>
|
||||
{
|
||||
b1.Property<int>("BookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int>("BookStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<int?>("PdfStatus")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b1.Property<string>("Tags")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b1.HasKey("BookId");
|
||||
|
||||
b1.ToTable("UserDefinedItem");
|
||||
|
||||
b1.WithOwner("Book")
|
||||
.HasForeignKey("BookId");
|
||||
|
||||
b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
|
||||
{
|
||||
b2.Property<int>("UserDefinedItemBookId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b2.Property<float>("OverallRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("PerformanceRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.Property<float>("StoryRating")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b2.HasKey("UserDefinedItemBookId");
|
||||
|
||||
b2.ToTable("UserDefinedItem");
|
||||
|
||||
b2.WithOwner()
|
||||
.HasForeignKey("UserDefinedItemBookId");
|
||||
});
|
||||
|
||||
b1.Navigation("Book");
|
||||
|
||||
b1.Navigation("Rating");
|
||||
});
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("Rating");
|
||||
|
||||
b.Navigation("Supplements");
|
||||
|
||||
b.Navigation("UserDefinedItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("ContributorsLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Contributor", "Contributor")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("ContributorId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Contributor");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Category", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Category", "ParentCategory")
|
||||
.WithMany()
|
||||
.HasForeignKey("ParentCategoryCategoryId");
|
||||
|
||||
b.Navigation("ParentCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithOne()
|
||||
.HasForeignKey("DataLayer.LibraryBook", "BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||
{
|
||||
b.HasOne("DataLayer.Book", "Book")
|
||||
.WithMany("SeriesLink")
|
||||
.HasForeignKey("BookId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DataLayer.Series", "Series")
|
||||
.WithMany("BooksLink")
|
||||
.HasForeignKey("SeriesId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Book");
|
||||
|
||||
b.Navigation("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
b.Navigation("ContributorsLink");
|
||||
|
||||
b.Navigation("SeriesLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
{
|
||||
b.Navigation("BooksLink");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
63
DataLayer/Migrations/20210902192153_RenameLibraryBooks.cs
Normal file
63
DataLayer/Migrations/20210902192153_RenameLibraryBooks.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace DataLayer.Migrations
|
||||
{
|
||||
public partial class RenameLibraryBooks : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Library_Books_BookId",
|
||||
table: "Library");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_Library",
|
||||
table: "Library");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "Library",
|
||||
newName: "LibraryBooks");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_LibraryBooks",
|
||||
table: "LibraryBooks",
|
||||
column: "BookId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_LibraryBooks_Books_BookId",
|
||||
table: "LibraryBooks",
|
||||
column: "BookId",
|
||||
principalTable: "Books",
|
||||
principalColumn: "BookId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_LibraryBooks_Books_BookId",
|
||||
table: "LibraryBooks");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_LibraryBooks",
|
||||
table: "LibraryBooks");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "LibraryBooks",
|
||||
newName: "Library");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_Library",
|
||||
table: "Library",
|
||||
column: "BookId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_Library_Books_BookId",
|
||||
table: "Library",
|
||||
column: "BookId",
|
||||
principalTable: "Books",
|
||||
principalColumn: "BookId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace DataLayer.Migrations
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.5");
|
||||
.HasAnnotation("ProductVersion", "5.0.9");
|
||||
|
||||
modelBuilder.Entity("DataLayer.Book", b =>
|
||||
{
|
||||
@@ -28,6 +28,9 @@ namespace DataLayer.Migrations
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ContentType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("DatePublished")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
@@ -152,7 +155,7 @@ namespace DataLayer.Migrations
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.ToTable("Library");
|
||||
b.ToTable("LibraryBooks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DataLayer.Series", b =>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace DataLayer
|
||||
{
|
||||
public static class GenericPaging
|
||||
{
|
||||
public static IQueryable<T> Page<T>(this IQueryable<T> query, int pageNumZeroStart, int pageSize)
|
||||
{
|
||||
if (pageSize < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(pageSize), "pageSize must be at least 1");
|
||||
|
||||
if (pageNumZeroStart > 0)
|
||||
query = query.Skip(pageNumZeroStart * pageSize);
|
||||
|
||||
return query.Take(pageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ namespace DataLayer
|
||||
{
|
||||
// only library importing should use tracking. All else should be NoTracking.
|
||||
// only library importing should directly query Book. All else should use LibraryBook
|
||||
public static class LibraryQueries
|
||||
public static class LibraryBookQueries
|
||||
{
|
||||
//// tracking is a bad idea for main grid. it prevents anything else from updating entities unless getting them from the grid
|
||||
//public static List<LibraryBook> GetLibrary_Flat_WithTracking(this LibationContext context)
|
||||
@@ -17,14 +17,14 @@ namespace DataLayer
|
||||
|
||||
public static List<LibraryBook> GetLibrary_Flat_NoTracking(this LibationContext context)
|
||||
=> context
|
||||
.Library
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
.GetLibrary()
|
||||
.ToList();
|
||||
|
||||
public static LibraryBook GetLibraryBook_Flat_NoTracking(this LibationContext context, string productId)
|
||||
=> context
|
||||
.Library
|
||||
.LibraryBooks
|
||||
.AsNoTrackingWithIdentityResolution()
|
||||
.GetLibraryBook(productId);
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
FOR QUICK MIGRATION INSTRUCTIONS:
|
||||
_DB_NOTES.txt
|
||||
|
||||
|
||||
HOW TO CREATE: EF CORE PROJECT
|
||||
==============================
|
||||
example is for sqlite but the same works with MsSql
|
||||
|
||||
|
||||
nuget
|
||||
Microsoft.EntityFrameworkCore.Tools (needed for using Package Manager Console)
|
||||
Microsoft.EntityFrameworkCore.Sqlite
|
||||
|
||||
MIGRATIONS
|
||||
require core, not standard
|
||||
this can be a problem b/c standard and framework can only reference standard, not core
|
||||
TO USE MIGRATIONS (core and/or standard)
|
||||
add to csproj
|
||||
<PropertyGroup>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
</PropertyGroup>
|
||||
TO USE MIGRATIONS AS *BOTH* CORE AND STANDARD
|
||||
edit csproj
|
||||
pluralize this xml tag
|
||||
from: TargetFramework
|
||||
to: TargetFrameworks
|
||||
inside of TargetFrameworks
|
||||
from: netstandard2.1
|
||||
to: netcoreapp3.1;netstandard2.1
|
||||
|
||||
run. error
|
||||
SQLite Error 1: 'no such table: Blogs'.
|
||||
|
||||
set project "Set as StartUp Project"
|
||||
|
||||
Tools >> Nuget Package Manager >> Package Manager Console
|
||||
default project: Examples\SQLite_NETCore2_0
|
||||
|
||||
PM> add-migration InitialCreate
|
||||
PM> Update-Database
|
||||
|
||||
if add-migration xyz throws and error, don't take the error msg at face value. try again with add-migration xyz -verbose
|
||||
|
||||
new sqlite .db file created: Copy always/Copy if newer
|
||||
or copy .db file to destination
|
||||
|
||||
relative:
|
||||
optionsBuilder.UseSqlite("Data Source=blogging.db");
|
||||
absolute (use fwd slashes):
|
||||
optionsBuilder.UseSqlite("Data Source=C:/foo/bar/blogging.db");
|
||||
|
||||
|
||||
REFERENCE ARTICLES
|
||||
------------------
|
||||
https://docs.microsoft.com/en-us/ef/core/get-started/netcore/new-db-sqlite
|
||||
https://carlos.mendible.com/2016/07/11/step-by-step-dotnet-core-and-entity-framework-core/
|
||||
https://www.benday.com/2017/12/19/ef-core-2-0-migrations-without-hard-coded-connection-strings/
|
||||
@@ -19,13 +19,13 @@ namespace DtoImporterService
|
||||
new ContributorImporter(DbContext).Import(importItems);
|
||||
new SeriesImporter(DbContext).Import(importItems);
|
||||
new CategoryImporter(DbContext).Import(importItems);
|
||||
|
||||
|
||||
// get distinct
|
||||
var productIds = importItems.Select(i => i.DtoItem.ProductId).ToList();
|
||||
|
||||
var productIds = importItems.Select(i => i.DtoItem.ProductId).Distinct().ToList();
|
||||
|
||||
// load db existing => .Local
|
||||
loadLocal_books(productIds);
|
||||
|
||||
|
||||
// upsert
|
||||
var qtyNew = upsertBooks(importItems);
|
||||
return qtyNew;
|
||||
@@ -33,12 +33,30 @@ namespace DtoImporterService
|
||||
|
||||
private void loadLocal_books(List<string> productIds)
|
||||
{
|
||||
var localProductIds = DbContext.Books.Local.Select(b => b.AudibleProductId);
|
||||
// if this context has already loaded books, don't need to reload them. vestige from when context was long-lived. in practice, we now typically use a fresh context. this is quick though so no harm in leaving it.
|
||||
var localProductIds = DbContext.Books.Local.Select(b => b.AudibleProductId).ToList();
|
||||
var remainingProductIds = productIds
|
||||
.Distinct()
|
||||
.Except(localProductIds)
|
||||
.ToList();
|
||||
|
||||
#region // explanation of DbContext.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList();
|
||||
/*
|
||||
articles suggest loading to Local with
|
||||
context.Books.Load();
|
||||
we want Books and associated fields
|
||||
context.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList();
|
||||
this is emulating Load() but with also getting associated fields
|
||||
|
||||
from: Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
|
||||
// Summary:
|
||||
// Enumerates the query. When using Entity Framework, this causes the results of
|
||||
// the query to be loaded into the associated context. This is equivalent to calling
|
||||
// ToList and then throwing away the list (without the overhead of actually creating
|
||||
// the list).
|
||||
public static void Load<TSource>([NotNullAttribute] this IQueryable<TSource> source);
|
||||
*/
|
||||
#endregion
|
||||
|
||||
// GetBooks() eager loads Series, category, et al
|
||||
if (remainingProductIds.Any())
|
||||
DbContext.Books.GetBooks(b => remainingProductIds.Contains(b.AudibleProductId)).ToList();
|
||||
@@ -67,6 +85,8 @@ namespace DtoImporterService
|
||||
{
|
||||
var item = importItem.DtoItem;
|
||||
|
||||
var contentType = item.IsEpisodes ? DataLayer.ContentType.Episode : DataLayer.ContentType.Product;
|
||||
|
||||
// absence of authors is very rare, but possible
|
||||
if (!item.Authors?.Any() ?? true)
|
||||
item.Authors = new[] { new Person { Name = "", Asin = null } };
|
||||
@@ -105,6 +125,7 @@ namespace DtoImporterService
|
||||
item.TitleWithSubtitle,
|
||||
item.Description,
|
||||
item.LengthInMinutes,
|
||||
contentType,
|
||||
authors,
|
||||
narrators,
|
||||
category,
|
||||
@@ -134,9 +155,6 @@ namespace DtoImporterService
|
||||
book.PictureId = item.PictureId;
|
||||
book.UpdateProductRating(item.Product_OverallStars, item.Product_PerformanceStars, item.Product_StoryStars);
|
||||
|
||||
// needed during v3 => v4 migration
|
||||
book.UpdateLocale(importItem.LocaleName);
|
||||
|
||||
// important to update user-specific info. this will have changed if user has rated/reviewed the book since last library import
|
||||
book.UserDefinedItem.UpdateRating(item.MyUserRating_Overall, item.MyUserRating_Performance, item.MyUserRating_Story);
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace DtoImporterService
|
||||
|
||||
private void loadLocal_categories(List<string> categoryIds)
|
||||
{
|
||||
var localIds = DbContext.Categories.Local.Select(c => c.AudibleCategoryId);
|
||||
var localIds = DbContext.Categories.Local.Select(c => c.AudibleCategoryId).ToList();
|
||||
var remainingCategoryIds = categoryIds
|
||||
.Distinct()
|
||||
.Except(localIds)
|
||||
|
||||
@@ -47,57 +47,61 @@ namespace DtoImporterService
|
||||
|
||||
private void loadLocal_contributors(List<string> contributorNames)
|
||||
{
|
||||
// must include default/empty/missing
|
||||
contributorNames.Add(Contributor.GetEmpty().Name);
|
||||
|
||||
//// BAD: very inefficient
|
||||
// var x = context.Contributors.Local.Where(c => !contribNames.Contains(c.Name));
|
||||
|
||||
// GOOD: Except() is efficient. Due to hashing, it's close to O(n)
|
||||
var localNames = DbContext.Contributors.Local.Select(c => c.Name);
|
||||
var localNames = DbContext.Contributors.Local.Select(c => c.Name).ToList();
|
||||
var remainingContribNames = contributorNames
|
||||
.Distinct()
|
||||
.Except(localNames)
|
||||
.ToList();
|
||||
|
||||
// load existing => local
|
||||
// remember to include default/empty/missing
|
||||
var emptyName = Contributor.GetEmpty().Name;
|
||||
if (remainingContribNames.Any())
|
||||
DbContext.Contributors.Where(c => remainingContribNames.Contains(c.Name) || c.Name == emptyName).ToList();
|
||||
DbContext.Contributors.Where(c => remainingContribNames.Contains(c.Name)).ToList();
|
||||
}
|
||||
|
||||
// only use after loading contributors => local
|
||||
private int upsertPeople(List<Person> people)
|
||||
{
|
||||
var qtyNew = 0;
|
||||
var localNames = DbContext.Contributors.Local.Select(c => c.Name).ToList();
|
||||
var newPeople = people
|
||||
.Select(p => p.Name)
|
||||
.Distinct()
|
||||
.Except(localNames)
|
||||
.ToList();
|
||||
|
||||
foreach (var p in people)
|
||||
var groupby = people.GroupBy(
|
||||
p => p.Name,
|
||||
p => p,
|
||||
(key, g) => new { Name = key, People = g.ToList() }
|
||||
);
|
||||
foreach (var name in newPeople)
|
||||
{
|
||||
// Should be 'Single' not 'First'. A user had a duplicate get in somehow though so I'm now using 'First' defensively
|
||||
var person = DbContext.Contributors.Local.FirstOrDefault(c => c.Name == p.Name);
|
||||
if (person == null)
|
||||
{
|
||||
person = DbContext.Contributors.Add(new Contributor(p.Name, p.Asin)).Entity;
|
||||
qtyNew++;
|
||||
}
|
||||
var p = groupby.Single(g => g.Name == name).People.First();
|
||||
DbContext.Contributors.Add(new Contributor(p.Name, p.Asin));
|
||||
}
|
||||
|
||||
return qtyNew;
|
||||
return newPeople.Count;
|
||||
}
|
||||
|
||||
// only use after loading contributors => local
|
||||
private int upsertPublishers(List<string> publishers)
|
||||
{
|
||||
var qtyNew = 0;
|
||||
var localNames = DbContext.Contributors.Local.Select(c => c.Name).ToList();
|
||||
var newPublishers = publishers
|
||||
.Distinct()
|
||||
.Except(localNames)
|
||||
.ToList();
|
||||
|
||||
foreach (var publisherName in publishers)
|
||||
{
|
||||
if (DbContext.Contributors.Local.SingleOrDefault(c => c.Name == publisherName) == null)
|
||||
{
|
||||
DbContext.Contributors.Add(new Contributor(publisherName));
|
||||
qtyNew++;
|
||||
}
|
||||
}
|
||||
foreach (var pub in newPublishers)
|
||||
DbContext.Contributors.Add(new Contributor(pub));
|
||||
|
||||
return qtyNew;
|
||||
return newPublishers.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AudibleApi.Common;
|
||||
using DataLayer;
|
||||
using InternalUtilities;
|
||||
|
||||
namespace DtoImporterService
|
||||
{
|
||||
public class LibraryImporter : ItemsImporterBase
|
||||
public class LibraryBookImporter : ItemsImporterBase
|
||||
{
|
||||
public LibraryImporter(LibationContext context) : base(context) { }
|
||||
public LibraryBookImporter(LibationContext context) : base(context) { }
|
||||
|
||||
public override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems) => new LibraryValidator().Validate(importItems.Select(i => i.DtoItem));
|
||||
|
||||
@@ -28,13 +27,14 @@ namespace DtoImporterService
|
||||
// - what to show in the grid
|
||||
// - which to consider liberated
|
||||
//
|
||||
// sqlite cannot alter pk. the work around is an extensive headache. it'll be fixed in pre .net5/efcore5
|
||||
// sqlite cannot alter pk. the work around is an extensive headache
|
||||
// - update: now possible in .net5/efcore5
|
||||
//
|
||||
// currently, inserting LibraryBook will throw error if the same book is in multiple accounts for the same region.
|
||||
//
|
||||
// CURRENT SOLUTION: don't re-insert
|
||||
|
||||
var currentLibraryProductIds = DbContext.Library.Select(l => l.Book.AudibleProductId).ToList();
|
||||
var currentLibraryProductIds = DbContext.LibraryBooks.Select(l => l.Book.AudibleProductId).ToList();
|
||||
var newItems = importItems.Where(dto => !currentLibraryProductIds.Contains(dto.DtoItem.ProductId)).ToList();
|
||||
|
||||
foreach (var newItem in newItems)
|
||||
@@ -43,16 +43,7 @@ namespace DtoImporterService
|
||||
DbContext.Books.Local.Single(b => b.AudibleProductId == newItem.DtoItem.ProductId),
|
||||
newItem.DtoItem.DateAdded,
|
||||
newItem.AccountId);
|
||||
DbContext.Library.Add(libraryBook);
|
||||
}
|
||||
|
||||
// needed for v3 => v4 upgrade
|
||||
var toUpdate = DbContext.Library.Where(l => l.Account == null);
|
||||
foreach (var u in toUpdate)
|
||||
{
|
||||
var item = importItems.FirstOrDefault(ii => ii.DtoItem.ProductId == u.Book.AudibleProductId);
|
||||
if (item != null)
|
||||
u.UpdateAccount(item.AccountId);
|
||||
DbContext.LibraryBooks.Add(libraryBook);
|
||||
}
|
||||
|
||||
var qtyNew = newItems.Count;
|
||||
34
DtoImporterService/PerfLogger.cs
Normal file
34
DtoImporterService/PerfLogger.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace DtoImporterService
|
||||
{
|
||||
public record timeLogEntry(string msg, long totalElapsed, long delta);
|
||||
public static class PerfLogger
|
||||
{
|
||||
private static Stopwatch sw = new Stopwatch();
|
||||
private static List<timeLogEntry> __log { get; } = new List<timeLogEntry> { new("begin", 0, 0) };
|
||||
|
||||
public static void logTime(string s)
|
||||
{
|
||||
var totalElapsed = sw.ElapsedMilliseconds;
|
||||
|
||||
var prev = __log.Last().totalElapsed;
|
||||
var delta = totalElapsed - prev;
|
||||
|
||||
__log.Add(new(s, totalElapsed, delta));
|
||||
}
|
||||
public static void logRestart()
|
||||
{
|
||||
__log.Clear();
|
||||
__log.Add(new("begin", 0, 0));
|
||||
sw.Restart();
|
||||
}
|
||||
public static void stop() => sw.Stop();
|
||||
public static string logOutput =>
|
||||
$"{nameof(timeLogEntry.msg)}\t{nameof(timeLogEntry.totalElapsed)}\t{nameof(timeLogEntry.delta)}\r\n"
|
||||
+ __log.Select(t => $"{t.msg}\t{t.totalElapsed}\t{t.delta}").Aggregate((a, b) => $"{a}\r\n{b}");
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ namespace FileManager
|
||||
|
||||
private PersistentDictionary persistentDictionary;
|
||||
|
||||
public T GetNonString<T>(string propertyName) => persistentDictionary.GetNonString<T>(propertyName);
|
||||
public object GetObject(string propertyName) => persistentDictionary.GetObject(propertyName);
|
||||
public void SetObject(string propertyName, object newValue) => persistentDictionary.SetNonString(propertyName, newValue);
|
||||
|
||||
@@ -67,39 +68,7 @@ namespace FileManager
|
||||
return attribute?.Description;
|
||||
}
|
||||
|
||||
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
|
||||
|
||||
#region MainForm: X, Y, Width, Height, MainFormIsMaximized
|
||||
public int MainFormX
|
||||
{
|
||||
get => persistentDictionary.GetNonString<int>(nameof(MainFormX));
|
||||
set => persistentDictionary.SetNonString(nameof(MainFormX), value);
|
||||
}
|
||||
|
||||
public int MainFormY
|
||||
{
|
||||
get => persistentDictionary.GetNonString<int>(nameof(MainFormY));
|
||||
set => persistentDictionary.SetNonString(nameof(MainFormY), value);
|
||||
}
|
||||
|
||||
public int MainFormWidth
|
||||
{
|
||||
get => persistentDictionary.GetNonString<int>(nameof(MainFormWidth));
|
||||
set => persistentDictionary.SetNonString(nameof(MainFormWidth), value);
|
||||
}
|
||||
|
||||
public int MainFormHeight
|
||||
{
|
||||
get => persistentDictionary.GetNonString<int>(nameof(MainFormHeight));
|
||||
set => persistentDictionary.SetNonString(nameof(MainFormHeight), value);
|
||||
}
|
||||
|
||||
public bool MainFormIsMaximized
|
||||
{
|
||||
get => persistentDictionary.GetNonString<bool>(nameof(MainFormIsMaximized));
|
||||
set => persistentDictionary.SetNonString(nameof(MainFormIsMaximized), value);
|
||||
}
|
||||
#endregion
|
||||
public bool Exists(string propertyName) => persistentDictionary.Exists(propertyName);
|
||||
|
||||
[Description("Location for book storage. Includes destination of newly liberated books")]
|
||||
public string Books
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core" Version="1.0.5.1" />
|
||||
<PackageReference Include="Dinah.Core" Version="1.1.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
|
||||
<PackageReference Include="Octokit" Version="0.50.0" />
|
||||
<PackageReference Include="Polly" Version="7.2.2" />
|
||||
|
||||
@@ -51,7 +51,8 @@ namespace FileManager
|
||||
{
|
||||
var obj = GetObject(propertyName);
|
||||
if (obj is null) return default;
|
||||
if (obj is JToken jToken) return jToken.Value<T>();
|
||||
if (obj is JValue jValue) return jValue.Value<T>();
|
||||
if (obj is JObject jObject) return jObject.ToObject<T>();
|
||||
return (T)obj;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,26 +58,22 @@ namespace InternalUtilities
|
||||
|
||||
private static async Task<List<Item>> getItemsAsync(Api api, LibraryOptions.ResponseGroupOptions responseGroups)
|
||||
{
|
||||
var items = await api.GetAllLibraryItemsAsync(responseGroups);
|
||||
var items = new List<Item>();
|
||||
#if DEBUG
|
||||
//// this will not work for multi accounts
|
||||
//var library_json = "library.json";
|
||||
//if (System.IO.File.Exists(library_json))
|
||||
//{
|
||||
// items = AudibleApi.Common.Converter.FromJson<List<Item>>(System.IO.File.ReadAllText(library_json));
|
||||
//}
|
||||
#endif
|
||||
if (!items.Any())
|
||||
items = await api.GetAllLibraryItemsAsync(responseGroups);
|
||||
#if DEBUG
|
||||
//System.IO.File.WriteAllText("library.json", AudibleApi.Common.Converter.ToJson(items));
|
||||
#endif
|
||||
|
||||
// remove episode parents
|
||||
items.RemoveAll(i => i.IsEpisodes);
|
||||
|
||||
#region // episode handling. doesn't quite work
|
||||
// // add individual/children episodes
|
||||
// var childIds = items
|
||||
// .Where(i => i.Episodes)
|
||||
// .SelectMany(ep => ep.Relationships)
|
||||
// .Where(r => r.RelationshipToProduct == AudibleApi.Common.RelationshipToProduct.Child && r.RelationshipType == AudibleApi.Common.RelationshipType.Episode)
|
||||
// .Select(c => c.Asin)
|
||||
// .ToList();
|
||||
// foreach (var childId in childIds)
|
||||
// {
|
||||
// var bookResult = await api.GetLibraryBookAsync(childId, AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS);
|
||||
// var bookItem = AudibleApi.Common.LibraryDtoV10.FromJson(bookResult.ToString()).Item;
|
||||
// items.Add(bookItem);
|
||||
// }
|
||||
#endregion
|
||||
await manageEpisodesAsync(api, items);
|
||||
|
||||
var validators = new List<IValidator>();
|
||||
validators.AddRange(getValidators());
|
||||
@@ -91,6 +87,143 @@ namespace InternalUtilities
|
||||
return items;
|
||||
}
|
||||
|
||||
#region episodes and podcasts
|
||||
private static async Task manageEpisodesAsync(Api api, List<Item> items)
|
||||
{
|
||||
// add podcasts and episodes to list. If fail, don't let it de-rail the rest of the import
|
||||
try
|
||||
{
|
||||
// get parents
|
||||
var parents = items.Where(i => i.IsEpisodes).ToList();
|
||||
#if DEBUG
|
||||
//var parentsDebug = parents.Select(i => i.ToJson()).Aggregate((a, b) => $"{a}\r\n\r\n{b}");
|
||||
//System.IO.File.WriteAllText("parents.json", parentsDebug);
|
||||
#endif
|
||||
|
||||
if (!parents.Any())
|
||||
return;
|
||||
|
||||
Serilog.Log.Logger.Information($"{parents.Count} series of shows/podcasts found");
|
||||
|
||||
// remove episode parents. even if the following stuff fails, these will still be removed from the collection.
|
||||
// also must happen before processing children because children abuses this flag
|
||||
items.RemoveAll(i => i.IsEpisodes);
|
||||
|
||||
// add children
|
||||
var children = await getEpisodesAsync(api, parents);
|
||||
Serilog.Log.Logger.Information($"{children.Count} episodes of shows/podcasts found");
|
||||
items.AddRange(children);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error adding podcasts and episodes");
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<List<Item>> getEpisodesAsync(Api api, List<Item> parents)
|
||||
{
|
||||
var results = new List<Item>();
|
||||
|
||||
foreach (var parent in parents)
|
||||
{
|
||||
var children = await getEpisodeChildrenAsync(api, parent);
|
||||
|
||||
foreach (var child in children)
|
||||
{
|
||||
// use parent's 'DateAdded'. DateAdded is just a convenience prop for: PurchaseDate.UtcDateTime
|
||||
child.PurchaseDate = parent.PurchaseDate;
|
||||
// parent is essentially a series
|
||||
child.Series = new Series[]
|
||||
{
|
||||
new Series
|
||||
{
|
||||
Asin = parent.Asin,
|
||||
Sequence = parent.Relationships.Single(r => r.Asin == child.Asin).Sort.ToString(),
|
||||
Title = parent.TitleWithSubtitle
|
||||
}
|
||||
};
|
||||
// overload (read: abuse) IsEpisodes flag
|
||||
child.Relationships = new Relationship[]
|
||||
{
|
||||
new Relationship
|
||||
{
|
||||
RelationshipToProduct = RelationshipToProduct.Child,
|
||||
RelationshipType = RelationshipType.Episode
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
results.AddRange(children);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static async Task<List<Item>> getEpisodeChildrenAsync(Api api, Item parent)
|
||||
{
|
||||
var childrenIds = parent.Relationships
|
||||
.Where(r => r.RelationshipToProduct == RelationshipToProduct.Child && r.RelationshipType == RelationshipType.Episode)
|
||||
.Select(r => r.Asin)
|
||||
.ToList();
|
||||
|
||||
// fetch children in batches
|
||||
const int batchSize = 20;
|
||||
|
||||
var results = new List<Item>();
|
||||
|
||||
for (var i = 1; ; i++)
|
||||
{
|
||||
var idBatch = childrenIds.Skip((i - 1) * batchSize).Take(batchSize).ToList();
|
||||
if (!idBatch.Any())
|
||||
break;
|
||||
|
||||
List<Item> childrenBatch;
|
||||
try
|
||||
{
|
||||
childrenBatch = await api.GetCatalogProductsAsync(idBatch, CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
|
||||
#if DEBUG
|
||||
//var childrenBatchDebug = childrenBatch.Select(i => i.ToJson()).Aggregate((a, b) => $"{a}\r\n\r\n{b}");
|
||||
//System.IO.File.WriteAllText($"children of {parent.Asin}.json", childrenBatchDebug);
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, "Error fetching batch of episodes. {@DebugInfo}", new
|
||||
{
|
||||
ParentId = parent.Asin,
|
||||
ParentTitle = parent.Title,
|
||||
BatchNumber = i,
|
||||
ChildIdBatch = idBatch
|
||||
});
|
||||
throw;
|
||||
}
|
||||
|
||||
Serilog.Log.Logger.Debug($"Batch {i}: {childrenBatch.Count} results");
|
||||
// the service returned no results. probably indicates an error. stop running batches
|
||||
if (!childrenBatch.Any())
|
||||
break;
|
||||
|
||||
results.AddRange(childrenBatch);
|
||||
}
|
||||
|
||||
Serilog.Log.Logger.Debug("Parent episodes/podcasts series. Children found. {@DebugInfo}", new
|
||||
{
|
||||
ParentId = parent.Asin,
|
||||
ParentTitle = parent.Title,
|
||||
ChildCount = childrenIds.Count
|
||||
});
|
||||
|
||||
if (childrenIds.Count != results.Count)
|
||||
{
|
||||
var ex = new ApplicationException($"Mis-match: Children defined by parent={childrenIds.Count}. Children returned by batches={results.Count}");
|
||||
Serilog.Log.Logger.Error(ex, "Quantity of series episodes defined by parent does not match quantity returned by batch fetching.");
|
||||
throw ex;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static List<IValidator> getValidators()
|
||||
{
|
||||
var type = typeof(IValidator);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="1.0.6.2" />
|
||||
<PackageReference Include="AudibleApi" Version="1.2.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
14
Libation.sln
14
Libation.sln
@@ -37,8 +37,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationSearchEngine", "Lib
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationWinForms", "LibationWinForms\LibationWinForms.csproj", "{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinFormsDesigner", "WinFormsDesigner\WinFormsDesigner.csproj", "{0807616A-A77A-4B08-A65A-1582B09E114B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DtoImporterService", "DtoImporterService\DtoImporterService.csproj", "{401865F5-1942-4713-B230-04544C0A97B0}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationServices", "ApplicationServices\ApplicationServices.csproj", "{B95650EA-25F0-449E-BA5D-99126BC5D730}"
|
||||
@@ -51,8 +49,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InternalUtilities.Tests", "
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationSearchEngine.Tests", "_Tests\LibationSearchEngine.Tests\LibationSearchEngine.Tests.csproj", "{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAXClean", "..\AAXClean\AAXClean.csproj", "{94BEB7CC-511D-45AB-9F09-09BE858EE486}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hoopla", "Hoopla\Hoopla.csproj", "{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}"
|
||||
EndProject
|
||||
Global
|
||||
@@ -89,10 +85,6 @@ Global
|
||||
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0807616A-A77A-4B08-A65A-1582B09E114B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0807616A-A77A-4B08-A65A-1582B09E114B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0807616A-A77A-4B08-A65A-1582B09E114B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0807616A-A77A-4B08-A65A-1582B09E114B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{401865F5-1942-4713-B230-04544C0A97B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{401865F5-1942-4713-B230-04544C0A97B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{401865F5-1942-4713-B230-04544C0A97B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -113,10 +105,6 @@ Global
|
||||
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -133,13 +121,11 @@ Global
|
||||
{06882742-27A6-4347-97D9-56162CEC9C11} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249}
|
||||
{2E1F5DB4-40CC-4804-A893-5DCE0193E598} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
|
||||
{635F00E1-AAD1-45F7-BEB7-D909AD33B9F6} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
|
||||
{0807616A-A77A-4B08-A65A-1582B09E114B} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
|
||||
{401865F5-1942-4713-B230-04544C0A97B0} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
|
||||
{B95650EA-25F0-449E-BA5D-99126BC5D730} = {41CDCC73-9B81-49DD-9570-C54406E852AF}
|
||||
{F3B04A3A-20C8-4582-A54A-715AF6A5D859} = {8679CAC8-9164-4007-BDD2-F004810EDA14}
|
||||
{8447C956-B03E-4F59-9DD4-877793B849D9} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
|
||||
{D8F56E5A-3E65-41A6-B7E7-C4515A264B1F} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<!-- <PublishSingleFile>true</PublishSingleFile> -->
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
<Version>5.5.9.1</Version>
|
||||
<Version>5.6.7.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
|
||||
@@ -115,13 +115,13 @@ namespace LibationLauncher
|
||||
return;
|
||||
|
||||
// path did not result in valid settings
|
||||
MessageBox.Show(
|
||||
var continueResult = MessageBox.Show(
|
||||
$"No valid settings were found at this location.\r\nWould you like to create a new install settings in this folder?\r\n\r\n{libationFilesDialog.SelectedDirectory}",
|
||||
"New install?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Question);
|
||||
|
||||
if (libationFilesDialog.ShowDialog() != DialogResult.Yes)
|
||||
if (continueResult != DialogResult.Yes)
|
||||
{
|
||||
CancelInstallation();
|
||||
return;
|
||||
|
||||
@@ -129,6 +129,11 @@ namespace LibationSearchEngine
|
||||
["IsLiberated"] = lb => isLiberated(lb.Book),
|
||||
["Liberated"] = lb => isLiberated(lb.Book),
|
||||
["LiberatedError"] = lb => liberatedError(lb.Book),
|
||||
|
||||
["Podcast"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
["IsPodcast"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
["Episode"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
["IsEpisode"] = lb => lb.Book.ContentType == ContentType.Episode,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -192,25 +197,9 @@ namespace LibationSearchEngine
|
||||
#endregion
|
||||
|
||||
#region create and update index
|
||||
/// <summary>
|
||||
/// create new. ie: full re-index
|
||||
/// </summary>
|
||||
/// <param name="overwrite"></param>
|
||||
public void CreateNewIndex(LibationContext context, bool overwrite = true)
|
||||
/// <summary>create new. ie: full re-index</summary>
|
||||
public void CreateNewIndex(IEnumerable<LibraryBook> library, bool overwrite = true)
|
||||
{
|
||||
// 300 titles: 200- 400 ms
|
||||
// 1021 titles: 1777-2250 ms
|
||||
var sw = System.Diagnostics.Stopwatch.StartNew();
|
||||
var stamps = new List<long>();
|
||||
void log() => stamps.Add(sw.ElapsedMilliseconds);
|
||||
|
||||
|
||||
log();
|
||||
|
||||
var library = context.GetLibrary_Flat_NoTracking();
|
||||
|
||||
log();
|
||||
|
||||
// location of index/create the index
|
||||
using var index = getIndex();
|
||||
var exists = IndexReader.IndexExists(index);
|
||||
@@ -224,8 +213,6 @@ namespace LibationSearchEngine
|
||||
var doc = createBookIndexDocument(libraryBook);
|
||||
ixWriter.AddDocument(doc);
|
||||
}
|
||||
|
||||
log();
|
||||
}
|
||||
|
||||
/// <summary>Long running. Use await Task.Run(() => UpdateBook(productId))</summary>
|
||||
|
||||
@@ -9,6 +9,6 @@ namespace LibationWinForms
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
|
||||
=> this.UIThread(() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)));
|
||||
=> this.UIThreadAsync(() => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
using DataLayer;
|
||||
using FileManager;
|
||||
using System;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
class AudioConvertForm : AudioDecodeForm
|
||||
{
|
||||
public AudioConvertForm()
|
||||
{
|
||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
|
||||
#region AudioDecodeForm overrides
|
||||
public override string DecodeActionName => "Converting";
|
||||
#endregion
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using DataLayer;
|
||||
using System;
|
||||
using DataLayer;
|
||||
using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Windows.Forms;
|
||||
using Dinah.Core.Threading;
|
||||
using LibationWinForms.BookLiberation.BaseForms;
|
||||
using System;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
@@ -47,7 +47,7 @@ namespace LibationWinForms.BookLiberation
|
||||
if (downloadProgress.ProgressPercentage == 0)
|
||||
updateRemainingTime(0);
|
||||
else
|
||||
progressBar1.UIThread(() => progressBar1.Value = (int)downloadProgress.ProgressPercentage);
|
||||
progressBar1.UIThreadAsync(() => progressBar1.Value = (int)downloadProgress.ProgressPercentage);
|
||||
}
|
||||
|
||||
public override void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining)
|
||||
@@ -61,7 +61,7 @@ namespace LibationWinForms.BookLiberation
|
||||
|
||||
public override void OnTitleDiscovered(object sender, string title)
|
||||
{
|
||||
this.UIThread(() => this.Text = DecodeActionName + " " + title);
|
||||
this.UIThreadAsync(() => this.Text = DecodeActionName + " " + title);
|
||||
this.title = title;
|
||||
updateBookInfo();
|
||||
}
|
||||
@@ -79,14 +79,14 @@ namespace LibationWinForms.BookLiberation
|
||||
}
|
||||
|
||||
public override void OnCoverImageDiscovered(object sender, byte[] coverArt)
|
||||
=> pictureBox1.UIThread(() => pictureBox1.Image = Dinah.Core.Drawing.ImageReader.ToImage(coverArt));
|
||||
=> pictureBox1.UIThreadAsync(() => pictureBox1.Image = Dinah.Core.Drawing.ImageReader.ToImage(coverArt));
|
||||
#endregion
|
||||
|
||||
// thread-safe UI updates
|
||||
private void updateBookInfo()
|
||||
=> bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}");
|
||||
=> bookInfoLbl.UIThreadAsync(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}");
|
||||
|
||||
private void updateRemainingTime(int remaining)
|
||||
=> remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec");
|
||||
=> remainingTimeLbl.UIThreadAsync(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
using DataLayer;
|
||||
using FileManager;
|
||||
using System;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
class AudioDecryptForm : AudioDecodeForm
|
||||
{
|
||||
public AudioDecryptForm()
|
||||
{
|
||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
}
|
||||
|
||||
#region AudioDecodeForm overrides
|
||||
public override string DecodeActionName => "Decrypting";
|
||||
#endregion
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using Dinah.Core.Windows.Forms;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core.Threading;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
@@ -19,7 +18,7 @@ namespace LibationWinForms.BookLiberation
|
||||
public void WriteLine(string text)
|
||||
{
|
||||
if (!IsDisposed)
|
||||
logTb.UIThread(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}"));
|
||||
logTb.UIThreadAsync(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}"));
|
||||
}
|
||||
|
||||
public void FinalizeUI()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using DataLayer;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using DataLayer;
|
||||
using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Threading;
|
||||
using FileLiberator;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms.BookLiberation.BaseForms
|
||||
{
|
||||
@@ -122,8 +122,8 @@ namespace LibationWinForms.BookLiberation.BaseForms
|
||||
/// <summary>
|
||||
/// If the form was shown using Show (not ShowDialog), Form.Close calls Form.Dispose
|
||||
/// </summary>
|
||||
private void OnStreamingCompletedClose(object sender, string completedString) => this.UIThread(Close);
|
||||
private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThread(Dispose);
|
||||
private void OnStreamingCompletedClose(object sender, string completedString) => this.UIThreadAsync(Close);
|
||||
private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThreadAsync(Dispose);
|
||||
|
||||
/// <summary>
|
||||
/// If StreamingBegin is fired from a worker thread, the window will be created on that
|
||||
@@ -132,7 +132,7 @@ namespace LibationWinForms.BookLiberation.BaseForms
|
||||
/// could cause it to freeze. Form.BeginInvoke won't work until the form is created
|
||||
/// (ie. shown) because Control doesn't get a window handle until it is Shown.
|
||||
/// </summary>
|
||||
private void OnStreamingBeginShow(object sender, string beginString) => Invoker.UIThread(Show);
|
||||
private void OnStreamingBeginShow(object sender, string beginString) => Invoker.UIThreadAsync(Show);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Windows.Forms;
|
||||
using LibationWinForms.BookLiberation.BaseForms;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using Dinah.Core.Net.Http;
|
||||
using Dinah.Core.Threading;
|
||||
using LibationWinForms.BookLiberation.BaseForms;
|
||||
|
||||
namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
@@ -21,7 +20,7 @@ namespace LibationWinForms.BookLiberation
|
||||
#region IStreamable event handler overrides
|
||||
public override void OnStreamingBegin(object sender, string beginString)
|
||||
{
|
||||
filenameLbl.UIThread(() => filenameLbl.Text = beginString);
|
||||
filenameLbl.UIThreadAsync(() => filenameLbl.Text = beginString);
|
||||
}
|
||||
public override void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress)
|
||||
{
|
||||
@@ -29,11 +28,11 @@ namespace LibationWinForms.BookLiberation
|
||||
if (!downloadProgress.TotalBytesToReceive.HasValue || downloadProgress.TotalBytesToReceive.Value <= 0)
|
||||
return;
|
||||
|
||||
progressLbl.UIThread(() => progressLbl.Text = $"{downloadProgress.BytesReceived:#,##0} of {downloadProgress.TotalBytesToReceive.Value:#,##0}");
|
||||
progressLbl.UIThreadAsync(() => progressLbl.Text = $"{downloadProgress.BytesReceived:#,##0} of {downloadProgress.TotalBytesToReceive.Value:#,##0}");
|
||||
|
||||
var d = double.Parse(downloadProgress.BytesReceived.ToString()) / double.Parse(downloadProgress.TotalBytesToReceive.Value.ToString()) * 100.0;
|
||||
var i = int.Parse(Math.Truncate(d).ToString());
|
||||
progressBar1.UIThread(() => progressBar1.Value = i);
|
||||
progressBar1.UIThreadAsync(() => progressBar1.Value = i);
|
||||
|
||||
lastDownloadProgress = DateTime.Now;
|
||||
}
|
||||
@@ -50,14 +49,14 @@ namespace LibationWinForms.BookLiberation
|
||||
private void timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
// if no update in the last 30 seconds, display frozen label
|
||||
lastUpdateLbl.UIThread(() => lastUpdateLbl.Visible = lastDownloadProgress.AddSeconds(30) < DateTime.Now);
|
||||
lastUpdateLbl.UIThreadAsync(() => lastUpdateLbl.Visible = lastDownloadProgress.AddSeconds(30) < DateTime.Now);
|
||||
if (lastUpdateLbl.Visible)
|
||||
{
|
||||
var diff = DateTime.Now - lastDownloadProgress;
|
||||
var min = (int)diff.TotalMinutes;
|
||||
var minText = min > 0 ? $"{min}min " : "";
|
||||
|
||||
lastUpdateLbl.UIThread(() => lastUpdateLbl.Text = $"Frozen? Last download activity: {minText}{diff.Seconds}sec ago");
|
||||
lastUpdateLbl.UIThreadAsync(() => lastUpdateLbl.Text = $"Frozen? Last download activity: {minText}{diff.Seconds}sec ago");
|
||||
}
|
||||
}
|
||||
private void DownloadForm_FormClosing(object sender, FormClosingEventArgs e) => timer.Stop();
|
||||
|
||||
@@ -304,7 +304,7 @@ An error occurred while trying to process this book.
|
||||
protected override async Task RunAsync()
|
||||
{
|
||||
// support for 'skip this time only' requires state. iterators provide this state for free. therefore: use foreach/iterator here
|
||||
foreach (var libraryBook in Processable.GetValidLibraryBooks(ApplicationServices.DbContexts.GetContext().GetLibrary_Flat_NoTracking()))
|
||||
foreach (var libraryBook in Processable.GetValidLibraryBooks(ApplicationServices.DbContexts.GetLibrary_Flat_NoTracking()))
|
||||
{
|
||||
var keepGoing = await ProcessOneAsync(libraryBook, validate: false);
|
||||
if (!keepGoing)
|
||||
|
||||
280
LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs
generated
280
LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs
generated
@@ -29,147 +29,147 @@ namespace LibationWinForms.Dialogs
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
this._dataGridView = new System.Windows.Forms.DataGridView();
|
||||
this.removeDataGridViewCheckBoxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
||||
this.coverDataGridViewImageColumn = new System.Windows.Forms.DataGridViewImageColumn();
|
||||
this.titleDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.authorsDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.miscDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.purchaseDateGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components);
|
||||
this.btnRemoveBooks = new System.Windows.Forms.Button();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// _dataGridView
|
||||
//
|
||||
this._dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
this._dataGridView = new System.Windows.Forms.DataGridView();
|
||||
this.removeDataGridViewCheckBoxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
|
||||
this.coverDataGridViewImageColumn = new System.Windows.Forms.DataGridViewImageColumn();
|
||||
this.titleDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.authorsDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.miscDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.purchaseDateGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components);
|
||||
this.btnRemoveBooks = new System.Windows.Forms.Button();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// _dataGridView
|
||||
//
|
||||
this._dataGridView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this._dataGridView.AutoGenerateColumns = false;
|
||||
this._dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this._dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this._dataGridView.AutoGenerateColumns = false;
|
||||
this._dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this._dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.removeDataGridViewCheckBoxColumn,
|
||||
this.coverDataGridViewImageColumn,
|
||||
this.titleDataGridViewTextBoxColumn,
|
||||
this.authorsDataGridViewTextBoxColumn,
|
||||
this.miscDataGridViewTextBoxColumn,
|
||||
this.purchaseDateGridViewTextBoxColumn});
|
||||
this._dataGridView.DataSource = this.gridEntryBindingSource;
|
||||
dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window;
|
||||
dataGridViewCellStyle2.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
||||
dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
||||
dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
|
||||
this._dataGridView.DefaultCellStyle = dataGridViewCellStyle2;
|
||||
this._dataGridView.Location = new System.Drawing.Point(0, 0);
|
||||
this._dataGridView.Name = "_dataGridView";
|
||||
this._dataGridView.RowHeadersVisible = false;
|
||||
this._dataGridView.RowTemplate.Height = 82;
|
||||
this._dataGridView.Size = new System.Drawing.Size(800, 409);
|
||||
this._dataGridView.TabIndex = 0;
|
||||
//
|
||||
// removeDataGridViewCheckBoxColumn
|
||||
//
|
||||
this.removeDataGridViewCheckBoxColumn.DataPropertyName = "Remove";
|
||||
this.removeDataGridViewCheckBoxColumn.FalseValue = "False";
|
||||
this.removeDataGridViewCheckBoxColumn.Frozen = true;
|
||||
this.removeDataGridViewCheckBoxColumn.HeaderText = "Remove";
|
||||
this.removeDataGridViewCheckBoxColumn.MinimumWidth = 60;
|
||||
this.removeDataGridViewCheckBoxColumn.Name = "removeDataGridViewCheckBoxColumn";
|
||||
this.removeDataGridViewCheckBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
||||
this.removeDataGridViewCheckBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
|
||||
this.removeDataGridViewCheckBoxColumn.TrueValue = "True";
|
||||
this.removeDataGridViewCheckBoxColumn.Width = 60;
|
||||
//
|
||||
// coverDataGridViewImageColumn
|
||||
//
|
||||
this.coverDataGridViewImageColumn.DataPropertyName = "Cover";
|
||||
this.coverDataGridViewImageColumn.HeaderText = "Cover";
|
||||
this.coverDataGridViewImageColumn.MinimumWidth = 80;
|
||||
this.coverDataGridViewImageColumn.Name = "coverDataGridViewImageColumn";
|
||||
this.coverDataGridViewImageColumn.ReadOnly = true;
|
||||
this.coverDataGridViewImageColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
||||
this.coverDataGridViewImageColumn.Width = 80;
|
||||
//
|
||||
// titleDataGridViewTextBoxColumn
|
||||
//
|
||||
this.titleDataGridViewTextBoxColumn.DataPropertyName = "Title";
|
||||
this.titleDataGridViewTextBoxColumn.HeaderText = "Title";
|
||||
this.titleDataGridViewTextBoxColumn.Name = "titleDataGridViewTextBoxColumn";
|
||||
this.titleDataGridViewTextBoxColumn.ReadOnly = true;
|
||||
this.titleDataGridViewTextBoxColumn.Width = 200;
|
||||
//
|
||||
// authorsDataGridViewTextBoxColumn
|
||||
//
|
||||
this.authorsDataGridViewTextBoxColumn.DataPropertyName = "Authors";
|
||||
this.authorsDataGridViewTextBoxColumn.HeaderText = "Authors";
|
||||
this.authorsDataGridViewTextBoxColumn.Name = "authorsDataGridViewTextBoxColumn";
|
||||
this.authorsDataGridViewTextBoxColumn.ReadOnly = true;
|
||||
//
|
||||
// miscDataGridViewTextBoxColumn
|
||||
//
|
||||
this.miscDataGridViewTextBoxColumn.DataPropertyName = "Misc";
|
||||
this.miscDataGridViewTextBoxColumn.HeaderText = "Misc";
|
||||
this.miscDataGridViewTextBoxColumn.Name = "miscDataGridViewTextBoxColumn";
|
||||
this.miscDataGridViewTextBoxColumn.ReadOnly = true;
|
||||
this.miscDataGridViewTextBoxColumn.Width = 150;
|
||||
//
|
||||
// purchaseDateGridViewTextBoxColumn
|
||||
//
|
||||
this.purchaseDateGridViewTextBoxColumn.DataPropertyName = "PurchaseDate";
|
||||
this.purchaseDateGridViewTextBoxColumn.HeaderText = "Purchase Date";
|
||||
this.purchaseDateGridViewTextBoxColumn.Name = "purchaseDateGridViewTextBoxColumn";
|
||||
this.purchaseDateGridViewTextBoxColumn.ReadOnly = true;
|
||||
this.purchaseDateGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
||||
//
|
||||
// gridEntryBindingSource
|
||||
//
|
||||
this.gridEntryBindingSource.AllowNew = false;
|
||||
this.gridEntryBindingSource.DataSource = typeof(LibationWinForms.Dialogs.RemovableGridEntry);
|
||||
//
|
||||
// btnRemoveBooks
|
||||
//
|
||||
this.btnRemoveBooks.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnRemoveBooks.Location = new System.Drawing.Point(570, 419);
|
||||
this.btnRemoveBooks.Name = "btnRemoveBooks";
|
||||
this.btnRemoveBooks.Size = new System.Drawing.Size(218, 23);
|
||||
this.btnRemoveBooks.TabIndex = 1;
|
||||
this.btnRemoveBooks.Text = "Remove Selected Books from Libation";
|
||||
this.btnRemoveBooks.UseVisualStyleBackColor = true;
|
||||
this.btnRemoveBooks.Click += new System.EventHandler(this.btnRemoveBooks_Click);
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(12, 423);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(178, 15);
|
||||
this.label1.TabIndex = 2;
|
||||
this.label1.Text = "{0} book{1} selected for removal.";
|
||||
//
|
||||
// RemoveBooksDialog
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||
this.Controls.Add(this.label1);
|
||||
this.Controls.Add(this.btnRemoveBooks);
|
||||
this.Controls.Add(this._dataGridView);
|
||||
this.Name = "RemoveBooksDialog";
|
||||
this.Text = "RemoveBooksDialog";
|
||||
this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown);
|
||||
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
this._dataGridView.DataSource = this.gridEntryBindingSource;
|
||||
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window;
|
||||
dataGridViewCellStyle1.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
|
||||
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
||||
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
||||
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
|
||||
this._dataGridView.DefaultCellStyle = dataGridViewCellStyle1;
|
||||
this._dataGridView.Location = new System.Drawing.Point(0, 0);
|
||||
this._dataGridView.Name = "_dataGridView";
|
||||
this._dataGridView.RowHeadersVisible = false;
|
||||
this._dataGridView.RowTemplate.Height = 82;
|
||||
this._dataGridView.Size = new System.Drawing.Size(730, 409);
|
||||
this._dataGridView.TabIndex = 0;
|
||||
//
|
||||
// removeDataGridViewCheckBoxColumn
|
||||
//
|
||||
this.removeDataGridViewCheckBoxColumn.DataPropertyName = "Remove";
|
||||
this.removeDataGridViewCheckBoxColumn.FalseValue = "False";
|
||||
this.removeDataGridViewCheckBoxColumn.Frozen = true;
|
||||
this.removeDataGridViewCheckBoxColumn.HeaderText = "Remove";
|
||||
this.removeDataGridViewCheckBoxColumn.MinimumWidth = 80;
|
||||
this.removeDataGridViewCheckBoxColumn.Name = "removeDataGridViewCheckBoxColumn";
|
||||
this.removeDataGridViewCheckBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
||||
this.removeDataGridViewCheckBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic;
|
||||
this.removeDataGridViewCheckBoxColumn.TrueValue = "True";
|
||||
this.removeDataGridViewCheckBoxColumn.Width = 80;
|
||||
//
|
||||
// coverDataGridViewImageColumn
|
||||
//
|
||||
this.coverDataGridViewImageColumn.DataPropertyName = "Cover";
|
||||
this.coverDataGridViewImageColumn.HeaderText = "Cover";
|
||||
this.coverDataGridViewImageColumn.MinimumWidth = 80;
|
||||
this.coverDataGridViewImageColumn.Name = "coverDataGridViewImageColumn";
|
||||
this.coverDataGridViewImageColumn.ReadOnly = true;
|
||||
this.coverDataGridViewImageColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
||||
this.coverDataGridViewImageColumn.Width = 80;
|
||||
//
|
||||
// titleDataGridViewTextBoxColumn
|
||||
//
|
||||
this.titleDataGridViewTextBoxColumn.DataPropertyName = "Title";
|
||||
this.titleDataGridViewTextBoxColumn.HeaderText = "Title";
|
||||
this.titleDataGridViewTextBoxColumn.Name = "titleDataGridViewTextBoxColumn";
|
||||
this.titleDataGridViewTextBoxColumn.ReadOnly = true;
|
||||
this.titleDataGridViewTextBoxColumn.Width = 200;
|
||||
//
|
||||
// authorsDataGridViewTextBoxColumn
|
||||
//
|
||||
this.authorsDataGridViewTextBoxColumn.DataPropertyName = "Authors";
|
||||
this.authorsDataGridViewTextBoxColumn.HeaderText = "Authors";
|
||||
this.authorsDataGridViewTextBoxColumn.Name = "authorsDataGridViewTextBoxColumn";
|
||||
this.authorsDataGridViewTextBoxColumn.ReadOnly = true;
|
||||
//
|
||||
// miscDataGridViewTextBoxColumn
|
||||
//
|
||||
this.miscDataGridViewTextBoxColumn.DataPropertyName = "Misc";
|
||||
this.miscDataGridViewTextBoxColumn.HeaderText = "Misc";
|
||||
this.miscDataGridViewTextBoxColumn.Name = "miscDataGridViewTextBoxColumn";
|
||||
this.miscDataGridViewTextBoxColumn.ReadOnly = true;
|
||||
this.miscDataGridViewTextBoxColumn.Width = 150;
|
||||
//
|
||||
// purchaseDateGridViewTextBoxColumn
|
||||
//
|
||||
this.purchaseDateGridViewTextBoxColumn.DataPropertyName = "PurchaseDate";
|
||||
this.purchaseDateGridViewTextBoxColumn.HeaderText = "Purchase Date";
|
||||
this.purchaseDateGridViewTextBoxColumn.Name = "purchaseDateGridViewTextBoxColumn";
|
||||
this.purchaseDateGridViewTextBoxColumn.ReadOnly = true;
|
||||
this.purchaseDateGridViewTextBoxColumn.Resizable = System.Windows.Forms.DataGridViewTriState.False;
|
||||
//
|
||||
// gridEntryBindingSource
|
||||
//
|
||||
this.gridEntryBindingSource.AllowNew = false;
|
||||
this.gridEntryBindingSource.DataSource = typeof(LibationWinForms.Dialogs.RemovableGridEntry);
|
||||
//
|
||||
// btnRemoveBooks
|
||||
//
|
||||
this.btnRemoveBooks.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.btnRemoveBooks.Location = new System.Drawing.Point(500, 419);
|
||||
this.btnRemoveBooks.Name = "btnRemoveBooks";
|
||||
this.btnRemoveBooks.Size = new System.Drawing.Size(218, 23);
|
||||
this.btnRemoveBooks.TabIndex = 1;
|
||||
this.btnRemoveBooks.Text = "Remove Selected Books from Libation";
|
||||
this.btnRemoveBooks.UseVisualStyleBackColor = true;
|
||||
this.btnRemoveBooks.Click += new System.EventHandler(this.btnRemoveBooks_Click);
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(12, 423);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(178, 15);
|
||||
this.label1.TabIndex = 2;
|
||||
this.label1.Text = "{0} book{1} selected for removal.";
|
||||
//
|
||||
// RemoveBooksDialog
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(730, 450);
|
||||
this.Controls.Add(this.label1);
|
||||
this.Controls.Add(this.btnRemoveBooks);
|
||||
this.Controls.Add(this._dataGridView);
|
||||
this.Name = "RemoveBooksDialog";
|
||||
this.Text = "RemoveBooksDialog";
|
||||
this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown);
|
||||
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
@@ -179,11 +179,11 @@ namespace LibationWinForms.Dialogs
|
||||
private System.Windows.Forms.BindingSource gridEntryBindingSource;
|
||||
private System.Windows.Forms.Button btnRemoveBooks;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.DataGridViewCheckBoxColumn removeDataGridViewCheckBoxColumn;
|
||||
private System.Windows.Forms.DataGridViewImageColumn coverDataGridViewImageColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn titleDataGridViewTextBoxColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn authorsDataGridViewTextBoxColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn miscDataGridViewTextBoxColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn purchaseDateGridViewTextBoxColumn;
|
||||
}
|
||||
private System.Windows.Forms.DataGridViewCheckBoxColumn removeDataGridViewCheckBoxColumn;
|
||||
private System.Windows.Forms.DataGridViewImageColumn coverDataGridViewImageColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn titleDataGridViewTextBoxColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn authorsDataGridViewTextBoxColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn miscDataGridViewTextBoxColumn;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn purchaseDateGridViewTextBoxColumn;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
using System;
|
||||
using ApplicationServices;
|
||||
using DataLayer;
|
||||
using Dinah.Core.DataBinding;
|
||||
using FileManager;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms.Login;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
using DataLayer;
|
||||
using Dinah.Core.DataBinding;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms.Login;
|
||||
|
||||
namespace LibationWinForms.Dialogs
|
||||
{
|
||||
public partial class RemoveBooksDialog : Form
|
||||
{
|
||||
public bool BooksRemoved { get; private set; }
|
||||
|
||||
private Account[] _accounts { get; }
|
||||
private readonly List<LibraryBook> _libraryBooks;
|
||||
private readonly SortableBindingList<RemovableGridEntry> _removableGridEntries;
|
||||
@@ -26,15 +25,19 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
public RemoveBooksDialog(params Account[] accounts)
|
||||
{
|
||||
_libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking();
|
||||
_libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
_accounts = accounts;
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
|
||||
_labelFormat = label1.Text;
|
||||
|
||||
_dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
||||
_dataGridView.CellValueChanged += DataGridView1_CellValueChanged;
|
||||
_dataGridView.BindingContextChanged += (s, e) => UpdateSelection();
|
||||
_dataGridView.CellContentClick += (_, _) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
|
||||
_dataGridView.CellValueChanged += (_, _) => UpdateSelection();
|
||||
_dataGridView.BindingContextChanged += _dataGridView_BindingContextChanged;
|
||||
|
||||
var orderedGridEntries = _libraryBooks
|
||||
.Select(lb => new RemovableGridEntry(lb))
|
||||
@@ -47,10 +50,10 @@ namespace LibationWinForms.Dialogs
|
||||
_dataGridView.Enabled = false;
|
||||
}
|
||||
|
||||
private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
|
||||
private void _dataGridView_BindingContextChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (e.ColumnIndex == 0)
|
||||
UpdateSelection();
|
||||
_dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending);
|
||||
UpdateSelection();
|
||||
}
|
||||
|
||||
private async void RemoveBooksDialog_Shown(object sender, EventArgs e)
|
||||
@@ -59,9 +62,9 @@ namespace LibationWinForms.Dialogs
|
||||
return;
|
||||
try
|
||||
{
|
||||
var rmovedBooks = await LibraryCommands.FindInactiveBooks((account) => new WinformResponder(account), _libraryBooks, _accounts);
|
||||
var removedBooks = await LibraryCommands.FindInactiveBooks((account) => new WinformResponder(account), _libraryBooks, _accounts);
|
||||
|
||||
var removable = _removableGridEntries.Where(rge => rmovedBooks.Any(rb => rb.Book.AudibleProductId == rge.AudibleProductId));
|
||||
var removable = _removableGridEntries.Where(rge => removedBooks.Any(rb => rb.Book.AudibleProductId == rge.AudibleProductId)).ToList();
|
||||
|
||||
if (!removable.Any())
|
||||
return;
|
||||
@@ -84,7 +87,7 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
}
|
||||
|
||||
private void btnRemoveBooks_Click(object sender, EventArgs e)
|
||||
private async void btnRemoveBooks_Click(object sender, EventArgs e)
|
||||
{
|
||||
var selectedBooks = SelectedEntries.ToList();
|
||||
|
||||
@@ -105,26 +108,18 @@ namespace LibationWinForms.Dialogs
|
||||
|
||||
if (result == DialogResult.Yes)
|
||||
{
|
||||
using var context = DbContexts.GetContext();
|
||||
|
||||
var libBooks = context.GetLibrary_Flat_NoTracking();
|
||||
|
||||
var removeLibraryBooks = libBooks.Where(lb => selectedBooks.Any(rge => rge.AudibleProductId == lb.Book.AudibleProductId)).ToList();
|
||||
|
||||
context.Library.RemoveRange(removeLibraryBooks);
|
||||
context.SaveChanges();
|
||||
var idsToRemove = selectedBooks.Select(rge => rge.AudibleProductId).ToList();
|
||||
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
||||
|
||||
foreach (var rEntry in selectedBooks)
|
||||
_removableGridEntries.Remove(rEntry);
|
||||
|
||||
BooksRemoved = removeLibraryBooks.Count > 0;
|
||||
|
||||
UpdateSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelection()
|
||||
{
|
||||
_dataGridView.Sort(_dataGridView.Columns[0], ListSortDirection.Descending);
|
||||
var selectedCount = SelectedCount;
|
||||
label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty);
|
||||
btnRemoveBooks.Enabled = selectedCount > 0;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
@@ -58,4 +57,7 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="gridEntryBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
17
LibationWinForms/Form1.Designer.cs
generated
17
LibationWinForms/Form1.Designer.cs
generated
@@ -173,18 +173,19 @@
|
||||
this.removeLibraryBooksToolStripMenuItem.Name = "removeLibraryBooksToolStripMenuItem";
|
||||
this.removeLibraryBooksToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.removeLibraryBooksToolStripMenuItem.Text = "Remove Library Books";
|
||||
this.removeLibraryBooksToolStripMenuItem.Click += new System.EventHandler(this.removeLibraryBooksToolStripMenuItem_Click);
|
||||
//
|
||||
// removeAllAccountsToolStripMenuItem
|
||||
//
|
||||
this.removeAllAccountsToolStripMenuItem.Name = "removeAllAccountsToolStripMenuItem";
|
||||
this.removeAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
|
||||
this.removeAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.removeAllAccountsToolStripMenuItem.Text = "All Accounts";
|
||||
this.removeAllAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeAllAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
// removeSomeAccountsToolStripMenuItem
|
||||
//
|
||||
this.removeSomeAccountsToolStripMenuItem.Name = "removeSomeAccountsToolStripMenuItem";
|
||||
this.removeSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(157, 22);
|
||||
this.removeSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.removeSomeAccountsToolStripMenuItem.Text = "Some Accounts";
|
||||
this.removeSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeSomeAccountsToolStripMenuItem_Click);
|
||||
//
|
||||
@@ -201,23 +202,22 @@
|
||||
// beginBookBackupsToolStripMenuItem
|
||||
//
|
||||
this.beginBookBackupsToolStripMenuItem.Name = "beginBookBackupsToolStripMenuItem";
|
||||
this.beginBookBackupsToolStripMenuItem.Size = new System.Drawing.Size(284, 22);
|
||||
this.beginBookBackupsToolStripMenuItem.Size = new System.Drawing.Size(293, 22);
|
||||
this.beginBookBackupsToolStripMenuItem.Text = "Begin &Book and PDF Backups: {0}";
|
||||
this.beginBookBackupsToolStripMenuItem.Click += new System.EventHandler(this.beginBookBackupsToolStripMenuItem_Click);
|
||||
//
|
||||
// beginPdfBackupsToolStripMenuItem
|
||||
//
|
||||
this.beginPdfBackupsToolStripMenuItem.Name = "beginPdfBackupsToolStripMenuItem";
|
||||
this.beginPdfBackupsToolStripMenuItem.Size = new System.Drawing.Size(284, 22);
|
||||
this.beginPdfBackupsToolStripMenuItem.Size = new System.Drawing.Size(293, 22);
|
||||
this.beginPdfBackupsToolStripMenuItem.Text = "Begin &PDF Only Backups: {0}";
|
||||
this.beginPdfBackupsToolStripMenuItem.Click += new System.EventHandler(this.beginPdfBackupsToolStripMenuItem_Click);
|
||||
//
|
||||
// convertAllM4bToMp3ToolStripMenuItem
|
||||
//
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Name = "convertAllM4bToMp3ToolStripMenuItem";
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Size = new System.Drawing.Size(284, 22);
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Text = "Convert all M4b to Mp3 [Long-running]";
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Visible = true;
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Size = new System.Drawing.Size(293, 22);
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Text = "Convert all M4b to Mp3 [Long-running]...";
|
||||
this.convertAllM4bToMp3ToolStripMenuItem.Click += new System.EventHandler(this.convertAllM4bToMp3ToolStripMenuItem_Click);
|
||||
//
|
||||
// exportToolStripMenuItem
|
||||
@@ -310,7 +310,7 @@
|
||||
// springLbl
|
||||
//
|
||||
this.springLbl.Name = "springLbl";
|
||||
this.springLbl.Size = new System.Drawing.Size(517, 17);
|
||||
this.springLbl.Size = new System.Drawing.Size(548, 17);
|
||||
this.springLbl.Spring = true;
|
||||
//
|
||||
// backupsCountsLbl
|
||||
@@ -353,7 +353,6 @@
|
||||
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
|
||||
this.Name = "Form1";
|
||||
this.Text = "Libation: Liberate your Library";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing);
|
||||
this.Load += new System.EventHandler(this.Form1_Load);
|
||||
this.menuStrip1.ResumeLayout(false);
|
||||
this.menuStrip1.PerformLayout();
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
using ApplicationServices;
|
||||
using DataLayer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using ApplicationServices;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Drawing;
|
||||
using Dinah.Core.Windows.Forms;
|
||||
using Dinah.Core.Threading;
|
||||
using FileManager;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms.Dialogs;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
@@ -31,9 +29,11 @@ namespace LibationWinForms
|
||||
return;
|
||||
|
||||
// independent UI updates
|
||||
this.Load += setBackupCountsAsync;
|
||||
this.Load += (_, __) => RestoreSizeAndLocation();
|
||||
this.Load += (_, __) => RefreshImportMenu();
|
||||
this.Load += (_, _) => this.RestoreSizeAndLocation(Configuration.Instance);
|
||||
this.Load += RefreshImportMenu;
|
||||
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
|
||||
LibraryCommands.LibrarySizeChanged += reloadGridAndUpdateBottomNumbers;
|
||||
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts;
|
||||
|
||||
var format = System.Drawing.Imaging.ImageFormat.Jpeg;
|
||||
PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format));
|
||||
@@ -46,101 +46,29 @@ namespace LibationWinForms
|
||||
if (this.DesignMode)
|
||||
return;
|
||||
|
||||
reloadGrid();
|
||||
// can't refactor into "this.Load => reloadGridAndUpdateBottomNumbers"
|
||||
// because loadInitialQuickFilterState must follow it
|
||||
reloadGridAndUpdateBottomNumbers();
|
||||
|
||||
// also applies filter. ONLY call AFTER loading grid
|
||||
loadInitialQuickFilterState();
|
||||
}
|
||||
|
||||
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
SaveSizeAndLocation();
|
||||
}
|
||||
|
||||
private void RestoreSizeAndLocation()
|
||||
{
|
||||
var config = Configuration.Instance;
|
||||
|
||||
var width = config.MainFormWidth;
|
||||
var height = config.MainFormHeight;
|
||||
|
||||
// too small -- something must have gone wrong. use defaults
|
||||
if (width < 25 || height < 25)
|
||||
{
|
||||
width = 1023;
|
||||
height = 578;
|
||||
}
|
||||
|
||||
// Fit to the current screen size in case the screen resolution changed since the size was last persisted
|
||||
if (width > Screen.PrimaryScreen.WorkingArea.Width)
|
||||
width = Screen.PrimaryScreen.WorkingArea.Width;
|
||||
if (height > Screen.PrimaryScreen.WorkingArea.Height)
|
||||
height = Screen.PrimaryScreen.WorkingArea.Height;
|
||||
|
||||
var x = config.MainFormX;
|
||||
var y = config.MainFormY;
|
||||
|
||||
var rect = new System.Drawing.Rectangle(x, y, width, height);
|
||||
|
||||
// is proposed rect on a screen?
|
||||
if (Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect)))
|
||||
{
|
||||
this.StartPosition = FormStartPosition.Manual;
|
||||
this.DesktopBounds = rect;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.StartPosition = FormStartPosition.WindowsDefaultLocation;
|
||||
this.Size = rect.Size;
|
||||
}
|
||||
|
||||
// FINAL: for Maximized: start normal state, set size and location, THEN set max state
|
||||
this.WindowState = config.MainFormIsMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
|
||||
}
|
||||
|
||||
private void SaveSizeAndLocation()
|
||||
{
|
||||
System.Drawing.Point location;
|
||||
System.Drawing.Size size;
|
||||
|
||||
// save location and size if the state is normal
|
||||
if (this.WindowState == FormWindowState.Normal)
|
||||
{
|
||||
location = this.Location;
|
||||
size = this.Size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// save the RestoreBounds if the form is minimized or maximized
|
||||
location = this.RestoreBounds.Location;
|
||||
size = this.RestoreBounds.Size;
|
||||
}
|
||||
|
||||
var config = Configuration.Instance;
|
||||
|
||||
config.MainFormX = location.X;
|
||||
config.MainFormY = location.Y;
|
||||
|
||||
config.MainFormWidth = size.Width;
|
||||
config.MainFormHeight = size.Height;
|
||||
|
||||
config.MainFormIsMaximized = this.WindowState == FormWindowState.Maximized;
|
||||
}
|
||||
|
||||
#region reload grid
|
||||
private bool isProcessingGridSelect = false;
|
||||
private void reloadGrid()
|
||||
private void reloadGridAndUpdateBottomNumbers(object _ = null, object __ = null)
|
||||
{
|
||||
// suppressed filter while init'ing UI
|
||||
var prev_isProcessingGridSelect = isProcessingGridSelect;
|
||||
isProcessingGridSelect = true;
|
||||
setGrid();
|
||||
this.UIThreadSync(() => setGrid());
|
||||
isProcessingGridSelect = prev_isProcessingGridSelect;
|
||||
|
||||
// UI init complete. now we can apply filter
|
||||
doFilter(lastGoodFilter);
|
||||
this.UIThreadAsync(() => doFilter(lastGoodFilter));
|
||||
|
||||
setBackupCounts(null, null);
|
||||
}
|
||||
|
||||
#region reload grid
|
||||
private ProductsGrid currProductsGrid;
|
||||
private void setGrid()
|
||||
{
|
||||
@@ -150,14 +78,12 @@ namespace LibationWinForms
|
||||
{
|
||||
gridPanel.Controls.Remove(currProductsGrid);
|
||||
currProductsGrid.VisibleCountChanged -= setVisibleCount;
|
||||
currProductsGrid.BackupCountsChanged -= setBackupCountsAsync;
|
||||
currProductsGrid.Dispose();
|
||||
}
|
||||
|
||||
currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill };
|
||||
currProductsGrid.VisibleCountChanged += setVisibleCount;
|
||||
currProductsGrid.BackupCountsChanged += setBackupCountsAsync;
|
||||
gridPanel.UIThread(() => gridPanel.Controls.Add(currProductsGrid));
|
||||
gridPanel.UIThreadSync(() => gridPanel.Controls.Add(currProductsGrid));
|
||||
currProductsGrid.Display();
|
||||
}
|
||||
ResumeLayout();
|
||||
@@ -169,16 +95,45 @@ namespace LibationWinForms
|
||||
#endregion
|
||||
|
||||
#region bottom: backup counts
|
||||
private async void setBackupCountsAsync(object _, object __)
|
||||
private System.ComponentModel.BackgroundWorker updateCountsBw;
|
||||
private bool runBackupCountsAgain;
|
||||
|
||||
private void setBackupCounts(object _, object __)
|
||||
{
|
||||
var libraryStats = await Task.Run(() => LibraryCommands.GetCounts());
|
||||
runBackupCountsAgain = true;
|
||||
|
||||
if (updateCountsBw is not null)
|
||||
return;
|
||||
|
||||
updateCountsBw = new System.ComponentModel.BackgroundWorker();
|
||||
updateCountsBw.DoWork += UpdateCountsBw_DoWork;
|
||||
updateCountsBw.RunWorkerCompleted += UpdateCountsBw_RunWorkerCompleted;
|
||||
updateCountsBw.RunWorkerAsync();
|
||||
}
|
||||
|
||||
private void UpdateCountsBw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
|
||||
{
|
||||
while (runBackupCountsAgain)
|
||||
{
|
||||
runBackupCountsAgain = false;
|
||||
|
||||
var libraryStats = LibraryCommands.GetCounts();
|
||||
e.Result = libraryStats;
|
||||
}
|
||||
updateCountsBw = null;
|
||||
}
|
||||
|
||||
private void UpdateCountsBw_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
var libraryStats = e.Result as LibraryCommands.LibraryStats;
|
||||
|
||||
setBookBackupCounts(libraryStats);
|
||||
setPdfBackupCounts(libraryStats);
|
||||
}
|
||||
|
||||
private void setBookBackupCounts(LibraryCommands.LibraryStats libraryStats)
|
||||
{
|
||||
var backupsCountsLbl_Format = "BACKUPS: No progress: {0} Encrypted: {1} Fully backed up: {2}";
|
||||
var backupsCountsLbl_Format = "BACKUPS: No progress: {0} In process: {1} Fully backed up: {2}";
|
||||
|
||||
// enable/disable export
|
||||
var hasResults = 0 < (libraryStats.booksFullyBackedUp + libraryStats.booksDownloadedOnly + libraryStats.booksNoProgress + libraryStats.booksError);
|
||||
@@ -199,9 +154,9 @@ namespace LibationWinForms
|
||||
: "All books have been liberated";
|
||||
|
||||
// update UI
|
||||
statusStrip1.UIThread(() => backupsCountsLbl.Text = statusStripText);
|
||||
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0);
|
||||
menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText));
|
||||
statusStrip1.UIThreadAsync(() => backupsCountsLbl.Text = statusStripText);
|
||||
menuStrip1.UIThreadAsync(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0);
|
||||
menuStrip1.UIThreadAsync(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText));
|
||||
}
|
||||
private void setPdfBackupCounts(LibraryCommands.LibraryStats libraryStats)
|
||||
{
|
||||
@@ -221,9 +176,9 @@ namespace LibationWinForms
|
||||
: "All PDFs have been downloaded";
|
||||
|
||||
// update UI
|
||||
statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText);
|
||||
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = libraryStats.pdfsNotDownloaded > 0);
|
||||
menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText));
|
||||
statusStrip1.UIThreadAsync(() => pdfsCountsLbl.Text = statusStripText);
|
||||
menuStrip1.UIThreadAsync(() => beginPdfBackupsToolStripMenuItem.Enabled = libraryStats.pdfsNotDownloaded > 0);
|
||||
menuStrip1.UIThreadAsync(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText));
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -248,6 +203,7 @@ namespace LibationWinForms
|
||||
}
|
||||
private void filterBtn_Click(object sender, EventArgs e) => doFilter();
|
||||
|
||||
private bool isProcessingGridSelect = false;
|
||||
private string lastGoodFilter = "";
|
||||
private void doFilter(string filterString)
|
||||
{
|
||||
@@ -275,7 +231,7 @@ namespace LibationWinForms
|
||||
#endregion
|
||||
|
||||
#region Import menu
|
||||
public void RefreshImportMenu()
|
||||
public void RefreshImportMenu(object _ = null, EventArgs __ = null)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var count = persister.AccountsSettings.Accounts.Count;
|
||||
@@ -287,11 +243,6 @@ namespace LibationWinForms
|
||||
|
||||
removeLibraryBooksToolStripMenuItem.Visible = count != 0;
|
||||
|
||||
if (count == 1)
|
||||
{
|
||||
removeLibraryBooksToolStripMenuItem.Click += removeThisAccountToolStripMenuItem_Click;
|
||||
}
|
||||
|
||||
removeSomeAccountsToolStripMenuItem.Visible = count > 1;
|
||||
removeAllAccountsToolStripMenuItem.Visible = count > 1;
|
||||
}
|
||||
@@ -329,13 +280,22 @@ namespace LibationWinForms
|
||||
scanLibraries(scanAccountsDialog.CheckedAccounts);
|
||||
}
|
||||
|
||||
private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
private void removeLibraryBooksToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
// if 0 accounts, this will not be visible
|
||||
// if 1 account, run scanLibrariesRemovedBooks() on this account
|
||||
// if multiple accounts, another menu set will open. do not run scanLibrariesRemovedBooks()
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault();
|
||||
var accounts = persister.AccountsSettings.GetAll();
|
||||
|
||||
if (accounts.Count != 1)
|
||||
return;
|
||||
|
||||
var firstAccount = accounts.Single();
|
||||
scanLibrariesRemovedBooks(firstAccount);
|
||||
}
|
||||
|
||||
// selectively remove books from all accounts
|
||||
private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||
@@ -343,6 +303,7 @@ namespace LibationWinForms
|
||||
scanLibrariesRemovedBooks(allAccounts.ToArray());
|
||||
}
|
||||
|
||||
// selectively remove books from some accounts
|
||||
private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
{
|
||||
using var scanAccountsDialog = new ScanAccountsDialog(this);
|
||||
@@ -360,9 +321,6 @@ namespace LibationWinForms
|
||||
{
|
||||
using var dialog = new RemoveBooksDialog(accounts);
|
||||
dialog.ShowDialog();
|
||||
|
||||
if (dialog.BooksRemoved)
|
||||
reloadGrid();
|
||||
}
|
||||
|
||||
private void scanLibraries(IEnumerable<Account> accounts) => scanLibraries(accounts.ToArray());
|
||||
@@ -375,9 +333,6 @@ namespace LibationWinForms
|
||||
var newAdded = dialog.NewBooksAdded;
|
||||
|
||||
MessageBox.Show($"Total processed: {totalProcessed}\r\nNew: {newAdded}");
|
||||
|
||||
if (totalProcessed > 0)
|
||||
reloadGrid();
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -389,7 +344,18 @@ namespace LibationWinForms
|
||||
=> await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync();
|
||||
|
||||
private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e)
|
||||
=> await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync();
|
||||
{
|
||||
var result = MessageBox.Show(
|
||||
"This converts all m4b titles in your library to mp3 files. Original files are not deleted."
|
||||
+ "\r\nFor large libraries this will take a long time and will take up more disk space."
|
||||
+ "\r\n\r\nContinue?"
|
||||
+ "\r\n\r\n(To always download titles as mp3 instead of m4b, go to Settings: Download my books as .MP3 files)",
|
||||
"Convert all M4b => Mp3?",
|
||||
MessageBoxButtons.YesNo,
|
||||
MessageBoxIcon.Warning);
|
||||
if (result == DialogResult.Yes)
|
||||
await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -484,6 +450,5 @@ namespace LibationWinForms
|
||||
|
||||
private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog();
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
89
LibationWinForms/FormSaveExtension.cs
Normal file
89
LibationWinForms/FormSaveExtension.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using FileManager;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LibationWinForms
|
||||
{
|
||||
public static class FormSaveExtension
|
||||
{
|
||||
public static void RestoreSizeAndLocation(this Form form, Configuration config)
|
||||
{
|
||||
FormSizeAndPosition savedState = config.GetNonString<FormSizeAndPosition>(form.Name);
|
||||
|
||||
if (savedState is null)
|
||||
return;
|
||||
|
||||
// too small -- something must have gone wrong. use defaults
|
||||
if (savedState.Width < 25 || savedState.Height < 25)
|
||||
{
|
||||
savedState.Width = form.Width;
|
||||
savedState.Height = form.Height;
|
||||
}
|
||||
|
||||
// Fit to the current screen size in case the screen resolution changed since the size was last persisted
|
||||
if (savedState.Width > Screen.PrimaryScreen.WorkingArea.Width)
|
||||
savedState.Width = Screen.PrimaryScreen.WorkingArea.Width;
|
||||
if (savedState.Height > Screen.PrimaryScreen.WorkingArea.Height)
|
||||
savedState.Height = Screen.PrimaryScreen.WorkingArea.Height;
|
||||
|
||||
var x = savedState.X;
|
||||
var y = savedState.Y;
|
||||
|
||||
var rect = new Rectangle(x, y, savedState.Width, savedState.Height);
|
||||
|
||||
// is proposed rect on a screen?
|
||||
if (Screen.AllScreens.Any(screen => screen.WorkingArea.Contains(rect)))
|
||||
{
|
||||
form.StartPosition = FormStartPosition.Manual;
|
||||
form.DesktopBounds = rect;
|
||||
}
|
||||
else
|
||||
{
|
||||
form.StartPosition = FormStartPosition.WindowsDefaultLocation;
|
||||
form.Size = rect.Size;
|
||||
}
|
||||
|
||||
// FINAL: for Maximized: start normal state, set size and location, THEN set max state
|
||||
form.WindowState = savedState.IsMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
|
||||
}
|
||||
|
||||
public static void SaveSizeAndLocation(this Form form, Configuration config)
|
||||
{
|
||||
Point location;
|
||||
Size size;
|
||||
var saveState = new FormSizeAndPosition();
|
||||
|
||||
// save location and size if the state is normal
|
||||
if (form.WindowState == FormWindowState.Normal)
|
||||
{
|
||||
location = form.Location;
|
||||
size = form.Size;
|
||||
}
|
||||
else
|
||||
{
|
||||
// save the RestoreBounds if the form is minimized or maximized
|
||||
location = form.RestoreBounds.Location;
|
||||
size = form.RestoreBounds.Size;
|
||||
}
|
||||
|
||||
saveState.X = location.X;
|
||||
saveState.Y = location.Y;
|
||||
|
||||
saveState.Width = size.Width;
|
||||
saveState.Height = size.Height;
|
||||
|
||||
saveState.IsMaximized = form.WindowState == FormWindowState.Maximized;
|
||||
|
||||
config.SetObject(form.Name, saveState);
|
||||
}
|
||||
}
|
||||
class FormSizeAndPosition
|
||||
{
|
||||
public int X;
|
||||
public int Y;
|
||||
public int Height;
|
||||
public int Width;
|
||||
public bool IsMaximized;
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="1.0.2.1" />
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="1.1.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -29,7 +29,6 @@ namespace LibationWinForms
|
||||
public partial class ProductsGrid : UserControl
|
||||
{
|
||||
public event EventHandler<int> VisibleCountChanged;
|
||||
public event EventHandler BackupCountsChanged;
|
||||
|
||||
// alias
|
||||
private DataGridView _dataGridView => gridEntryDataGridView;
|
||||
@@ -81,7 +80,10 @@ namespace LibationWinForms
|
||||
{
|
||||
var filePath = FileManager.AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
|
||||
if (!Go.To.File(filePath))
|
||||
MessageBox.Show($"File not found:\r\n{filePath}");
|
||||
{
|
||||
var suffix = string.IsNullOrWhiteSpace(filePath) ? "" : $":\r\n{filePath}";
|
||||
MessageBox.Show($"File not found" + suffix);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,8 +103,6 @@ namespace LibationWinForms
|
||||
liveGridEntry.Liberate = (bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
||||
|
||||
liveGridEntry.EndEdit();
|
||||
|
||||
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -119,8 +119,7 @@ namespace LibationWinForms
|
||||
//
|
||||
// transform into sorted GridEntry.s BEFORE binding
|
||||
//
|
||||
using var context = DbContexts.GetContext();
|
||||
var lib = context.GetLibrary_Flat_NoTracking();
|
||||
var lib = DbContexts.GetLibrary_Flat_NoTracking();
|
||||
|
||||
// if no data. hide all columns. return
|
||||
if (!lib.Any())
|
||||
@@ -145,8 +144,6 @@ namespace LibationWinForms
|
||||
|
||||
// FILTER
|
||||
Filter();
|
||||
|
||||
BackupCountsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -1,25 +1,9 @@
|
||||
-- begin VERSIONING ---------------------------------------------------------------------------------------------------------------------
|
||||
https://github.com/rmcrackan/Libation/releases
|
||||
|
||||
v3.1.8 : Experimental: add Australia to locale options
|
||||
v3.1.7 : Improved logging
|
||||
v3.1.6 : Bugfix: some series indexes/sequences formats cause library not to import
|
||||
v3.1.5 : Bugfix: some series indexes/sequences could cause library not to import
|
||||
v3.1.4 : Bugfix: IsAuthorNarrated was returning no books
|
||||
v3.1.3 : fix weirdness with build number
|
||||
v3.1.2 : minor bug fixes
|
||||
pre-github versions:
|
||||
v3.1.1 : Check if upgrade available on github
|
||||
v3.1.0 : FIRST PUBLIC RELEASE
|
||||
v3.1-beta.11 : Improved configuration and settings file management. Configurable logging
|
||||
v3.1-beta.10 : New feature: clicking Liberate button on a liberated item navigates to that audio file
|
||||
v3.1-beta.9 : New feature: liberate individual book
|
||||
v3.1-beta.8 : Bugfix: decrypt file conflict
|
||||
v3.1-beta.7 : Bugfix: decrypt book with no author
|
||||
v3.1-beta.6 : Improved logging
|
||||
v3.1-beta.5 : Improved importing
|
||||
v3.1-beta.4 : Added beta-specific logging
|
||||
v3.1-beta.3 : fixed known performance issue: Full-screen grid is slow to respond loading when books aren't liberated
|
||||
v3.1-beta.2 : fixed known performance issue: Tag add/edit
|
||||
v3.1-beta.1 : RELEASE TO BETA
|
||||
v3.0.3 : Switch to SQLite. No longer relies on LocalDB, which must be installed separately
|
||||
v3.0.2 : Final using LocalDB
|
||||
@@ -29,29 +13,6 @@ v2 : new library page scraping. still chrome cookies. all decryption is handled
|
||||
v1 : old library ajax scraping. wish list scraping. chrome cookies. directly call local inAudible. .net framework
|
||||
-- end VERSIONING ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- begin HOW TO PUBLISH ---------------------------------------------------------------------------------------------------------------------
|
||||
OPTION 1: UI
|
||||
rt-clk project project > Publish...
|
||||
click Publish
|
||||
|
||||
OPTION 2: cmd line
|
||||
change dir to folder containing project
|
||||
cd C:\[full...path]\Libation\LibationWinForms
|
||||
this will use the parameters specified in csproj
|
||||
dotnet publish -c Release
|
||||
|
||||
OPTION 3: cmd line, custom
|
||||
open csproj
|
||||
remove: PublishTrimmed, PublishReadyToRun, RuntimeIdentifier
|
||||
run customized publish. examples:
|
||||
publish all platforms
|
||||
dotnet publish -c Release
|
||||
publish win64 platform only
|
||||
dotnet publish -r win-x64 -c Release
|
||||
publish win64 platform, single-file
|
||||
dotnet publish -r win-x64 -c Release
|
||||
-- end HOW TO PUBLISH ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- begin IMAGES/ICONS ---------------------------------------------------------------------------------------------------------------------
|
||||
edit tags icon images from:
|
||||
icons8.com
|
||||
@@ -60,32 +21,9 @@ edit tags icon images from:
|
||||
'edit' icon: https://www.iconfinder.com/icons/383147/edit_icon
|
||||
-- end IMAGES/ICONS ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- begin AUDIBLE DETAILS ---------------------------------------------------------------------------------------------------------------------
|
||||
alternate book id (eg BK_RAND_006061) is called 'sku' , 'sku_lite' , 'prod_id' , 'product_id' in different parts of the site
|
||||
-- end AUDIBLE DETAILS ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- begin SOLUTION LAYOUT ---------------------------------------------------------------------------------------------------------------------
|
||||
do NOT combine jsons for
|
||||
- audible-scraped persistence: library, book details
|
||||
- libation-generated persistence: FileLocations.json
|
||||
- user-defined persistence: BookTags.json
|
||||
-- end SOLUTION LAYOUT ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
-- begin EF CORE ---------------------------------------------------------------------------------------------------------------------
|
||||
transaction notes
|
||||
-----------------
|
||||
// https://msdn.microsoft.com/en-us/data/dn456843.aspx
|
||||
// Rollback is called by transaction Dispose(). No need to call it explicitly
|
||||
using var dbContext = new LibationContext();
|
||||
using var dbContextTransaction = dbContext.Database.BeginTransaction();
|
||||
refreshAction(dbContext, productItems);
|
||||
dbContext.SaveChanges();
|
||||
dbContextTransaction.Commit();
|
||||
|
||||
aggregate root is transactional boundary
|
||||
// //context.Database.CurrentTransaction
|
||||
//var dbTransaction = Microsoft.EntityFrameworkCore.Storage.DbContextTransactionExtensions.GetDbTransaction(context.Database.CurrentTransaction);
|
||||
// // test with and without : using TransactionScope scope = new TransactionScope();
|
||||
//System.Transactions.Transaction.Current.TransactionCompleted += (sender, e) => { };
|
||||
// also : https://docs.microsoft.com/en-us/dotnet/api/system.transactions.transaction.enlistvolatile
|
||||
-- end EF CORE ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
345
WinFormsDesigner/Form1.Designer.cs
generated
345
WinFormsDesigner/Form1.Designer.cs
generated
@@ -1,345 +0,0 @@
|
||||
namespace WinFormsDesigner
|
||||
{
|
||||
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()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1));
|
||||
this.gridPanel = new System.Windows.Forms.Panel();
|
||||
this.filterHelpBtn = new System.Windows.Forms.Button();
|
||||
this.filterBtn = new System.Windows.Forms.Button();
|
||||
this.filterSearchTb = new System.Windows.Forms.TextBox();
|
||||
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
|
||||
this.importToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.noAccountsYetAddAccountToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.liberateToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginBookBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.beginPdfBackupsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.quickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.firstFilterIsDefaultToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.editQuickFiltersToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.settingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.accountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.basicSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.advancedSettingsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.statusStrip1 = new System.Windows.Forms.StatusStrip();
|
||||
this.visibleCountLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.springLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel();
|
||||
this.addFilterBtn = new System.Windows.Forms.Button();
|
||||
this.exportLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||
this.menuStrip1.SuspendLayout();
|
||||
this.statusStrip1.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// gridPanel
|
||||
//
|
||||
this.gridPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
|
||||
| System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.gridPanel.Location = new System.Drawing.Point(12, 56);
|
||||
this.gridPanel.Name = "gridPanel";
|
||||
this.gridPanel.Size = new System.Drawing.Size(839, 386);
|
||||
this.gridPanel.TabIndex = 5;
|
||||
//
|
||||
// filterHelpBtn
|
||||
//
|
||||
this.filterHelpBtn.Location = new System.Drawing.Point(12, 27);
|
||||
this.filterHelpBtn.Name = "filterHelpBtn";
|
||||
this.filterHelpBtn.Size = new System.Drawing.Size(22, 23);
|
||||
this.filterHelpBtn.TabIndex = 3;
|
||||
this.filterHelpBtn.Text = "?";
|
||||
this.filterHelpBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// filterBtn
|
||||
//
|
||||
this.filterBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.filterBtn.Location = new System.Drawing.Point(776, 27);
|
||||
this.filterBtn.Name = "filterBtn";
|
||||
this.filterBtn.Size = new System.Drawing.Size(75, 23);
|
||||
this.filterBtn.TabIndex = 2;
|
||||
this.filterBtn.Text = "Filter";
|
||||
this.filterBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// filterSearchTb
|
||||
//
|
||||
this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
|
||||
| System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.filterSearchTb.Location = new System.Drawing.Point(186, 29);
|
||||
this.filterSearchTb.Name = "filterSearchTb";
|
||||
this.filterSearchTb.Size = new System.Drawing.Size(584, 20);
|
||||
this.filterSearchTb.TabIndex = 1;
|
||||
//
|
||||
// menuStrip1
|
||||
//
|
||||
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.importToolStripMenuItem,
|
||||
this.liberateToolStripMenuItem,
|
||||
this.exportToolStripMenuItem,
|
||||
this.quickFiltersToolStripMenuItem,
|
||||
this.settingsToolStripMenuItem});
|
||||
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
|
||||
this.menuStrip1.Name = "menuStrip1";
|
||||
this.menuStrip1.Size = new System.Drawing.Size(863, 24);
|
||||
this.menuStrip1.TabIndex = 0;
|
||||
this.menuStrip1.Text = "menuStrip1";
|
||||
//
|
||||
// importToolStripMenuItem
|
||||
//
|
||||
this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.noAccountsYetAddAccountToolStripMenuItem,
|
||||
this.scanLibraryToolStripMenuItem,
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem,
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem});
|
||||
this.importToolStripMenuItem.Name = "importToolStripMenuItem";
|
||||
this.importToolStripMenuItem.Size = new System.Drawing.Size(55, 20);
|
||||
this.importToolStripMenuItem.Text = "&Import";
|
||||
//
|
||||
// noAccountsYetAddAccountToolStripMenuItem
|
||||
//
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Name = "noAccountsYetAddAccountToolStripMenuItem";
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.noAccountsYetAddAccountToolStripMenuItem.Text = "No accounts yet. A&dd Account...";
|
||||
//
|
||||
// scanLibraryToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem";
|
||||
this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryToolStripMenuItem.Text = "Scan &Library";
|
||||
//
|
||||
// scanLibraryOfAllAccountsToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Name = "scanLibraryOfAllAccountsToolStripMenuItem";
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryOfAllAccountsToolStripMenuItem.Text = "Scan Library of &All Accounts";
|
||||
//
|
||||
// scanLibraryOfSomeAccountsToolStripMenuItem
|
||||
//
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Name = "scanLibraryOfSomeAccountsToolStripMenuItem";
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22);
|
||||
this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts...";
|
||||
//
|
||||
// liberateToolStripMenuItem
|
||||
//
|
||||
this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.beginBookBackupsToolStripMenuItem,
|
||||
this.beginPdfBackupsToolStripMenuItem});
|
||||
this.liberateToolStripMenuItem.Name = "liberateToolStripMenuItem";
|
||||
this.liberateToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
|
||||
this.liberateToolStripMenuItem.Text = "&Liberate";
|
||||
//
|
||||
// beginBookBackupsToolStripMenuItem
|
||||
//
|
||||
this.beginBookBackupsToolStripMenuItem.Name = "beginBookBackupsToolStripMenuItem";
|
||||
this.beginBookBackupsToolStripMenuItem.Size = new System.Drawing.Size(248, 22);
|
||||
this.beginBookBackupsToolStripMenuItem.Text = "Begin &Book and PDF Backups: {0}";
|
||||
//
|
||||
// beginPdfBackupsToolStripMenuItem
|
||||
//
|
||||
this.beginPdfBackupsToolStripMenuItem.Name = "beginPdfBackupsToolStripMenuItem";
|
||||
this.beginPdfBackupsToolStripMenuItem.Size = new System.Drawing.Size(248, 22);
|
||||
this.beginPdfBackupsToolStripMenuItem.Text = "Begin &PDF Only Backups: {0}";
|
||||
//
|
||||
// exportToolStripMenuItem
|
||||
//
|
||||
this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.exportLibraryToolStripMenuItem});
|
||||
this.exportToolStripMenuItem.Name = "exportToolStripMenuItem";
|
||||
this.exportToolStripMenuItem.Size = new System.Drawing.Size(53, 20);
|
||||
this.exportToolStripMenuItem.Text = "E&xport";
|
||||
//
|
||||
// quickFiltersToolStripMenuItem
|
||||
//
|
||||
this.quickFiltersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.firstFilterIsDefaultToolStripMenuItem,
|
||||
this.editQuickFiltersToolStripMenuItem,
|
||||
this.toolStripSeparator1});
|
||||
this.quickFiltersToolStripMenuItem.Name = "quickFiltersToolStripMenuItem";
|
||||
this.quickFiltersToolStripMenuItem.Size = new System.Drawing.Size(84, 20);
|
||||
this.quickFiltersToolStripMenuItem.Text = "Quick &Filters";
|
||||
//
|
||||
// firstFilterIsDefaultToolStripMenuItem
|
||||
//
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Name = "firstFilterIsDefaultToolStripMenuItem";
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
|
||||
this.firstFilterIsDefaultToolStripMenuItem.Text = "Start Libation with 1st filter &Default";
|
||||
//
|
||||
// editQuickFiltersToolStripMenuItem
|
||||
//
|
||||
this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem";
|
||||
this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22);
|
||||
this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters...";
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
this.toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(253, 6);
|
||||
//
|
||||
// settingsToolStripMenuItem
|
||||
//
|
||||
this.settingsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.accountsToolStripMenuItem,
|
||||
this.basicSettingsToolStripMenuItem,
|
||||
this.advancedSettingsToolStripMenuItem});
|
||||
this.settingsToolStripMenuItem.Name = "settingsToolStripMenuItem";
|
||||
this.settingsToolStripMenuItem.Size = new System.Drawing.Size(61, 20);
|
||||
this.settingsToolStripMenuItem.Text = "&Settings";
|
||||
//
|
||||
// accountsToolStripMenuItem
|
||||
//
|
||||
this.accountsToolStripMenuItem.Name = "accountsToolStripMenuItem";
|
||||
this.accountsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.accountsToolStripMenuItem.Text = "&Accounts...";
|
||||
//
|
||||
// basicSettingsToolStripMenuItem
|
||||
//
|
||||
this.basicSettingsToolStripMenuItem.Name = "basicSettingsToolStripMenuItem";
|
||||
this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.basicSettingsToolStripMenuItem.Text = "&Basic Settings...";
|
||||
//
|
||||
// advancedSettingsToolStripMenuItem
|
||||
//
|
||||
this.advancedSettingsToolStripMenuItem.Name = "advancedSettingsToolStripMenuItem";
|
||||
this.advancedSettingsToolStripMenuItem.Size = new System.Drawing.Size(181, 22);
|
||||
this.advancedSettingsToolStripMenuItem.Text = "Ad&vanced Settings...";
|
||||
//
|
||||
// statusStrip1
|
||||
//
|
||||
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.visibleCountLbl,
|
||||
this.springLbl,
|
||||
this.backupsCountsLbl,
|
||||
this.pdfsCountsLbl});
|
||||
this.statusStrip1.Location = new System.Drawing.Point(0, 445);
|
||||
this.statusStrip1.Name = "statusStrip1";
|
||||
this.statusStrip1.Size = new System.Drawing.Size(863, 22);
|
||||
this.statusStrip1.TabIndex = 6;
|
||||
this.statusStrip1.Text = "statusStrip1";
|
||||
//
|
||||
// visibleCountLbl
|
||||
//
|
||||
this.visibleCountLbl.Name = "visibleCountLbl";
|
||||
this.visibleCountLbl.Size = new System.Drawing.Size(61, 17);
|
||||
this.visibleCountLbl.Text = "Visible: {0}";
|
||||
//
|
||||
// springLbl
|
||||
//
|
||||
this.springLbl.Name = "springLbl";
|
||||
this.springLbl.Size = new System.Drawing.Size(233, 17);
|
||||
this.springLbl.Spring = true;
|
||||
//
|
||||
// backupsCountsLbl
|
||||
//
|
||||
this.backupsCountsLbl.Name = "backupsCountsLbl";
|
||||
this.backupsCountsLbl.Size = new System.Drawing.Size(336, 17);
|
||||
this.backupsCountsLbl.Text = "BACKUPS: No progress: {0} Encrypted: {1} Fully backed up: {2}";
|
||||
//
|
||||
// pdfsCountsLbl
|
||||
//
|
||||
this.pdfsCountsLbl.Name = "pdfsCountsLbl";
|
||||
this.pdfsCountsLbl.Size = new System.Drawing.Size(218, 17);
|
||||
this.pdfsCountsLbl.Text = "| PDFs: NOT d/l\'ed: {0} Downloaded: {1}";
|
||||
//
|
||||
// addFilterBtn
|
||||
//
|
||||
this.addFilterBtn.Location = new System.Drawing.Point(40, 27);
|
||||
this.addFilterBtn.Name = "addFilterBtn";
|
||||
this.addFilterBtn.Size = new System.Drawing.Size(140, 23);
|
||||
this.addFilterBtn.TabIndex = 4;
|
||||
this.addFilterBtn.Text = "Add To Quick Filters";
|
||||
this.addFilterBtn.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// exportLibraryToolStripMenuItem
|
||||
//
|
||||
this.exportLibraryToolStripMenuItem.Name = "exportLibraryToolStripMenuItem";
|
||||
this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(180, 22);
|
||||
this.exportLibraryToolStripMenuItem.Text = "E&xport Library...";
|
||||
//
|
||||
// Form1
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(863, 467);
|
||||
this.Controls.Add(this.filterBtn);
|
||||
this.Controls.Add(this.addFilterBtn);
|
||||
this.Controls.Add(this.filterSearchTb);
|
||||
this.Controls.Add(this.filterHelpBtn);
|
||||
this.Controls.Add(this.statusStrip1);
|
||||
this.Controls.Add(this.gridPanel);
|
||||
this.Controls.Add(this.menuStrip1);
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.MainMenuStrip = this.menuStrip1;
|
||||
this.Name = "Form1";
|
||||
this.Text = "Libation: Liberate your Library";
|
||||
this.menuStrip1.ResumeLayout(false);
|
||||
this.menuStrip1.PerformLayout();
|
||||
this.statusStrip1.ResumeLayout(false);
|
||||
this.statusStrip1.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Panel gridPanel;
|
||||
private System.Windows.Forms.MenuStrip menuStrip1;
|
||||
private System.Windows.Forms.ToolStripMenuItem importToolStripMenuItem;
|
||||
private System.Windows.Forms.StatusStrip statusStrip1;
|
||||
private System.Windows.Forms.ToolStripStatusLabel springLbl;
|
||||
private System.Windows.Forms.ToolStripStatusLabel visibleCountLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem liberateToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel backupsCountsLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem beginBookBackupsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripStatusLabel pdfsCountsLbl;
|
||||
private System.Windows.Forms.ToolStripMenuItem beginPdfBackupsToolStripMenuItem;
|
||||
private System.Windows.Forms.TextBox filterSearchTb;
|
||||
private System.Windows.Forms.Button filterBtn;
|
||||
private System.Windows.Forms.Button filterHelpBtn;
|
||||
private System.Windows.Forms.ToolStripMenuItem settingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem quickFiltersToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem firstFilterIsDefaultToolStripMenuItem;
|
||||
private System.Windows.Forms.Button addFilterBtn;
|
||||
private System.Windows.Forms.ToolStripMenuItem editQuickFiltersToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
|
||||
private System.Windows.Forms.ToolStripMenuItem basicSettingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem advancedSettingsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem accountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfAllAccountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem scanLibraryOfSomeAccountsToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem noAccountsYetAddAccountToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem exportToolStripMenuItem;
|
||||
private System.Windows.Forms.ToolStripMenuItem exportLibraryToolStripMenuItem;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace WinFormsDesigner
|
||||
{
|
||||
public partial class Form1 : Form
|
||||
{
|
||||
public Form1()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
|
||||
namespace WinFormsDesigner
|
||||
{
|
||||
internal class GridEntry
|
||||
{
|
||||
[Browsable(false)]
|
||||
public string Tags { get; set; }
|
||||
[Browsable(false)]
|
||||
public IEnumerable<string> TagsEnumerated { get; set; }
|
||||
|
||||
[Browsable(false)]
|
||||
public string Download_Status { get; set; }
|
||||
|
||||
public Image Cover { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Authors { get; set; }
|
||||
public string Narrators { get; set; }
|
||||
public int Length { get; set; }
|
||||
public string Series { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Category { get; set; }
|
||||
public string Product_Rating { get; set; }
|
||||
public DateTime? Purchase_Date { get; set; }
|
||||
public string My_Rating { get; set; }
|
||||
public string Misc { get; set; }
|
||||
}
|
||||
}
|
||||
191
WinFormsDesigner/ProductsGrid.Designer.cs
generated
191
WinFormsDesigner/ProductsGrid.Designer.cs
generated
@@ -1,191 +0,0 @@
|
||||
namespace WinFormsDesigner
|
||||
{
|
||||
partial class ProductsGrid
|
||||
{
|
||||
/// <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 Component 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.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components);
|
||||
this.gridEntryDataGridView = new System.Windows.Forms.DataGridView();
|
||||
this.dataGridViewImageColumn1 = new System.Windows.Forms.DataGridViewImageColumn();
|
||||
this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn3 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn4 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn5 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn6 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn7 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn8 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn9 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn10 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridViewTextBoxColumn11 = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// gridEntryBindingSource
|
||||
//
|
||||
this.gridEntryBindingSource.DataSource = typeof(WinFormsDesigner.GridEntry);
|
||||
//
|
||||
// gridEntryDataGridView
|
||||
//
|
||||
this.gridEntryDataGridView.AutoGenerateColumns = false;
|
||||
this.gridEntryDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.gridEntryDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.dataGridViewImageColumn1,
|
||||
this.dataGridViewTextBoxColumn1,
|
||||
this.dataGridViewTextBoxColumn2,
|
||||
this.dataGridViewTextBoxColumn3,
|
||||
this.dataGridViewTextBoxColumn4,
|
||||
this.dataGridViewTextBoxColumn5,
|
||||
this.dataGridViewTextBoxColumn6,
|
||||
this.dataGridViewTextBoxColumn7,
|
||||
this.dataGridViewTextBoxColumn8,
|
||||
this.dataGridViewTextBoxColumn9,
|
||||
this.dataGridViewTextBoxColumn10,
|
||||
this.dataGridViewTextBoxColumn11});
|
||||
this.gridEntryDataGridView.DataSource = this.gridEntryBindingSource;
|
||||
this.gridEntryDataGridView.Location = new System.Drawing.Point(54, 58);
|
||||
this.gridEntryDataGridView.Name = "gridEntryDataGridView";
|
||||
this.gridEntryDataGridView.Size = new System.Drawing.Size(300, 220);
|
||||
this.gridEntryDataGridView.TabIndex = 0;
|
||||
//
|
||||
// dataGridViewImageColumn1
|
||||
//
|
||||
this.dataGridViewImageColumn1.DataPropertyName = "Cover";
|
||||
this.dataGridViewImageColumn1.HeaderText = "Cover";
|
||||
this.dataGridViewImageColumn1.Name = "dataGridViewImageColumn1";
|
||||
this.dataGridViewImageColumn1.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn1
|
||||
//
|
||||
this.dataGridViewTextBoxColumn1.DataPropertyName = "Title";
|
||||
this.dataGridViewTextBoxColumn1.HeaderText = "Title";
|
||||
this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1";
|
||||
this.dataGridViewTextBoxColumn1.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn2
|
||||
//
|
||||
this.dataGridViewTextBoxColumn2.DataPropertyName = "Authors";
|
||||
this.dataGridViewTextBoxColumn2.HeaderText = "Authors";
|
||||
this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2";
|
||||
this.dataGridViewTextBoxColumn2.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn3
|
||||
//
|
||||
this.dataGridViewTextBoxColumn3.DataPropertyName = "Narrators";
|
||||
this.dataGridViewTextBoxColumn3.HeaderText = "Narrators";
|
||||
this.dataGridViewTextBoxColumn3.Name = "dataGridViewTextBoxColumn3";
|
||||
this.dataGridViewTextBoxColumn3.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn4
|
||||
//
|
||||
this.dataGridViewTextBoxColumn4.DataPropertyName = "Length";
|
||||
this.dataGridViewTextBoxColumn4.HeaderText = "Length";
|
||||
this.dataGridViewTextBoxColumn4.Name = "dataGridViewTextBoxColumn4";
|
||||
this.dataGridViewTextBoxColumn4.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn5
|
||||
//
|
||||
this.dataGridViewTextBoxColumn5.DataPropertyName = "Series";
|
||||
this.dataGridViewTextBoxColumn5.HeaderText = "Series";
|
||||
this.dataGridViewTextBoxColumn5.Name = "dataGridViewTextBoxColumn5";
|
||||
this.dataGridViewTextBoxColumn5.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn6
|
||||
//
|
||||
this.dataGridViewTextBoxColumn6.DataPropertyName = "Description";
|
||||
this.dataGridViewTextBoxColumn6.HeaderText = "Description";
|
||||
this.dataGridViewTextBoxColumn6.Name = "dataGridViewTextBoxColumn6";
|
||||
this.dataGridViewTextBoxColumn6.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn7
|
||||
//
|
||||
this.dataGridViewTextBoxColumn7.DataPropertyName = "Category";
|
||||
this.dataGridViewTextBoxColumn7.HeaderText = "Category";
|
||||
this.dataGridViewTextBoxColumn7.Name = "dataGridViewTextBoxColumn7";
|
||||
this.dataGridViewTextBoxColumn7.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn8
|
||||
//
|
||||
this.dataGridViewTextBoxColumn8.DataPropertyName = "Product_Rating";
|
||||
this.dataGridViewTextBoxColumn8.HeaderText = "Product_Rating";
|
||||
this.dataGridViewTextBoxColumn8.Name = "dataGridViewTextBoxColumn8";
|
||||
this.dataGridViewTextBoxColumn8.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn9
|
||||
//
|
||||
this.dataGridViewTextBoxColumn9.DataPropertyName = "Purchase_Date";
|
||||
this.dataGridViewTextBoxColumn9.HeaderText = "Purchase_Date";
|
||||
this.dataGridViewTextBoxColumn9.Name = "dataGridViewTextBoxColumn9";
|
||||
this.dataGridViewTextBoxColumn9.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn10
|
||||
//
|
||||
this.dataGridViewTextBoxColumn10.DataPropertyName = "My_Rating";
|
||||
this.dataGridViewTextBoxColumn10.HeaderText = "My_Rating";
|
||||
this.dataGridViewTextBoxColumn10.Name = "dataGridViewTextBoxColumn10";
|
||||
this.dataGridViewTextBoxColumn10.ReadOnly = true;
|
||||
//
|
||||
// dataGridViewTextBoxColumn11
|
||||
//
|
||||
this.dataGridViewTextBoxColumn11.DataPropertyName = "Misc";
|
||||
this.dataGridViewTextBoxColumn11.HeaderText = "Misc";
|
||||
this.dataGridViewTextBoxColumn11.Name = "dataGridViewTextBoxColumn11";
|
||||
this.dataGridViewTextBoxColumn11.ReadOnly = true;
|
||||
//
|
||||
// ProductsGrid
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.Controls.Add(this.gridEntryDataGridView);
|
||||
this.Name = "ProductsGrid";
|
||||
this.Size = new System.Drawing.Size(434, 329);
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit();
|
||||
((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.BindingSource gridEntryBindingSource;
|
||||
private System.Windows.Forms.DataGridView gridEntryDataGridView;
|
||||
private System.Windows.Forms.DataGridViewImageColumn dataGridViewImageColumn1;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn3;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn4;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn5;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn6;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn7;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn8;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn9;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn10;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn11;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace WinFormsDesigner
|
||||
{
|
||||
// INSTRUCTIONS TO UPDATE DATA_GRID_VIEW
|
||||
// - delete current DataGridView
|
||||
// - view > other windows > data sources
|
||||
// - refresh
|
||||
// OR
|
||||
// - Add New Data Source
|
||||
// Object. Next
|
||||
// WinFormsDesigner
|
||||
// AudibleDTO
|
||||
// GridEntry
|
||||
// - go to Design view
|
||||
// - click on Data Sources > ProductItem. drowdown: DataGridView
|
||||
// - drag/drop ProductItem on design surface
|
||||
public partial class ProductsGrid : UserControl
|
||||
{
|
||||
public ProductsGrid()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="gridEntryBindingSource.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
</root>
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace WinFormsDesigner
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new Form1());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This file is automatically generated by Visual Studio .Net. It is
|
||||
used to store generic object data source configuration information.
|
||||
Renaming the file extension or editing the content of this file may
|
||||
cause the file to be unrecognizable by the program.
|
||||
-->
|
||||
<GenericObjectDataSource DisplayName="GridEntry" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
|
||||
<TypeInfo>WinFormsDesigner.GridEntry, WinFormsDesigner, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
|
||||
</GenericObjectDataSource>
|
||||
@@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{0807616A-A77A-4B08-A65A-1582B09E114B}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>WinFormsDesigner</RootNamespace>
|
||||
<AssemblyName>WinFormsDesigner</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>
|
||||
</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Form1.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Form1.Designer.cs">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ProductsGrid.cs">
|
||||
<SubType>UserControl</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ProductsGrid.Designer.cs">
|
||||
<DependentUpon>ProductsGrid.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="GridEntry.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<EmbeddedResource Include="Form1.resx">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="ProductsGrid.resx">
|
||||
<DependentUpon>ProductsGrid.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<None Include="Properties\DataSources\WinFormsDesigner.GridEntry.datasource" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -1,45 +1,42 @@
|
||||
Logging/Debugging (EF CORE)
|
||||
===========================
|
||||
Once you configure logging on a DbContext instance it will be enabled on all instances of that DbContext type
|
||||
using var context = new MyContext();
|
||||
context.ConfigureLogging(s => System.Diagnostics.Debug.WriteLine(s)); // write to Visual Studio "Output" tab
|
||||
//context.ConfigureLogging(s => Console.WriteLine(s));
|
||||
see comments at top of file:
|
||||
Dinah.EntityFrameworkCore\DbContextLoggingExtensions.cs
|
||||
|
||||
LocalDb
|
||||
=======
|
||||
only works if LocalDb is separately installed on host box
|
||||
SSMS db connection: (LocalDb)\MSSQLLocalDB
|
||||
eg: Server=(localdb)\mssqllocaldb;Database=DataLayer.LibationContext;Integrated Security=true;
|
||||
LocalDb database files live at:
|
||||
C:\Users\[user]\DataLayer.LibationContext.mdf
|
||||
C:\Users\[user]\DataLayer.LibationContext_log.ldf
|
||||
|
||||
Migrations
|
||||
==========
|
||||
|
||||
Visual Studio, EF Core
|
||||
----------------------
|
||||
Migrations, quick
|
||||
=================
|
||||
View > Other Windows > Package Manager Console
|
||||
Default project: DataLayer
|
||||
Startup project: DataLayer
|
||||
since we have mult contexts, must use -context:
|
||||
Add-Migration MyComment -context LibationContext
|
||||
Update-Database -context LibationContext
|
||||
Add-Migration MyComment -context LibationContext
|
||||
Update-Database -context LibationContext
|
||||
Startup project: reset to prev. eg: LibationLauncher
|
||||
|
||||
|
||||
ERROR
|
||||
=====
|
||||
Add-Migration : The term 'Add-Migration' is not recognized as the name of a cmdlet, function, script file, or operable program
|
||||
|
||||
SOLUTION
|
||||
--------
|
||||
add nuget pkg: Microsoft.EntityFrameworkCore.Tools
|
||||
Migrations, detailed
|
||||
====================
|
||||
if only 1 context present, can omit -context arg:
|
||||
Add-Migration MyComment
|
||||
Update-Database
|
||||
|
||||
|
||||
SQLite
|
||||
======
|
||||
SQLite does not support all migrations (schema changes) due to limitations in SQLite
|
||||
delete db before running Update-Database?
|
||||
Migrations, errors
|
||||
=================
|
||||
if add-migration xyz throws and error, don't take the error msg at face value. try again with add-migration xyz -verbose
|
||||
|
||||
ERROR: Add-Migration : The term 'Add-Migration' is not recognized as the name of a cmdlet, function, script file, or operable program
|
||||
SOLUTION: add nuget pkg: Microsoft.EntityFrameworkCore.Tools
|
||||
|
||||
|
||||
SqLite config
|
||||
=============
|
||||
relative:
|
||||
optionsBuilder.UseSqlite("Data Source=blogging.db");
|
||||
absolute (use fwd slashes):
|
||||
optionsBuilder.UseSqlite("Data Source=C:/foo/bar/blogging.db");
|
||||
|
||||
|
||||
Logging/Debugging (EF CORE)
|
||||
===========================
|
||||
Once you configure logging on a DbContext instance it will be enabled on all instances of that DbContext type
|
||||
using var context = new MyContext();
|
||||
context.ConfigureLogging(s => System.Diagnostics.Debug.WriteLine(s)); // write to Visual Studio "Output" tab
|
||||
//context.ConfigureLogging(s => Console.WriteLine(s));
|
||||
see comments at top of file:
|
||||
Dinah.EntityFrameworkCore\DbContextLoggingExtensions.cs
|
||||
@@ -10,8 +10,8 @@
|
||||
<PackageReference Include="FluentAssertions" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.6" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.6" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
<PackageReference Include="FluentAssertions" Version="6.1.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.6" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.6" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.7" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.7" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
||||
@@ -43,7 +43,6 @@ CODING GUIDELINES
|
||||
IDIOSYNCRASIES
|
||||
|
||||
* Libation has been a labor of love for years. This passion project has also been a 'forever project' and thus a place to test out new technologies, styles, and ideas. Consistency is not guaranteed. Legacy code is a certainty.
|
||||
* WinFormsDesigner is not used directly and you will likely have no reason to bother with it. Even though all win forms _functionality_ has been re-implemented in .netcore, last I checked there were still a small number of edge cases not yet implemented in the .netcore based _designer_. WinFormsDesigner is .NET Framework which supports all old designer features.
|
||||
|
||||
|
||||
BEING NOTIFIED OF CHANGES
|
||||
|
||||
Reference in New Issue
Block a user