Compare commits

...

4 Commits

Author SHA1 Message Date
Robert McRackan
f29968f379 conditional re-index 2021-09-23 14:44:11 -04:00
Robert McRackan
14e14ba9bd batch book status updates
bug fixes, esp. threading
2021-09-23 14:33:04 -04:00
Robert McRackan
613c97524a User get getting "bindingsource cannot be its own data source" error. Can't repro locally. Adding SyncBindingSource for datagrid views to attempt to solve it 2021-09-23 10:41:33 -04:00
Robert McRackan
4fd16f04e0 More robust error handling, especially before logging can be initialized 2021-09-23 09:19:35 -04:00
13 changed files with 197 additions and 57 deletions

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Version>6.0.3.1</Version>
<Version>6.0.6.2</Version>
</PropertyGroup>
<ItemGroup>

View File

@@ -61,7 +61,16 @@ namespace AppScaffolding
return;
var startingContents = File.ReadAllText(APPSETTINGS_JSON);
var jObj = JObject.Parse(startingContents);
JObject jObj;
try
{
jObj = JObject.Parse(startingContents);
}
catch
{
return;
}
action(jObj);
@@ -130,7 +139,16 @@ namespace AppScaffolding
return;
var startingContents = File.ReadAllText(SettingsJsonPath);
var jObj = JObject.Parse(startingContents);
JObject jObj;
try
{
jObj = JObject.Parse(startingContents);
}
catch
{
return;
}
action(jObj);

View File

@@ -106,6 +106,11 @@ namespace DataLayer
#endregion
#region LiberatedStatuses
/// <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;
private LiberatedStatus _bookStatus;
private LiberatedStatus? _pdfStatus;
@@ -132,13 +137,41 @@ namespace DataLayer
ItemChanged?.Invoke(this, nameof(PdfStatus));
}
}
}
#endregion
#region batch changes
public static event EventHandler<string> Batch_ItemChanged;
public void BatchMode_UpdateBookStatus(LiberatedStatus value)
{
if (_bookStatus != value)
{
_bookStatus = value;
batchFlag = true;
}
}
// don't overwrite current with null. Therefore input is "LiberatedStatus" not "LiberatedStatus?"
public void BatchMode_UpdatePdfStatus(LiberatedStatus value)
{
if (_pdfStatus != value)
{
_pdfStatus = value;
batchFlag = true;
}
}
private static bool batchFlag = false;
public static void BatchMode_Finalize()
{
if (batchFlag)
Batch_ItemChanged?.Invoke(null, null);
batchFlag = false;
}
#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}";
}
}

View File

@@ -315,7 +315,7 @@ namespace LibationSearchEngine
var docs = searcher.Search(query, 1);
var scoreDoc = docs.ScoreDocs.SingleOrDefault();
if (scoreDoc == null)
throw new Exception("document not found");
return;
var document = searcher.Doc(scoreDoc.Doc);

View File

@@ -33,10 +33,37 @@ namespace LibationWinForms.Dialogs
}
private void githubLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
=> Go.To.Url("https://github.com/rmcrackan/Libation/issues");
{
var url = "https://github.com/rmcrackan/Libation/issues";
try
{
Go.To.Url(url);
}
catch
{
MessageBox.Show($"Error opening url\r\n{url}", "Error opening url", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void logsLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
=> Go.To.Folder(FileManager.Configuration.Instance.LibationFiles);
{
string dir = "";
try
{
dir = FileManager.Configuration.Instance.LibationFiles;
}
catch { }
try
{
Go.To.Folder(dir);
}
catch
{
MessageBox.Show($"Error opening folder\r\n{dir}", "Error opening folder", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void okBtn_Click(object sender, EventArgs e)
{

View File

@@ -38,7 +38,7 @@ namespace LibationWinForms.Dialogs
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.gridEntryBindingSource = new LibationWinForms.SyncBindingSource(this.components);
this.btnRemoveBooks = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
((System.ComponentModel.ISupportInitialize)(this._dataGridView)).BeginInit();
@@ -176,7 +176,7 @@ namespace LibationWinForms.Dialogs
#endregion
private System.Windows.Forms.DataGridView _dataGridView;
private System.Windows.Forms.BindingSource gridEntryBindingSource;
private LibationWinForms.SyncBindingSource gridEntryBindingSource;
private System.Windows.Forms.Button btnRemoveBooks;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.DataGridViewCheckBoxColumn removeDataGridViewCheckBoxColumn;

View File

@@ -34,6 +34,8 @@ namespace LibationWinForms
this.FormClosing += (_, _) => this.SaveSizeAndLocation(Configuration.Instance);
LibraryCommands.LibrarySizeChanged += reloadGridAndUpdateBottomNumbers;
LibraryCommands.BookUserDefinedItemCommitted += setBackupCounts;
// used by async migrations to update ui when complete
DataLayer.UserDefinedItem.Batch_ItemChanged += reloadGridAndUpdateBottomNumbers;
var format = System.Drawing.Imaging.ImageFormat.Jpeg;
PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format));

View File

@@ -27,17 +27,16 @@ namespace LibationWinForms
#endregion
public event EventHandler Committed;
private Book Book => LibraryBook.Book;
private Image _cover;
private Action Refilter { get; }
public GridEntry(LibraryBook libraryBook, Action refilterOnChanged = null)
public GridEntry(LibraryBook libraryBook)
{
LibraryBook = libraryBook;
Refilter = refilterOnChanged;
_memberValues = CreateMemberValueDictionary();
//Get cover art. If it's default, subscribe to PictureCached
{
(bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80));
@@ -142,7 +141,7 @@ namespace LibationWinForms
Book.UserDefinedItem.BookStatus = displayStatus;
Refilter?.Invoke();
Committed?.Invoke(this, null);
}
#endregion

View File

@@ -1,6 +1,5 @@
using LibationWinForms.Dialogs;
using System;
using System.Linq;
using System;
using LibationWinForms.Dialogs;
namespace LibationWinForms
{
@@ -15,7 +14,11 @@ namespace LibationWinForms
/// <returns>One of the System.Windows.Forms.DialogResult values.</returns>
public static System.Windows.Forms.DialogResult Show(string text, string caption, Exception exception)
{
Serilog.Log.Logger.Error(exception, "Alert admin error: {@DebugText}", new { text, caption });
try
{
Serilog.Log.Logger.Error(exception, "Alert admin error: {@DebugText}", new { text, caption });
}
catch { }
using var form = new MessageBoxAlertAdminDialog(text, caption, exception);
return form.ShowDialog();

View File

@@ -30,7 +30,7 @@
{
this.components = new System.ComponentModel.Container();
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components);
this.gridEntryBindingSource = new LibationWinForms.SyncBindingSource(this.components);
this.gridEntryDataGridView = new System.Windows.Forms.DataGridView();
this.dataGridViewImageButtonBoxColumn1 = new LibationWinForms.LiberateDataGridViewImageButtonColumn();
this.dataGridViewImageColumn1 = new System.Windows.Forms.DataGridViewImageColumn();
@@ -222,7 +222,7 @@
#endregion
private System.Windows.Forms.BindingSource gridEntryBindingSource;
private LibationWinForms.SyncBindingSource gridEntryBindingSource;
private System.Windows.Forms.DataGridView gridEntryDataGridView;
private LiberateDataGridViewImageButtonColumn dataGridViewImageButtonBoxColumn1;
private System.Windows.Forms.DataGridViewImageColumn dataGridViewImageColumn1;

View File

@@ -3,9 +3,9 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using ApplicationServices;
using DataLayer;
using Dinah.Core;
using Dinah.Core.DataBinding;
using Dinah.Core.Threading;
using Dinah.Core.Windows.Forms;
using LibationWinForms.Dialogs;
@@ -130,7 +130,12 @@ namespace LibationWinForms
}
var orderedGridEntries = lib
.Select(lb => new GridEntry(lb, Filter)).ToList()
.Select(lb =>
{
var entry = new GridEntry(lb);
entry.Committed += (_, __) => Filter();
return entry;
}).ToList()
// default load order
.OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate)))
//// more advanced example: sort by author, then series, then title
@@ -166,8 +171,11 @@ namespace LibationWinForms
var bindingContext = BindingContext[_dataGridView.DataSource];
bindingContext.SuspendBinding();
{
for (var r = _dataGridView.RowCount - 1; r >= 0; r--)
_dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId);
this.UIThreadSync(() =>
{
for (var r = _dataGridView.RowCount - 1; r >= 0; r--)
_dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId);
});
}
//Causes repainting of the DataGridView

View File

@@ -24,36 +24,54 @@ namespace LibationWinForms
[STAThread]
static void Main()
{
//// Uncomment to see Console. Must be called before anything writes to Console.
//// Only use while debugging. Acts erratically in the wild
//AllocConsole();
try
{
//// Uncomment to see Console. Must be called before anything writes to Console.
//// Only use while debugging. Acts erratically in the wild
//AllocConsole();
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//***********************************************//
// //
// do not use Configuration before this line //
// //
//***********************************************//
// Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration
var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations();
//***********************************************//
// //
// do not use Configuration before this line //
// //
//***********************************************//
// Migrations which must occur before configuration is loaded for the first time. Usually ones which alter the Configuration
var config = AppScaffolding.LibationScaffolding.RunPreConfigMigrations();
RunInstaller(config);
RunInstaller(config);
// most migrations go in here
AppScaffolding.LibationScaffolding.RunPostConfigMigrations();
// most migrations go in here
AppScaffolding.LibationScaffolding.RunPostConfigMigrations();
// migrations which require Forms or are long-running
RunWindowsOnlyMigrations(config);
// migrations which require Forms or are long-running
RunWindowsOnlyMigrations(config);
MessageBoxVerboseLoggingWarning.ShowIfTrue();
MessageBoxVerboseLoggingWarning.ShowIfTrue();
#if !DEBUG
checkForUpdate();
#endif
}
catch (Exception ex)
{
var title = "Fatal error, pre-logging";
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
try
{
MessageBoxAlertAdmin.Show(body, title, ex);
}
catch
{
MessageBox.Show($"{body}\r\n\r\n{ex.Message}\r\n\r\n{ex.StackTrace}", title, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
return;
}
AppScaffolding.LibationScaffolding.RunPostMigrationScaffolding();
Application.Run(new Form1());
@@ -241,22 +259,26 @@ namespace LibationWinForms
// assign these strings and enums/ints unconditionally. EFCore will only update if changed
if (fileType == FileType.PDF)
book.UserDefinedItem.PdfStatus = LiberatedStatus.Liberated;
book.UserDefinedItem.BatchMode_UpdatePdfStatus(LiberatedStatus.Liberated);
if (fileType == FileType.Audio)
{
var lhack = libhackFiles.FirstOrDefault(f => f.ContainsInsensitive(asin));
if (lhack is null)
book.UserDefinedItem.BookStatus = LiberatedStatus.Liberated;
book.UserDefinedItem.BatchMode_UpdateBookStatus(LiberatedStatus.Liberated);
else
{
book.UserDefinedItem.BookStatus = LiberatedStatus.Error;
book.UserDefinedItem.BatchMode_UpdateBookStatus(LiberatedStatus.Error);
libhackFilesToDelete.Add(lhack);
}
}
}
context.SaveChanges();
// in order: save to db, full reindex from db, refresh ui
var changed = context.SaveChanges();
if (changed > 0)
ApplicationServices.SearchEngineCommands.FullReIndex();
UserDefinedItem.BatchMode_Finalize();
// only do this after save changes
foreach (var libhackFile in libhackFilesToDelete)
@@ -300,14 +322,14 @@ namespace LibationWinForms
if (result != DialogResult.Yes)
return;
using var fileSelector = new SaveFileDialog { FileName = zipName, Filter = "Zip Files (*.zip)|*.zip|All files (*.*)|*.*" };
if (fileSelector.ShowDialog() != DialogResult.OK)
return;
var selectedPath = fileSelector.FileName;
try
{
LibationWinForms.BookLiberation.ProcessorAutomationController.DownloadFile(zipUrl, selectedPath, true);
using var fileSelector = new SaveFileDialog { FileName = zipName, Filter = "Zip Files (*.zip)|*.zip|All files (*.*)|*.*" };
if (fileSelector.ShowDialog() != DialogResult.OK)
return;
var selectedPath = fileSelector.FileName;
BookLiberation.ProcessorAutomationController.DownloadFile(zipUrl, selectedPath, true);
}
catch (Exception ex)
{

View File

@@ -0,0 +1,28 @@
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
// https://stackoverflow.com/a/32886415
namespace LibationWinForms
{
public class SyncBindingSource : BindingSource
{
private SynchronizationContext syncContext { get; }
public SyncBindingSource() : base()
=> syncContext = SynchronizationContext.Current;
public SyncBindingSource(IContainer container) : base(container)
=> syncContext = SynchronizationContext.Current;
public SyncBindingSource(object dataSource, string dataMember) : base(dataSource, dataMember)
=> syncContext = SynchronizationContext.Current;
protected override void OnListChanged(ListChangedEventArgs e)
{
if (syncContext != null)
syncContext.Send(_ => base.OnListChanged(e), null);
else
base.OnListChanged(e);
}
}
}