From f68f374b7812f15ffc295dccfd8387da07512454 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 01:07:24 -0600 Subject: [PATCH 01/64] Use resource user-agent. --- FileLiberator/DownloadDecryptBook.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index 1bfea2a6..a2b961c6 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -78,7 +78,7 @@ namespace FileLiberator contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl, contentLic?.Voucher?.Key, contentLic?.Voucher?.Iv, - "Audible/671 CFNetwork/1240.0.4 Darwin/20.6.0" + Resources.UserAgent ); if (Configuration.Instance.AllowLibationFixup) From 35f54779f0b82f657d728e81c20eaad9d3e208ef Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 01:08:00 -0600 Subject: [PATCH 02/64] Add library command to get books removed from library. --- ApplicationServices/LibraryCommands.cs | 53 +++++++++++++++++++++++++- InternalUtilities/AudibleApiActions.cs | 8 ++-- 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/ApplicationServices/LibraryCommands.cs b/ApplicationServices/LibraryCommands.cs index 97bdae3e..4672b942 100644 --- a/ApplicationServices/LibraryCommands.cs +++ b/ApplicationServices/LibraryCommands.cs @@ -20,6 +20,57 @@ namespace ApplicationServices public static class LibraryCommands { + private static LibraryOptions.ResponseGroupOptions LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS; + + public static async Task> FindInactiveBooks(Func loginCallbackFactoryFunc, List existingLibrary, params Account[] accounts) + { + //These are the minimum response groups required for the + //library scanner to pass all validation and filtering. + LibraryResponseGroups = + LibraryOptions.ResponseGroupOptions.ProductAttrs | + LibraryOptions.ResponseGroupOptions.ProductDesc | + LibraryOptions.ResponseGroupOptions.Relationships; + + if (accounts is null || accounts.Length == 0) + return new List(); + + try + { + var libraryItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts); + Log.Logger.Information($"GetAllLibraryItems: Total count {libraryItems.Count}"); + + var missingBookList = existingLibrary.Where(b => libraryItems.Count(i => i.DtoItem.Asin == b.Book.AudibleProductId) == 0).ToList(); + + return missingBookList; + } + catch (AudibleApi.Authentication.LoginFailedException lfEx) + { + lfEx.SaveFiles(FileManager.Configuration.Instance.LibationFiles); + + // nuget Serilog.Exceptions would automatically log custom properties + // However, it comes with a scary warning when used with EntityFrameworkCore which I'm not yet ready to implement: + // https://github.com/RehanSaeed/Serilog.Exceptions + // work-around: use 3rd param. don't just put exception object in 3rd param -- info overload: stack trace, etc + Log.Logger.Error(lfEx, "Error scanning library. Login failed. {@DebugInfo}", new + { + lfEx.RequestUrl, + ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode, + ResponseStatusCodeDesc = lfEx.ResponseStatusCode, + lfEx.ResponseInputFields, + lfEx.ResponseBodyFilePaths + }); + throw; + } + catch (Exception ex) + { + Log.Logger.Error(ex, "Error importing library"); + throw; + } + finally + { + LibraryResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS; + } + } #region FULL LIBRARY scan and import public static async Task<(int totalCount, int newCount)> ImportAccountAsync(Func loginCallbackFactoryFunc, params Account[] accounts) { @@ -95,7 +146,7 @@ namespace ApplicationServices Account = account?.MaskedLogEntry ?? "[null]" }); - var dtoItems = await AudibleApiActions.GetLibraryValidatedAsync(api); + var dtoItems = await AudibleApiActions.GetLibraryValidatedAsync(api, LibraryResponseGroups); return dtoItems.Select(d => new ImportItem { DtoItem = d, AccountId = account.AccountId, LocaleName = account.Locale?.Name }).ToList(); } diff --git a/InternalUtilities/AudibleApiActions.cs b/InternalUtilities/AudibleApiActions.cs index 9cbe58ee..0d1e2edd 100644 --- a/InternalUtilities/AudibleApiActions.cs +++ b/InternalUtilities/AudibleApiActions.cs @@ -47,18 +47,18 @@ namespace InternalUtilities // 2 retries == 3 total .RetryAsync(2); - public static Task> GetLibraryValidatedAsync(Api api) + public static Task> GetLibraryValidatedAsync(Api api, LibraryOptions.ResponseGroupOptions responseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS) { // bug on audible's side. the 1st time after a long absence, a query to get library will return without titles or authors. a subsequent identical query will be successful. this is true whether or tokens are refreshed // worse, this 1st dummy call doesn't seem to help: // var page = await api.GetLibraryAsync(new AudibleApi.LibraryOptions { NumberOfResultPerPage = 1, PageNumber = 1, PurchasedAfter = DateTime.Now.AddYears(-20), ResponseGroups = AudibleApi.LibraryOptions.ResponseGroupOptions.ALL_OPTIONS }); // i don't want to incur the cost of making a full dummy call every time because it fails sometimes - return policy.ExecuteAsync(() => getItemsAsync(api)); + return policy.ExecuteAsync(() => getItemsAsync(api, responseGroups)); } - private static async Task> getItemsAsync(Api api) + private static async Task> getItemsAsync(Api api, LibraryOptions.ResponseGroupOptions responseGroups) { - var items = await api.GetAllLibraryItemsAsync(); + var items = await api.GetAllLibraryItemsAsync(responseGroups); // remove episode parents items.RemoveAll(i => i.IsEpisodes); From f621ca63e88e166856f32874a7b17795840749f9 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 01:08:35 -0600 Subject: [PATCH 03/64] Added RemoveBooksDialog --- .../Dialogs/RemoveBooksDialog.Designer.cs | 188 +++++++++++++++ LibationWinForms/Dialogs/RemoveBooksDialog.cs | 217 ++++++++++++++++++ .../Dialogs/RemoveBooksDialog.resx | 66 ++++++ ...ationWinForm.RemovableGridEntry.datasource | 10 + 4 files changed, 481 insertions(+) create mode 100644 LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs create mode 100644 LibationWinForms/Dialogs/RemoveBooksDialog.cs create mode 100644 LibationWinForms/Dialogs/RemoveBooksDialog.resx create mode 100644 LibationWinForms/Properties/DataSources/LibationWinForm.RemovableGridEntry.datasource diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs new file mode 100644 index 00000000..d4484ce3 --- /dev/null +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs @@ -0,0 +1,188 @@ + +namespace LibationWinForms.Dialogs +{ + partial class RemoveBooksDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); + this.dataGridView1 = new System.Windows.Forms.DataGridView(); + this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components); + this.btnRemoveBooks = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + 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.DatePurchased = new System.Windows.Forms.DataGridViewTextBoxColumn(); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit(); + this.SuspendLayout(); + // + // dataGridView1 + // + this.dataGridView1.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.dataGridView1.AutoGenerateColumns = false; + this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.removeDataGridViewCheckBoxColumn, + this.coverDataGridViewImageColumn, + this.titleDataGridViewTextBoxColumn, + this.authorsDataGridViewTextBoxColumn, + this.miscDataGridViewTextBoxColumn, + this.DatePurchased}); + this.dataGridView1.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.dataGridView1.DefaultCellStyle = dataGridViewCellStyle1; + this.dataGridView1.Location = new System.Drawing.Point(0, 0); + this.dataGridView1.Name = "dataGridView1"; + this.dataGridView1.RowHeadersVisible = false; + this.dataGridView1.RowTemplate.Height = 82; + this.dataGridView1.Size = new System.Drawing.Size(800, 409); + this.dataGridView1.TabIndex = 0; + // + // 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(169, 15); + this.label1.TabIndex = 2; + this.label1.Text = "{0} books selected for removal."; + // + // 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.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; + // + // DatePurchased + // + this.DatePurchased.DataPropertyName = "DatePurchased"; + this.DatePurchased.HeaderText = "Date Purchased"; + this.DatePurchased.Name = "DatePurchased"; + this.DatePurchased.ReadOnly = true; + this.DatePurchased.Width = 120; + // + // 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.dataGridView1); + this.Name = "RemoveBooksDialog"; + this.Text = "RemoveBooksDialog"; + this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown); + ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.DataGridView dataGridView1; + 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 DatePurchased; + } +} \ No newline at end of file diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs new file mode 100644 index 00000000..273176cb --- /dev/null +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -0,0 +1,217 @@ +using ApplicationServices; +using DataLayer; +using InternalUtilities; +using LibationWinForms.Login; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Windows.Forms; +using Dinah.Core.DataBinding; +using System.Runtime.CompilerServices; +using Dinah.Core.Drawing; +using System.Collections; + +namespace LibationWinForms.Dialogs +{ + public partial class RemoveBooksDialog : Form + { + public bool BooksRemoved { get; private set; } + + private Account[] _accounts { get; } + private List _libraryBooks; + private SortableBindingList _removableGridEntries; + private string _labelFormat; + private int SelectedCount => SelectedEntries?.Count() ?? 0; + private IEnumerable SelectedEntries => _removableGridEntries?.Where(b => b.Remove); + + public RemoveBooksDialog(params Account[] accounts) + { + _libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking(); + _accounts = accounts; + InitializeComponent(); + _labelFormat = label1.Text; + + dataGridView1.CellContentClick += (s, e) => dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit); + dataGridView1.CellValueChanged += DataGridView1_CellValueChanged; + dataGridView1.BindingContextChanged += (s, e) => UpdateSelection(); + + var orderedGridEntries = _libraryBooks + .Select(lb => new RemovableGridEntry(new GridEntry(lb))) + .OrderByDescending(ge => ge.GridEntry.Purchase_Date) + .ToList(); + + _removableGridEntries = orderedGridEntries.ToSortableBindingList(); + gridEntryBindingSource.DataSource = _removableGridEntries; + + dataGridView1.Enabled = false; + } + + private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) + { + if (e.ColumnIndex == 0) + { + UpdateSelection(); + } + } + + private async void RemoveBooksDialog_Shown(object sender, EventArgs e) + { + if (_accounts == null || _accounts.Length == 0) + return; + try + { + var rmovedBooks = await LibraryCommands.FindInactiveBooks((account) => new WinformResponder(account), _libraryBooks, _accounts); + + var removable = _removableGridEntries.Where(rge => rmovedBooks.Count(rb => rb.Book.AudibleProductId == rge.GridEntry.AudibleProductId) == 1); + + if (removable.Count() == 0) + return; + + foreach (var r in removable) + r.Remove = true; + + UpdateSelection(); + } + catch (Exception ex) + { + MessageBoxAlertAdmin.Show( + "Error scanning library. You may still manually select books to remove from Libation's library.", + "Error scanning library", + ex); + } + finally + { + dataGridView1.Enabled = true; + } + } + + private void btnRemoveBooks_Click(object sender, EventArgs e) + { + var selected = SelectedEntries.ToList(); + + if (selected.Count == 0) return; + + string titles = string.Join("\r\n", selected.Select(rge => "-" + rge.Title)); + + var result = MessageBox.Show( + this, + $"Are you sure you want to remove the following {selected.Count} books from Libation's library?\r\n\r\n{titles}", + "Remove books from Libation?", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question, + MessageBoxDefaultButton.Button1); + + if (result == DialogResult.Yes) + { + using var context = DbContexts.GetContext(); + + var libBooks = context.GetLibrary_Flat_NoTracking(); + + var removeLibraryBooks = libBooks.Where(lb => selected.Count(rge => rge.GridEntry.AudibleProductId == lb.Book.AudibleProductId) == 1).ToArray(); + context.Library.RemoveRange(removeLibraryBooks); + context.SaveChanges(); + BooksRemoved = true; + + foreach (var rEntry in selected) + _removableGridEntries.Remove(rEntry); + + UpdateSelection(); + } + } + private void UpdateSelection() + { + dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Descending); + var selectedCount = SelectedCount; + label1.Text = string.Format(_labelFormat, selectedCount); + btnRemoveBooks.Enabled = selectedCount > 0; + } + } + class CompareBool : IComparer + { + public int Compare(object x, object y) + { + var rge1 = x as RemovableGridEntry; + var rge2 = y as RemovableGridEntry; + + return rge1.Remove.CompareTo(rge2.Remove); + } + } + + + internal class RemovableGridEntry : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + public GridEntry GridEntry { get; } + + public bool Remove + { + get + { + return _remove; + } + set + { + if (_remove != value) + { + _remove = value; + NotifyPropertyChanged(); + } + } + } + public Image Cover + { + get + { + return _cover; + } + set + { + _cover = value; + NotifyPropertyChanged(); + } + } + public string Title => GetDisplayValue(nameof(GridEntry.Title), GridEntry.Title); + public string Authors => GetDisplayValue(nameof(GridEntry.Authors), GridEntry.Authors); + public string Misc => GetDisplayValue(nameof(GridEntry.Misc), GridEntry.Misc); + public string DatePurchased => GetDisplayValue(nameof(GridEntry.Purchase_Date), GridEntry.Purchase_Date); + + private bool _remove = false; + private Image _cover; + + public RemovableGridEntry(GridEntry gridEntry) + { + GridEntry = gridEntry; + + var picDef = new FileManager.PictureDefinition(GridEntry.GetBook().PictureId, FileManager.PictureSize._80x80); + (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); + + if (isDefault) + FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; + + _cover = ImageReader.ToImage(picture); + } + + private void PictureStorage_PictureCached(object sender, string pictureId) + { + if (pictureId == GridEntry.GetBook().PictureId) + { + Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); + FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; + } + } + + private string GetDisplayValue(string propertyName, string defaultValue) + { + if (GridEntry.TryDisplayValue(propertyName, out string value)) + return value; + return defaultValue; + } + + private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + } +} diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.resx b/LibationWinForms/Dialogs/RemoveBooksDialog.resx new file mode 100644 index 00000000..303f91a6 --- /dev/null +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.resx @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + True + + + 17, 17 + + \ No newline at end of file diff --git a/LibationWinForms/Properties/DataSources/LibationWinForm.RemovableGridEntry.datasource b/LibationWinForms/Properties/DataSources/LibationWinForm.RemovableGridEntry.datasource new file mode 100644 index 00000000..da01a0bf --- /dev/null +++ b/LibationWinForms/Properties/DataSources/LibationWinForm.RemovableGridEntry.datasource @@ -0,0 +1,10 @@ + + + + LibationWinForms.Dialogs.RemovableGridEntry, LibationWinForms, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + \ No newline at end of file From af486412816355e53fd9eb4f79520fcedc608708 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 01:08:58 -0600 Subject: [PATCH 04/64] Added remove books context menu item. --- LibationWinForms/Form1.Designer.cs | 574 +++++++++++++++-------------- LibationWinForms/Form1.cs | 15 + 2 files changed, 307 insertions(+), 282 deletions(-) diff --git a/LibationWinForms/Form1.Designer.cs b/LibationWinForms/Form1.Designer.cs index 93c3914a..e4659ea3 100644 --- a/LibationWinForms/Form1.Designer.cs +++ b/LibationWinForms/Form1.Designer.cs @@ -28,312 +28,321 @@ /// 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.convertAllM4bToMp3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.exportLibraryToolStripMenuItem = 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.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.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.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.convertAllM4bToMp3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportLibraryToolStripMenuItem = 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.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.removeLibraryBooksToolStripMenuItem = 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(14, 65); - this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.gridPanel.Name = "gridPanel"; - this.gridPanel.Size = new System.Drawing.Size(979, 445); - this.gridPanel.TabIndex = 5; - // - // filterHelpBtn - // - this.filterHelpBtn.Location = new System.Drawing.Point(14, 31); - this.filterHelpBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.filterHelpBtn.Name = "filterHelpBtn"; - this.filterHelpBtn.Size = new System.Drawing.Size(26, 27); - this.filterHelpBtn.TabIndex = 3; - this.filterHelpBtn.Text = "?"; - this.filterHelpBtn.UseVisualStyleBackColor = true; - this.filterHelpBtn.Click += new System.EventHandler(this.filterHelpBtn_Click); - // - // 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(905, 31); - this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.filterBtn.Name = "filterBtn"; - this.filterBtn.Size = new System.Drawing.Size(88, 27); - this.filterBtn.TabIndex = 2; - this.filterBtn.Text = "Filter"; - this.filterBtn.UseVisualStyleBackColor = true; - this.filterBtn.Click += new System.EventHandler(this.filterBtn_Click); - // - // filterSearchTb - // - this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.gridPanel.Location = new System.Drawing.Point(14, 65); + this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.gridPanel.Name = "gridPanel"; + this.gridPanel.Size = new System.Drawing.Size(979, 445); + this.gridPanel.TabIndex = 5; + // + // filterHelpBtn + // + this.filterHelpBtn.Location = new System.Drawing.Point(14, 31); + this.filterHelpBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.filterHelpBtn.Name = "filterHelpBtn"; + this.filterHelpBtn.Size = new System.Drawing.Size(26, 27); + this.filterHelpBtn.TabIndex = 3; + this.filterHelpBtn.Text = "?"; + this.filterHelpBtn.UseVisualStyleBackColor = true; + this.filterHelpBtn.Click += new System.EventHandler(this.filterHelpBtn_Click); + // + // 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(905, 31); + this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.filterBtn.Name = "filterBtn"; + this.filterBtn.Size = new System.Drawing.Size(88, 27); + this.filterBtn.TabIndex = 2; + this.filterBtn.Text = "Filter"; + this.filterBtn.UseVisualStyleBackColor = true; + this.filterBtn.Click += new System.EventHandler(this.filterBtn_Click); + // + // 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(217, 33); - this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.filterSearchTb.Name = "filterSearchTb"; - this.filterSearchTb.Size = new System.Drawing.Size(681, 23); - this.filterSearchTb.TabIndex = 1; - this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); - // - // menuStrip1 - // - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.filterSearchTb.Location = new System.Drawing.Point(217, 33); + this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.filterSearchTb.Name = "filterSearchTb"; + this.filterSearchTb.Size = new System.Drawing.Size(681, 23); + this.filterSearchTb.TabIndex = 1; + this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); + // + // 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.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); - this.menuStrip1.Size = new System.Drawing.Size(1007, 24); - this.menuStrip1.TabIndex = 0; - this.menuStrip1.Text = "menuStrip1"; - // - // importToolStripMenuItem - // - this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); + this.menuStrip1.Size = new System.Drawing.Size(1007, 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..."; - this.noAccountsYetAddAccountToolStripMenuItem.Click += new System.EventHandler(this.noAccountsYetAddAccountToolStripMenuItem_Click); - // - // scanLibraryToolStripMenuItem - // - this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem"; - this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22); - this.scanLibraryToolStripMenuItem.Text = "Scan &Library"; - this.scanLibraryToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryToolStripMenuItem_Click); - // - // scanLibraryOfAllAccountsToolStripMenuItem - // - this.scanLibraryOfAllAccountsToolStripMenuItem.Name = "scanLibraryOfAllAccountsToolStripMenuItem"; - this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22); - this.scanLibraryOfAllAccountsToolStripMenuItem.Text = "Scan Library of &All Accounts"; - this.scanLibraryOfAllAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfAllAccountsToolStripMenuItem_Click); - // - // scanLibraryOfSomeAccountsToolStripMenuItem - // - this.scanLibraryOfSomeAccountsToolStripMenuItem.Name = "scanLibraryOfSomeAccountsToolStripMenuItem"; - this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22); - this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts..."; - this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click); - // - // liberateToolStripMenuItem - // - this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.scanLibraryOfSomeAccountsToolStripMenuItem, + this.removeLibraryBooksToolStripMenuItem}); + 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..."; + this.noAccountsYetAddAccountToolStripMenuItem.Click += new System.EventHandler(this.noAccountsYetAddAccountToolStripMenuItem_Click); + // + // scanLibraryToolStripMenuItem + // + this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem"; + this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.scanLibraryToolStripMenuItem.Text = "Scan &Library"; + this.scanLibraryToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryToolStripMenuItem_Click); + // + // scanLibraryOfAllAccountsToolStripMenuItem + // + this.scanLibraryOfAllAccountsToolStripMenuItem.Name = "scanLibraryOfAllAccountsToolStripMenuItem"; + this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.scanLibraryOfAllAccountsToolStripMenuItem.Text = "Scan Library of &All Accounts"; + this.scanLibraryOfAllAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfAllAccountsToolStripMenuItem_Click); + // + // scanLibraryOfSomeAccountsToolStripMenuItem + // + this.scanLibraryOfSomeAccountsToolStripMenuItem.Name = "scanLibraryOfSomeAccountsToolStripMenuItem"; + this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts..."; + this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click); + // + // liberateToolStripMenuItem + // + this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.beginBookBackupsToolStripMenuItem, this.beginPdfBackupsToolStripMenuItem, this.convertAllM4bToMp3ToolStripMenuItem}); - 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(284, 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.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 = false; - this.convertAllM4bToMp3ToolStripMenuItem.Click += new System.EventHandler(this.convertAllM4bToMp3ToolStripMenuItem_Click); - // - // exportToolStripMenuItem - // - this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + 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(284, 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.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 = false; + this.convertAllM4bToMp3ToolStripMenuItem.Click += new System.EventHandler(this.convertAllM4bToMp3ToolStripMenuItem_Click); + // + // 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"; - // - // exportLibraryToolStripMenuItem - // - this.exportLibraryToolStripMenuItem.Name = "exportLibraryToolStripMenuItem"; - this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(156, 22); - this.exportLibraryToolStripMenuItem.Text = "E&xport Library..."; - this.exportLibraryToolStripMenuItem.Click += new System.EventHandler(this.exportLibraryToolStripMenuItem_Click); - // - // quickFiltersToolStripMenuItem - // - this.quickFiltersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.exportToolStripMenuItem.Name = "exportToolStripMenuItem"; + this.exportToolStripMenuItem.Size = new System.Drawing.Size(53, 20); + this.exportToolStripMenuItem.Text = "E&xport"; + // + // exportLibraryToolStripMenuItem + // + this.exportLibraryToolStripMenuItem.Name = "exportLibraryToolStripMenuItem"; + this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(156, 22); + this.exportLibraryToolStripMenuItem.Text = "E&xport Library..."; + this.exportLibraryToolStripMenuItem.Click += new System.EventHandler(this.exportLibraryToolStripMenuItem_Click); + // + // 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"; - this.firstFilterIsDefaultToolStripMenuItem.Click += new System.EventHandler(this.FirstFilterIsDefaultToolStripMenuItem_Click); - // - // editQuickFiltersToolStripMenuItem - // - this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem"; - this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22); - this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters..."; - this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.EditQuickFiltersToolStripMenuItem_Click); - // - // 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.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"; + this.firstFilterIsDefaultToolStripMenuItem.Click += new System.EventHandler(this.FirstFilterIsDefaultToolStripMenuItem_Click); + // + // editQuickFiltersToolStripMenuItem + // + this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem"; + this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22); + this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters..."; + this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.EditQuickFiltersToolStripMenuItem_Click); + // + // 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.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(133, 22); - this.accountsToolStripMenuItem.Text = "&Accounts..."; - this.accountsToolStripMenuItem.Click += new System.EventHandler(this.accountsToolStripMenuItem_Click); - // - // basicSettingsToolStripMenuItem - // - this.basicSettingsToolStripMenuItem.Name = "basicSettingsToolStripMenuItem"; - this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(133, 22); - this.basicSettingsToolStripMenuItem.Text = "&Settings..."; - this.basicSettingsToolStripMenuItem.Click += new System.EventHandler(this.basicSettingsToolStripMenuItem_Click); - // - // statusStrip1 - // - this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + 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(133, 22); + this.accountsToolStripMenuItem.Text = "&Accounts..."; + this.accountsToolStripMenuItem.Click += new System.EventHandler(this.accountsToolStripMenuItem_Click); + // + // basicSettingsToolStripMenuItem + // + this.basicSettingsToolStripMenuItem.Name = "basicSettingsToolStripMenuItem"; + this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(133, 22); + this.basicSettingsToolStripMenuItem.Text = "&Settings..."; + this.basicSettingsToolStripMenuItem.Click += new System.EventHandler(this.basicSettingsToolStripMenuItem_Click); + // + // 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, 517); - this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); - this.statusStrip1.Size = new System.Drawing.Size(1007, 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(375, 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(47, 31); - this.addFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.addFilterBtn.Name = "addFilterBtn"; - this.addFilterBtn.Size = new System.Drawing.Size(163, 27); - this.addFilterBtn.TabIndex = 4; - this.addFilterBtn.Text = "Add To Quick Filters"; - this.addFilterBtn.UseVisualStyleBackColor = true; - this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click); - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1007, 539); - 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.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(); - this.statusStrip1.ResumeLayout(false); - this.statusStrip1.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); + this.statusStrip1.Location = new System.Drawing.Point(0, 517); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); + this.statusStrip1.Size = new System.Drawing.Size(1007, 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(375, 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(47, 31); + this.addFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.addFilterBtn.Name = "addFilterBtn"; + this.addFilterBtn.Size = new System.Drawing.Size(163, 27); + this.addFilterBtn.TabIndex = 4; + this.addFilterBtn.Text = "Add To Quick Filters"; + this.addFilterBtn.UseVisualStyleBackColor = true; + this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click); + // + // removeLibraryBooksToolStripMenuItem + // + 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); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1007, 539); + 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.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(); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); } @@ -368,5 +377,6 @@ private System.Windows.Forms.ToolStripMenuItem exportToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem exportLibraryToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem convertAllM4bToMp3ToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem removeLibraryBooksToolStripMenuItem; } } diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index 8acabf08..4221082c 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -455,5 +455,20 @@ namespace LibationWinForms private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); #endregion + + private void removeLibraryBooksToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); + scanLibrariesRemovedBooks(firstAccount); + } + private void scanLibrariesRemovedBooks(params Account[] accounts) + { + using var dialog = new RemoveBooksDialog(accounts); + dialog.ShowDialog(); + + if (dialog.BooksRemoved) + reloadGrid(); + } } } From d48bd5ad0770fef13141511461ad07b839929d25 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 15:00:24 -0600 Subject: [PATCH 05/64] Major UI refactoring. --- LibationLauncher/LibationLauncher.csproj | 2 +- .../DataGridViewImageButtonColumn.cs | 50 +++ LibationWinForms/Dialogs/RemoveBooksDialog.cs | 21 +- .../EditTagsDataGridViewImageButtonColumn.cs | 26 ++ LibationWinForms/GridEntry.cs | 356 +++++++++------- LibationWinForms/IObjectMemberComparable.cs | 11 + .../LiberateDataGridViewImageButtonColumn.cs | 30 ++ LibationWinForms/ObjectComparer[T].cs | 10 + LibationWinForms/ObjectMemberComparer[T].cs | 21 + LibationWinForms/ProductsGrid.Designer.cs | 363 ++++++++++------- LibationWinForms/ProductsGrid.cs | 383 +++++------------- LibationWinForms/ProductsGrid.resx | 77 +--- LibationWinForms/SortableBindingList2[T].cs | 72 ++++ 13 files changed, 769 insertions(+), 653 deletions(-) create mode 100644 LibationWinForms/DataGridViewImageButtonColumn.cs create mode 100644 LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs create mode 100644 LibationWinForms/IObjectMemberComparable.cs create mode 100644 LibationWinForms/LiberateDataGridViewImageButtonColumn.cs create mode 100644 LibationWinForms/ObjectComparer[T].cs create mode 100644 LibationWinForms/ObjectMemberComparer[T].cs create mode 100644 LibationWinForms/SortableBindingList2[T].cs diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index c0d121d3..5af2cd25 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.0 + 5.4.9.79 diff --git a/LibationWinForms/DataGridViewImageButtonColumn.cs b/LibationWinForms/DataGridViewImageButtonColumn.cs new file mode 100644 index 00000000..31d41780 --- /dev/null +++ b/LibationWinForms/DataGridViewImageButtonColumn.cs @@ -0,0 +1,50 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace LibationWinForms +{ + public abstract class DataGridViewImageButtonColumn : DataGridViewButtonColumn + { + private DataGridViewImageButtonCell _cellTemplate; + public override DataGridViewCell CellTemplate + { + get => GetCellTemplate(); + set + { + if (value is DataGridViewImageButtonCell cellTemplate) + _cellTemplate = cellTemplate; + } + } + + protected abstract DataGridViewImageButtonCell NewCell(); + + private DataGridViewImageButtonCell GetCellTemplate() + { + if (_cellTemplate is null) + return NewCell(); + else + return _cellTemplate; + } + + public override object Clone() + { + var clone = (DataGridViewImageButtonColumn)base.Clone(); + clone._cellTemplate = _cellTemplate; + + return clone; + } + } + + public class DataGridViewImageButtonCell : DataGridViewButtonCell + { + protected void DrawImage(Graphics graphics, Bitmap image, Rectangle cellBounds) + { + var w = image.Width; + var h = image.Height; + var x = cellBounds.Left + (cellBounds.Width - w) / 2; + var y = cellBounds.Top + (cellBounds.Height - h) / 2; + + graphics.DrawImage(image, new Rectangle(x, y, w, h)); + } + } +} diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs index 273176cb..7bfafb8c 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -40,7 +40,7 @@ namespace LibationWinForms.Dialogs var orderedGridEntries = _libraryBooks .Select(lb => new RemovableGridEntry(new GridEntry(lb))) - .OrderByDescending(ge => ge.GridEntry.Purchase_Date) + .OrderByDescending(ge => ge.GridEntry.PurchaseDate) .ToList(); _removableGridEntries = orderedGridEntries.ToSortableBindingList(); @@ -173,10 +173,10 @@ namespace LibationWinForms.Dialogs NotifyPropertyChanged(); } } - public string Title => GetDisplayValue(nameof(GridEntry.Title), GridEntry.Title); - public string Authors => GetDisplayValue(nameof(GridEntry.Authors), GridEntry.Authors); - public string Misc => GetDisplayValue(nameof(GridEntry.Misc), GridEntry.Misc); - public string DatePurchased => GetDisplayValue(nameof(GridEntry.Purchase_Date), GridEntry.Purchase_Date); + public string Title => GridEntry.Title; + public string Authors => GridEntry.Authors; + public string Misc => GridEntry.Misc; + public string DatePurchased => GridEntry.PurchaseDate; private bool _remove = false; private Image _cover; @@ -185,7 +185,7 @@ namespace LibationWinForms.Dialogs { GridEntry = gridEntry; - var picDef = new FileManager.PictureDefinition(GridEntry.GetBook().PictureId, FileManager.PictureSize._80x80); + var picDef = new FileManager.PictureDefinition(GridEntry.LibraryBook.Book.PictureId, FileManager.PictureSize._80x80); (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); if (isDefault) @@ -196,19 +196,12 @@ namespace LibationWinForms.Dialogs private void PictureStorage_PictureCached(object sender, string pictureId) { - if (pictureId == GridEntry.GetBook().PictureId) + if (pictureId == GridEntry.LibraryBook.Book.PictureId) { Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; } } - - private string GetDisplayValue(string propertyName, string defaultValue) - { - if (GridEntry.TryDisplayValue(propertyName, out string value)) - return value; - return defaultValue; - } private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); diff --git a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs new file mode 100644 index 00000000..fdee638f --- /dev/null +++ b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs @@ -0,0 +1,26 @@ +using System.Drawing; +using System.Windows.Forms; + +namespace LibationWinForms +{ + public class EditTagsDataGridViewImageButtonColumn : DataGridViewImageButtonColumn + { + protected override DataGridViewImageButtonCell NewCell() + => new EditTagsDataGridViewImageButtonCell(); + } + + internal class EditTagsDataGridViewImageButtonCell : DataGridViewImageButtonCell + { + private static readonly Bitmap ButtonImage = Properties.Resources.edit_tags_25x25; + protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) + { + if (((string)value).Length == 0) + { + base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts); + DrawImage(graphics, ButtonImage, cellBounds); + } + else + base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts); + } + } +} diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 5f24c447..d20c6553 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -1,86 +1,159 @@ using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; +using System.Runtime.CompilerServices; using ApplicationServices; using DataLayer; +using Dinah.Core.Drawing; namespace LibationWinForms { - internal class GridEntry + internal class GridEntry : INotifyPropertyChanged, IObjectMemberComparable { - private LibraryBook libraryBook { get; } - private Book book => libraryBook.Book; + public const string LIBERATE_COLUMN_NAME = "Liberate"; + public const string EDIT_TAGS_COLUMN_NAME = "DisplayTags"; - public Book GetBook() => book; - public LibraryBook GetLibraryBook() => libraryBook; - - public GridEntry(LibraryBook libraryBook) => this.libraryBook = libraryBook; - - // hide from public fields from Data Source GUI with [Browsable(false)] + #region implementation properties + // hide from public fields from Data Source GUI with [Browsable(false)] + [Browsable(false)] + public string AudibleProductId => Book.AudibleProductId; [Browsable(false)] - public string AudibleProductId => book.AudibleProductId; + public string Tags => Book.UserDefinedItem.Tags; [Browsable(false)] - public string Tags => book.UserDefinedItem.Tags; + public IEnumerable TagsEnumerated => Book.UserDefinedItem.TagsEnumerated; [Browsable(false)] - public IEnumerable TagsEnumerated => book.UserDefinedItem.TagsEnumerated; - [Browsable(false)] - public string PictureId => book.PictureId; - [Browsable(false)] - public LiberatedState Liberated_Status => LibraryCommands.Liberated_Status(book); - [Browsable(false)] - public PdfState Pdf_Status => LibraryCommands.Pdf_Status(book); + public LibraryBook LibraryBook { get; } - // displayValues is what gets displayed - // the value that gets returned from the property is the cell's value - // this allows for the value to be sorted one way and displayed another - // eg: - // orig title: The Computer - // formatReplacement: The Computer - // value for sorting: Computer - private Dictionary displayValues { get; } = new Dictionary(); - public bool TryDisplayValue(string key, out string value) => displayValues.TryGetValue(key, out value); + #endregion - public Image Cover => - WindowsDesktopUtilities.WinAudibleImageServer.GetImage(book.PictureId, FileManager.PictureSize._80x80); + public event PropertyChangedEventHandler PropertyChanged; + private Book Book => LibraryBook.Book; + private Image _cover; - public string Title + public GridEntry(LibraryBook libraryBook) { - get + LibraryBook = libraryBook; + + _compareValues = CreatePropertyValueDictionary(); + + //Get cover art. If it's default, subscribe to PictureCached + var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80); + (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); + + if (isDefault) + FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; + + //Mutable property. Set the field so PropertyChanged doesn't fire. + _cover = ImageReader.ToImage(picture); + + //Immutable properties { - displayValues[nameof(Title)] = book.Title; - return getSortName(book.Title); + Title = Book.Title; + Series = Book.SeriesNames; + Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min"; + MyRating = GetStarString(Book.UserDefinedItem.Rating); + PurchaseDate = libraryBook.DateAdded.ToString("d"); + ProductRating = GetStarString(Book.Rating); + Authors = Book.AuthorNames; + Narrators = Book.NarratorNames; + Category = string.Join(" > ", Book.CategoriesNames); + Misc = GetMiscDisplay(libraryBook); + Description = GetDescriptionDisplay(Book); + } + + //DisplayTags and Liberate are live. + } + + private void PictureStorage_PictureCached(object sender, string pictureId) + { + if (pictureId == Book.PictureId) + { + Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); + FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; } } - public string Authors => book.AuthorNames; - public string Narrators => book.NarratorNames; + private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - public int Length + #region Data Source properties + public Image Cover { get { - displayValues[nameof(Length)] - = book.LengthInMinutes == 0 - ? "" - : $"{book.LengthInMinutes / 60} hr {book.LengthInMinutes % 60} min"; - - return book.LengthInMinutes; + return _cover; } - } - - public string Series - { - get + private set { - displayValues[nameof(Series)] = book.SeriesNames; - return getSortName(book.SeriesNames); + _cover = value; + NotifyPropertyChanged(); } } - private static string[] sortPrefixIgnores { get; } = new[] { "the", "a", "an" }; + public string ProductRating { get; } + public string PurchaseDate { get; } + public string MyRating { get; } + public string Series { get; } + public string Title { get; } + public string Length { get; } + public string Authors { get; } + public string Narrators { get; } + public string Category { get; } + public string Misc { get; } + public string Description { get; } + + public string DisplayTags => string.Join("\r\n", TagsEnumerated); + public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book)); + + #endregion + + #region Data Sorting + + private Dictionary> _compareValues { get; } + private static Dictionary _objectComparers; + + public object GetMemberValue(string propertyName) => _compareValues[propertyName](); + public IComparer GetComparer(Type propertyType) => _objectComparers[propertyType]; + + /// + /// Instantiate comparers for every type needed to sort columns. + /// + static GridEntry() + { + _objectComparers = new Dictionary() + { + { typeof(string), new ObjectComparer() }, + { typeof(int), new ObjectComparer() }, + { typeof(float), new ObjectComparer() }, + { typeof(DateTime), new ObjectComparer() }, + { typeof(LiberatedState), new ObjectComparer() }, + }; + } + /// + /// Create getters for all member values by name + /// + Dictionary> CreatePropertyValueDictionary() => new() + { + { nameof(Title), () => getSortName(Book.Title)}, + { nameof(Series),() => getSortName(Book.SeriesNames)}, + { nameof(Length), () => Book.LengthInMinutes}, + { nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore}, + { nameof(PurchaseDate), () => LibraryBook.DateAdded}, + { nameof(ProductRating), () => Book.Rating.FirstScore}, + { nameof(Authors), () => Authors}, + { nameof(Narrators), () => Narrators}, + { nameof(Description), () => Description}, + { nameof(Category), () => Category}, + { nameof(Misc), () => Misc}, + { nameof(DisplayTags), () => DisplayTags}, + { nameof(Liberate), () => Liberate.Item1} + }; + + private static readonly string[] sortPrefixIgnores = { "the", "a", "an" }; private static string getSortName(string unformattedName) { var sortName = unformattedName @@ -95,104 +168,115 @@ namespace LibationWinForms return sortName; } - private string descriptionCache = null; - public string Description + #endregion + + #region Static library display functions + + public static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus) { - get + string text; + Bitmap image; + + // get mouseover text { - // HtmlAgilityPack is expensive. cache results - if (descriptionCache is null) + var libState = liberatedStatus switch { - if (book.Description is null) - descriptionCache = ""; - else - { - var doc = new HtmlAgilityPack.HtmlDocument(); - doc.LoadHtml(book.Description); - var noHtml = doc.DocumentNode.InnerText; - descriptionCache - = noHtml.Length < 63 - ? noHtml - : noHtml.Substring(0, 60) + "..."; - } - } + LiberatedState.Liberated => "Liberated", + LiberatedState.PartialDownload => "File has been at least\r\npartially downloaded", + LiberatedState.NotDownloaded => "Book NOT downloaded", + _ => throw new Exception("Unexpected liberation state") + }; + + var pdfState = pdfStatus switch + { + PdfState.Downloaded => "\r\nPDF downloaded", + PdfState.NotDownloaded => "\r\nPDF NOT downloaded", + PdfState.NoPdf => "", + _ => throw new Exception("Unexpected PDF state") + }; + + text = libState + pdfState; + + if (liberatedStatus == LiberatedState.NotDownloaded || + liberatedStatus == LiberatedState.PartialDownload || + pdfStatus == PdfState.NotDownloaded) + text += "\r\nClick to complete"; - return descriptionCache; } - } - public string Category => string.Join(" > ", book.CategoriesNames); - - // star ratings retain numeric value but display star text. this is needed because just using star text doesn't sort correctly: - // - star - // - star star - // - star 1/2 - - public string Product_Rating - { - get + // get image { - displayValues[nameof(Product_Rating)] = starString(book.Rating); - return firstScore(book.Rating); + var image_lib + = liberatedStatus == LiberatedState.NotDownloaded ? "red" + : liberatedStatus == LiberatedState.PartialDownload ? "yellow" + : liberatedStatus == LiberatedState.Liberated ? "green" + : throw new Exception("Unexpected liberation state"); + var image_pdf + = pdfStatus == PdfState.NoPdf ? "" + : pdfStatus == PdfState.NotDownloaded ? "_pdf_no" + : pdfStatus == PdfState.Downloaded ? "_pdf_yes" + : throw new Exception("Unexpected PDF state"); + + image = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}"); } + + return (text, image); } - public string Purchase_Date + /// + /// This information should not change during lifetime, so call only once. + /// + private static string GetDescriptionDisplay(Book book) + { + var doc = new HtmlAgilityPack.HtmlDocument(); + doc.LoadHtml(book.Description); + var noHtml = doc.DocumentNode.InnerText; + return + noHtml.Length < 63? + noHtml : + noHtml.Substring(0, 60) + "..."; + } + + /// + /// This information should not change during lifetime, so call only once. + /// + private static string GetMiscDisplay(LibraryBook libraryBook) { - get - { - displayValues[nameof(Purchase_Date)] = libraryBook.DateAdded.ToString("d"); - return libraryBook.DateAdded.ToString("yyyy-MM-dd HH:mm:ss"); - } + // max 5 text rows + + var details = new List(); + + var locale + = string.IsNullOrWhiteSpace(libraryBook.Book.Locale) + ? "[unknown]" + : libraryBook.Book.Locale; + var acct + = string.IsNullOrWhiteSpace(libraryBook.Account) + ? "[unknown]" + : libraryBook.Account; + details.Add($"Account: {locale} - {acct}"); + + if (libraryBook.Book.HasPdf) + details.Add("Has PDF"); + if (libraryBook.Book.IsAbridged) + details.Add("Abridged"); + if (libraryBook.Book.DatePublished.HasValue) + details.Add($"Date pub'd: {libraryBook.Book.DatePublished.Value:MM/dd/yyyy}"); + // this goes last since it's most likely to have a line-break + if (!string.IsNullOrWhiteSpace(libraryBook.Book.Publisher)) + details.Add($"Pub: {libraryBook.Book.Publisher.Trim()}"); + + if (!details.Any()) + return "[details not imported]"; + + return string.Join("\r\n", details); } - public string My_Rating - { - get - { - displayValues[nameof(My_Rating)] = starString(book.UserDefinedItem.Rating); - return firstScore(book.UserDefinedItem.Rating); - } - } + private static string GetStarString(Rating rating) + => (rating?.FirstScore != null && rating?.FirstScore > 0f) + ? rating?.ToStarString() + : ""; - private string starString(Rating rating) - => (rating?.FirstScore != null && rating?.FirstScore > 0f) - ? rating?.ToStarString() - : ""; - private string firstScore(Rating rating) => rating?.FirstScore.ToString("0.0"); - - // max 5 text rows - public string Misc - { - get - { - var details = new List(); - - var locale - = string.IsNullOrWhiteSpace(book.Locale) - ? "[unknown]" - : book.Locale; - var acct - = string.IsNullOrWhiteSpace(libraryBook.Account) - ? "[unknown]" - : libraryBook.Account; - details.Add($"Account: {locale} - {acct}"); - - if (book.HasPdf) - details.Add("Has PDF"); - if (book.IsAbridged) - details.Add("Abridged"); - if (book.DatePublished.HasValue) - details.Add($"Date pub'd: {book.DatePublished.Value:MM/dd/yyyy}"); - // this goes last since it's most likely to have a line-break - if (!string.IsNullOrWhiteSpace(book.Publisher)) - details.Add($"Pub: {book.Publisher.Trim()}"); - - if (!details.Any()) - return "[details not imported]"; - - return string.Join("\r\n", details); - } - } - } + #endregion + } } diff --git a/LibationWinForms/IObjectMemberComparable.cs b/LibationWinForms/IObjectMemberComparable.cs new file mode 100644 index 00000000..8bc5b762 --- /dev/null +++ b/LibationWinForms/IObjectMemberComparable.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections; + +namespace LibationWinForms +{ + interface IObjectMemberComparable + { + IComparer GetComparer(Type propertyType); + object GetMemberValue(string valueName); + } +} diff --git a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs new file mode 100644 index 00000000..63d64257 --- /dev/null +++ b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs @@ -0,0 +1,30 @@ +using ApplicationServices; +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace LibationWinForms +{ + public class LiberateDataGridViewImageButtonColumn : DataGridViewImageButtonColumn + { + protected override DataGridViewImageButtonCell NewCell() + => new LiberateDataGridViewImageButtonCell(); + } + + internal class LiberateDataGridViewImageButtonCell : DataGridViewImageButtonCell + { + protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) + { + base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts); + + if (value is (LiberatedState liberatedState, PdfState pdfState)) + { + (string mouseoverText, Bitmap buttonImage) = GridEntry.GetLiberateDisplay(liberatedState, pdfState); + + DrawImage(graphics, buttonImage, cellBounds); + + ToolTipText = mouseoverText; + } + } + } +} diff --git a/LibationWinForms/ObjectComparer[T].cs b/LibationWinForms/ObjectComparer[T].cs new file mode 100644 index 00000000..2bc69d8d --- /dev/null +++ b/LibationWinForms/ObjectComparer[T].cs @@ -0,0 +1,10 @@ +using System; +using System.Collections; + +namespace LibationWinForms +{ + class ObjectComparer : IComparer where T : IComparable + { + public int Compare(object x, object y) => ((T)x).CompareTo((T)y); + } +} diff --git a/LibationWinForms/ObjectMemberComparer[T].cs b/LibationWinForms/ObjectMemberComparer[T].cs new file mode 100644 index 00000000..00f29510 --- /dev/null +++ b/LibationWinForms/ObjectMemberComparer[T].cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.ComponentModel; + +namespace LibationWinForms +{ + class ObjectMemberComparer : IComparer where T : IObjectMemberComparable + { + public ListSortDirection Direction { get; set; } = ListSortDirection.Ascending; + public string PropertyName { get; set; } + + public int Compare(T x, T y) + { + var val1 = x.GetMemberValue(PropertyName); + var val2 = y.GetMemberValue(PropertyName); + + return DirMult * x.GetComparer(val1.GetType()).Compare(val1, val2); + } + + private int DirMult => Direction == ListSortDirection.Descending ? -1 : 1; + } +} diff --git a/LibationWinForms/ProductsGrid.Designer.cs b/LibationWinForms/ProductsGrid.Designer.cs index a81c8abf..0a74a123 100644 --- a/LibationWinForms/ProductsGrid.Designer.cs +++ b/LibationWinForms/ProductsGrid.Designer.cs @@ -28,146 +28,198 @@ /// 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(LibationWinForms.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); + 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.gridEntryDataGridView = new System.Windows.Forms.DataGridView(); + this.dataGridViewImageButtonBoxColumn1 = new LibationWinForms.LiberateDataGridViewImageButtonColumn(); + 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.ProductRating = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.PurchaseDate = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.MyRating = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewTextBoxColumn11 = new System.Windows.Forms.DataGridViewTextBoxColumn(); + this.dataGridViewImageButtonBoxColumn2 = new LibationWinForms.EditTagsDataGridViewImageButtonColumn(); + ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).BeginInit(); + this.SuspendLayout(); + // + // gridEntryBindingSource + // + this.gridEntryBindingSource.DataSource = typeof(LibationWinForms.GridEntry); + // + // gridEntryDataGridView + // + this.gridEntryDataGridView.AllowUserToAddRows = false; + this.gridEntryDataGridView.AllowUserToDeleteRows = false; + this.gridEntryDataGridView.AutoGenerateColumns = false; + this.gridEntryDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.gridEntryDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { + this.dataGridViewImageButtonBoxColumn1, + this.dataGridViewImageColumn1, + this.dataGridViewTextBoxColumn1, + this.dataGridViewTextBoxColumn2, + this.dataGridViewTextBoxColumn3, + this.dataGridViewTextBoxColumn4, + this.dataGridViewTextBoxColumn5, + this.dataGridViewTextBoxColumn6, + this.dataGridViewTextBoxColumn7, + this.ProductRating, + this.PurchaseDate, + this.MyRating, + this.dataGridViewTextBoxColumn11, + this.dataGridViewImageButtonBoxColumn2}); + this.gridEntryDataGridView.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.gridEntryDataGridView.DefaultCellStyle = dataGridViewCellStyle1; + this.gridEntryDataGridView.Dock = System.Windows.Forms.DockStyle.Fill; + this.gridEntryDataGridView.Location = new System.Drawing.Point(0, 0); + this.gridEntryDataGridView.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.gridEntryDataGridView.Name = "gridEntryDataGridView"; + this.gridEntryDataGridView.ReadOnly = true; + this.gridEntryDataGridView.RowHeadersVisible = false; + this.gridEntryDataGridView.RowTemplate.Height = 82; + this.gridEntryDataGridView.Size = new System.Drawing.Size(1503, 380); + this.gridEntryDataGridView.TabIndex = 0; + // + // dataGridViewImageButtonBoxColumn1 + // + this.dataGridViewImageButtonBoxColumn1.DataPropertyName = "Liberate"; + this.dataGridViewImageButtonBoxColumn1.HeaderText = "Liberate"; + this.dataGridViewImageButtonBoxColumn1.Name = "dataGridViewImageButtonBoxColumn1"; + this.dataGridViewImageButtonBoxColumn1.ReadOnly = true; + this.dataGridViewImageButtonBoxColumn1.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewImageButtonBoxColumn1.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; + this.dataGridViewImageButtonBoxColumn1.Width = 70; + // + // dataGridViewImageColumn1 + // + this.dataGridViewImageColumn1.DataPropertyName = "Cover"; + this.dataGridViewImageColumn1.HeaderText = "Cover"; + this.dataGridViewImageColumn1.Name = "dataGridViewImageColumn1"; + this.dataGridViewImageColumn1.ReadOnly = true; + this.dataGridViewImageColumn1.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewImageColumn1.ToolTipText = "Cover Art"; + this.dataGridViewImageColumn1.Width = 80; + // + // dataGridViewTextBoxColumn1 + // + this.dataGridViewTextBoxColumn1.DataPropertyName = "Title"; + this.dataGridViewTextBoxColumn1.HeaderText = "Title"; + this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1"; + this.dataGridViewTextBoxColumn1.ReadOnly = true; + this.dataGridViewTextBoxColumn1.ToolTipText = "Book Title"; + this.dataGridViewTextBoxColumn1.Width = 200; + // + // dataGridViewTextBoxColumn2 + // + this.dataGridViewTextBoxColumn2.DataPropertyName = "Authors"; + this.dataGridViewTextBoxColumn2.HeaderText = "Authors"; + this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2"; + this.dataGridViewTextBoxColumn2.ReadOnly = true; + this.dataGridViewTextBoxColumn2.ToolTipText = "Book Author(s)"; + // + // dataGridViewTextBoxColumn3 + // + this.dataGridViewTextBoxColumn3.DataPropertyName = "Narrators"; + this.dataGridViewTextBoxColumn3.HeaderText = "Narrators"; + this.dataGridViewTextBoxColumn3.Name = "dataGridViewTextBoxColumn3"; + this.dataGridViewTextBoxColumn3.ReadOnly = true; + this.dataGridViewTextBoxColumn3.ToolTipText = "Audiobook Narrator(s)"; + // + // dataGridViewTextBoxColumn4 + // + this.dataGridViewTextBoxColumn4.DataPropertyName = "Length"; + this.dataGridViewTextBoxColumn4.HeaderText = "Length"; + this.dataGridViewTextBoxColumn4.Name = "dataGridViewTextBoxColumn4"; + this.dataGridViewTextBoxColumn4.ReadOnly = true; + this.dataGridViewTextBoxColumn4.ToolTipText = "Recording Length"; + // + // dataGridViewTextBoxColumn5 + // + this.dataGridViewTextBoxColumn5.DataPropertyName = "Series"; + this.dataGridViewTextBoxColumn5.HeaderText = "Series"; + this.dataGridViewTextBoxColumn5.Name = "dataGridViewTextBoxColumn5"; + this.dataGridViewTextBoxColumn5.ReadOnly = true; + this.dataGridViewTextBoxColumn5.ToolTipText = "Book Series"; + // + // 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; + // + // ProductRating + // + this.ProductRating.DataPropertyName = "ProductRating"; + this.ProductRating.HeaderText = "Product Rating"; + this.ProductRating.Name = "ProductRating"; + this.ProductRating.ReadOnly = true; + this.ProductRating.Width = 108; + // + // PurchaseDate + // + this.PurchaseDate.DataPropertyName = "PurchaseDate"; + this.PurchaseDate.HeaderText = "Purchase Date"; + this.PurchaseDate.Name = "PurchaseDate"; + this.PurchaseDate.ReadOnly = true; + // + // MyRating + // + this.MyRating.DataPropertyName = "MyRating"; + this.MyRating.HeaderText = "My Rating"; + this.MyRating.Name = "MyRating"; + this.MyRating.ReadOnly = true; + this.MyRating.Width = 108; + // + // dataGridViewTextBoxColumn11 + // + this.dataGridViewTextBoxColumn11.DataPropertyName = "Misc"; + this.dataGridViewTextBoxColumn11.HeaderText = "Misc"; + this.dataGridViewTextBoxColumn11.Name = "dataGridViewTextBoxColumn11"; + this.dataGridViewTextBoxColumn11.ReadOnly = true; + this.dataGridViewTextBoxColumn11.Width = 135; + // + // dataGridViewImageButtonBoxColumn2 + // + this.dataGridViewImageButtonBoxColumn2.DataPropertyName = "DisplayTags"; + this.dataGridViewImageButtonBoxColumn2.HeaderText = "Edit Tags"; + this.dataGridViewImageButtonBoxColumn2.Name = "dataGridViewImageButtonBoxColumn2"; + this.dataGridViewImageButtonBoxColumn2.ReadOnly = true; + this.dataGridViewImageButtonBoxColumn2.Resizable = System.Windows.Forms.DataGridViewTriState.False; + this.dataGridViewImageButtonBoxColumn2.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.Automatic; + // + // ProductsGrid + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.gridEntryDataGridView); + this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.Name = "ProductsGrid"; + this.Size = new System.Drawing.Size(1503, 380); + ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit(); + this.ResumeLayout(false); } @@ -175,17 +227,22 @@ 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; - } + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn8; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn9; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn10; + private LiberateDataGridViewImageButtonColumn dataGridViewImageButtonBoxColumn1; + 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 ProductRating; + private System.Windows.Forms.DataGridViewTextBoxColumn PurchaseDate; + private System.Windows.Forms.DataGridViewTextBoxColumn MyRating; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn11; + private EditTagsDataGridViewImageButtonColumn dataGridViewImageButtonBoxColumn2; + } } diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 5f811bd1..6cea7acc 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -1,12 +1,11 @@ using System; using System.Drawing; using System.Linq; +using System.Threading.Tasks; using System.Windows.Forms; using ApplicationServices; using DataLayer; using Dinah.Core; -using Dinah.Core.Collections.Generic; -using Dinah.Core.DataBinding; using Dinah.Core.Windows.Forms; using LibationWinForms.Dialogs; @@ -30,158 +29,54 @@ namespace LibationWinForms public event EventHandler VisibleCountChanged; public event EventHandler BackupCountsChanged; - private const string EDIT_TAGS = "Edit Tags"; - private const string LIBERATE = "Liberate"; - // alias - private DataGridView dataGridView => gridEntryDataGridView; + private DataGridView _dataGridView => gridEntryDataGridView; public ProductsGrid() { InitializeComponent(); - formatDataGridView(); - addLiberateButtons(); - addEditTagsButtons(); - formatColumns(); - - manageLiveImageUpdateSubscriptions(); - - enableDoubleBuffering(); - } - - private void enableDoubleBuffering() - { - var propertyInfo = dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - - //var before = (bool)propertyInfo.GetValue(dataGridView); - propertyInfo.SetValue(dataGridView, true, null); - //var after = (bool)propertyInfo.GetValue(dataGridView); - } - - private void formatDataGridView() - { - dataGridView.Dock = DockStyle.Fill; - dataGridView.AllowUserToAddRows = false; - dataGridView.AllowUserToDeleteRows = false; - dataGridView.AutoGenerateColumns = false; - dataGridView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; - dataGridView.DefaultCellStyle.WrapMode = DataGridViewTriState.True; - dataGridView.ReadOnly = true; - dataGridView.RowHeadersVisible = false; - - // adjust height for 80x80 pictures. - // this must be done before databinding. or can alter later by iterating through rows - dataGridView.RowTemplate.Height = 82; - dataGridView.CellFormatting += replaceFormatted; - dataGridView.CellFormatting += hiddenFormatting; // sorting breaks filters. must reapply filters after sorting - dataGridView.Sorted += (_, __) => filter(); - } + _dataGridView.Sorted += (_, __) => Filter(); + _dataGridView.CellFormatting += HiddenFormatting; + _dataGridView.CellContentClick += DataGridView_CellContentClick; - #region format text cells. ie: not buttons - private void replaceFormatted(object sender, DataGridViewCellFormattingEventArgs e) + EnableDoubleBuffering(); + } + + private void EnableDoubleBuffering() + { + var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + + //var before = (bool)propertyInfo.GetValue(dataGridView); + propertyInfo.SetValue(_dataGridView, true, null); + //var after = (bool)propertyInfo.GetValue(dataGridView); + } + + #region Button controls + + private async void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) { - var col = ((DataGridView)sender).Columns[e.ColumnIndex]; - if (col is DataGridViewTextBoxColumn textCol && getGridEntry(e.RowIndex).TryDisplayValue(textCol.Name, out string value)) + // handle grid button click: https://stackoverflow.com/a/13687844 + if (e.RowIndex < 0 || _dataGridView.Columns[e.ColumnIndex] is not DataGridViewButtonColumn) + return; + + var liveGridEntry = getGridEntry(e.RowIndex); + + switch (_dataGridView.Columns[e.ColumnIndex].DataPropertyName) { - // DO NOT DO THIS: getCell(e).Value = value; - // it's the wrong way and will infinitely call CellFormatting on each assign - - // this is the correct way. will actually set FormattedValue (and EditedFormattedValue) while leaving Value as-is for sorting - e.Value = value; - - getCell(e).ToolTipText = value; + case GridEntry.LIBERATE_COLUMN_NAME: + await Liberate_Click(liveGridEntry); + break; + case GridEntry.EDIT_TAGS_COLUMN_NAME: + EditTags_Click(liveGridEntry); + break; } } - private void hiddenFormatting(object sender, DataGridViewCellFormattingEventArgs e) + private async Task Liberate_Click(GridEntry liveGridEntry) { - var dgv = (DataGridView)sender; - // no action needed for buttons - if (e.RowIndex < 0 || dgv.Columns[e.ColumnIndex] is DataGridViewButtonColumn) - return; - - var isHidden = getGridEntry(e.RowIndex).TagsEnumerated.Contains("hidden"); - - getCell(e).Style - = isHidden - ? new DataGridViewCellStyle { ForeColor = Color.LightGray } - : dgv.DefaultCellStyle; - } - #endregion - - #region liberation buttons - private void addLiberateButtons() - { - dataGridView.Columns.Insert(0, new DataGridViewButtonColumn { HeaderText = LIBERATE }); - - dataGridView.CellPainting += liberate_Paint; - dataGridView.CellContentClick += liberate_Click; - } - - private void liberate_Paint(object sender, DataGridViewCellPaintingEventArgs e) - { - if (!isColumnValid(e, LIBERATE)) - return; - - var cell = getCell(e); - var gridEntry = getGridEntry(e.RowIndex); - var liberatedStatus = gridEntry.Liberated_Status; - var pdfStatus = gridEntry.Pdf_Status; - - // mouseover text - { - var libState = liberatedStatus switch - { - LiberatedState.Liberated => "Liberated", - LiberatedState.PartialDownload => "File has been at least\r\npartially downloaded", - LiberatedState.NotDownloaded => "Book NOT downloaded", - _ => throw new Exception("Unexpected liberation state") - }; - - var pdfState = pdfStatus switch - { - PdfState.Downloaded => "\r\nPDF downloaded", - PdfState.NotDownloaded => "\r\nPDF NOT downloaded", - PdfState.NoPdf => "", - _ => throw new Exception("Unexpected PDF state") - }; - - var text = libState + pdfState; - - if (liberatedStatus == LiberatedState.NotDownloaded || - liberatedStatus == LiberatedState.PartialDownload || - pdfStatus == PdfState.NotDownloaded) - text += "\r\nClick to complete"; - - //DEBUG//cell.Value = text; - cell.ToolTipText = text; - } - - // draw img - { - var image_lib - = liberatedStatus == LiberatedState.NotDownloaded ? "red" - : liberatedStatus == LiberatedState.PartialDownload ? "yellow" - : liberatedStatus == LiberatedState.Liberated ? "green" - : throw new Exception("Unexpected liberation state"); - var image_pdf - = pdfStatus == PdfState.NoPdf ? "" - : pdfStatus == PdfState.NotDownloaded ? "_pdf_no" - : pdfStatus == PdfState.Downloaded ? "_pdf_yes" - : throw new Exception("Unexpected PDF state"); - var image = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}"); - drawImage(e, image); - } - } - - private async void liberate_Click(object sender, DataGridViewCellEventArgs e) - { - if (!isColumnValid(e, LIBERATE)) - return; - - var libraryBook = getGridEntry(e.RowIndex).GetLibraryBook(); + var libraryBook = liveGridEntry.LibraryBook; // liberated: open explorer to file if (TransitionalFileLocator.Audio_Exists(libraryBook.Book)) @@ -195,146 +90,24 @@ namespace LibationWinForms // else: liberate await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId)); } - #endregion - public void RefreshRow(string productId) + private void EditTags_Click(GridEntry liveGridEntry) { - var rowId = getRowId((ge) => ge.AudibleProductId == productId); - - // update cells incl Liberate button text - dataGridView.InvalidateRow(rowId); - - // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change - filter(); - - BackupCountsChanged?.Invoke(this, EventArgs.Empty); - } - - #region tag buttons - private void addEditTagsButtons() - { - dataGridView.Columns.Add(new DataGridViewButtonColumn { HeaderText = EDIT_TAGS }); - - dataGridView.CellPainting += editTags_Paint; - dataGridView.CellContentClick += editTags_Click; - } - - private void editTags_Paint(object sender, DataGridViewCellPaintingEventArgs e) - { - // DataGridView Image for Button Column: https://stackoverflow.com/a/36253883 - - if (!isColumnValid(e, EDIT_TAGS)) - return; - - var cell = getCell(e); - var gridEntry = getGridEntry(e.RowIndex); - - var displayTags = gridEntry.TagsEnumerated.ToList(); - - if (displayTags.Any()) - cell.Value = string.Join("\r\n", displayTags); - else - { - // if removing all tags: clear previous tag text - cell.Value = ""; - drawImage(e, Properties.Resources.edit_tags_25x25); - } - } - - private void editTags_Click(object sender, DataGridViewCellEventArgs e) - { - // handle grid button click: https://stackoverflow.com/a/13687844 - - var dgv = (DataGridView)sender; - - if (!isColumnValid(e, EDIT_TAGS)) - return; - - var liveGridEntry = getGridEntry(e.RowIndex); - - // EditTagsDialog should display better-formatted title - liveGridEntry.TryDisplayValue(nameof(liveGridEntry.Title), out string value); - - var bookDetailsForm = new BookDetailsDialog(value, liveGridEntry.Tags); + var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.Tags); if (bookDetailsForm.ShowDialog() != DialogResult.OK) return; - var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.GetBook(), bookDetailsForm.NewTags); - if (qtyChanges == 0) + var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags); + if (qtyChanges == 0) return; - // force a re-draw, and re-apply filters - - // needed to update text colors - dgv.InvalidateRow(e.RowIndex); - - filter(); + //Re-apply filters + Filter(); } + #endregion - private static void drawImage(DataGridViewCellPaintingEventArgs e, Bitmap image) - { - e.Paint(e.CellBounds, DataGridViewPaintParts.All); - - var w = image.Width; - var h = image.Height; - var x = e.CellBounds.Left + (e.CellBounds.Width - w) / 2; - var y = e.CellBounds.Top + (e.CellBounds.Height - h) / 2; - - e.Graphics.DrawImage(image, new Rectangle(x, y, w, h)); - e.Handled = true; - } - - private bool isColumnValid(DataGridViewCellEventArgs e, string colName) => isColumnValid(e.RowIndex, e.ColumnIndex, colName); - private bool isColumnValid(DataGridViewCellPaintingEventArgs e, string colName) => isColumnValid(e.RowIndex, e.ColumnIndex, colName); - private bool isColumnValid(int rowIndex, int colIndex, string colName) - { - var col = dataGridView.Columns[colIndex]; - return rowIndex >= 0 && col.Name == colName && col is DataGridViewButtonColumn; - } - - private void formatColumns() - { - for (var i = dataGridView.ColumnCount - 1; i >= 0; i--) - { - var col = dataGridView.Columns[i]; - - // initial HeaderText is the lookup name from GridEntry class. any formatting below won't change this - col.Name = col.HeaderText; - - if (!(col is DataGridViewImageColumn || col is DataGridViewButtonColumn)) - col.SortMode = DataGridViewColumnSortMode.Automatic; - - col.HeaderText = col.HeaderText.Replace("_", " "); - - col.Width = col.Name switch - { - LIBERATE => 70, - nameof(GridEntry.Cover) => 80, - nameof(GridEntry.Title) => col.Width * 2, - nameof(GridEntry.Misc) => (int)(col.Width * 1.35), - var n when n.In(nameof(GridEntry.My_Rating), nameof(GridEntry.Product_Rating)) => col.Width + 8, - _ => col.Width - }; - } - } - - #region live update newly downloaded and cached images - private void manageLiveImageUpdateSubscriptions() - { - FileManager.PictureStorage.PictureCached += crossThreadImageUpdate; - Disposed += (_, __) => FileManager.PictureStorage.PictureCached -= crossThreadImageUpdate; - } - - private void crossThreadImageUpdate(object _, string pictureId) - => dataGridView.UIThread(() => updateRowImage(pictureId)); - private void updateRowImage(string pictureId) - { - var rowId = getRowId((ge) => ge.PictureId == pictureId); - if (rowId > -1) - dataGridView.InvalidateRow(rowId); - } - #endregion + #region UI display functions private bool hasBeenDisplayed = false; public void Display() @@ -352,15 +125,15 @@ namespace LibationWinForms // if no data. hide all columns. return if (!lib.Any()) { - for (var i = dataGridView.ColumnCount - 1; i >= 0; i--) - dataGridView.Columns.RemoveAt(i); + for (var i = _dataGridView.ColumnCount - 1; i >= 0; i--) + _dataGridView.Columns.RemoveAt(i); return; } var orderedGridEntries = lib .Select(lb => new GridEntry(lb)).ToList() // default load order - .OrderByDescending(ge => ge.Purchase_Date) + .OrderByDescending(ge => ge.PurchaseDate) //// more advanced example: sort by author, then series, then title //.OrderBy(ge => ge.Authors) // .ThenBy(ge => ge.Series) @@ -370,49 +143,83 @@ namespace LibationWinForms // // BIND // - gridEntryBindingSource.DataSource = orderedGridEntries.ToSortableBindingList(); + gridEntryBindingSource.DataSource = new SortableBindingList2(orderedGridEntries); // // FILTER // - filter(); + Filter(); BackupCountsChanged?.Invoke(this, EventArgs.Empty); } + public void RefreshRow(string productId) + { + var rowId = getRowIndex((ge) => ge.AudibleProductId == productId); + + // update cells incl Liberate button text + _dataGridView.InvalidateRow(rowId); + + // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change + Filter(); + + BackupCountsChanged?.Invoke(this, EventArgs.Empty); + } + + #region format text cells. ie: not buttons + + private void HiddenFormatting(object sender, DataGridViewCellFormattingEventArgs e) + { + var dgv = (DataGridView)sender; + // no action needed for buttons + if (e.RowIndex < 0 || dgv.Columns[e.ColumnIndex] is DataGridViewButtonColumn) + return; + + var isHidden = getGridEntry(e.RowIndex).TagsEnumerated.Contains("hidden"); + + getCell(e).Style + = isHidden + ? new DataGridViewCellStyle { ForeColor = Color.LightGray } + : dgv.DefaultCellStyle; + } + + #endregion + + #endregion + #region filter + string _filterSearchString; - private void filter() => Filter(_filterSearchString); + private void Filter() => Filter(_filterSearchString); public void Filter(string searchString) { _filterSearchString = searchString; - if (dataGridView.Rows.Count == 0) + if (_dataGridView.Rows.Count == 0) return; var searchResults = SearchEngineCommands.Search(searchString); var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); // https://stackoverflow.com/a/18942430 - var currencyManager = (CurrencyManager)BindingContext[dataGridView.DataSource]; + var currencyManager = (CurrencyManager)BindingContext[_dataGridView.DataSource]; currencyManager.SuspendBinding(); { - for (var r = dataGridView.RowCount - 1; r >= 0; r--) - dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId); + for (var r = _dataGridView.RowCount - 1; r >= 0; r--) + _dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId); } currencyManager.ResumeBinding(); - VisibleCountChanged?.Invoke(this, dataGridView.AsEnumerable().Count(r => r.Visible)); + VisibleCountChanged?.Invoke(this, _dataGridView.AsEnumerable().Count(r => r.Visible)); } + #endregion - private int getRowId(Func func) => dataGridView.GetRowIdOfBoundItem(func); + #region DataGridView Macros - private GridEntry getGridEntry(int rowIndex) => dataGridView.GetBoundItem(rowIndex); + private int getRowIndex(Func func) => _dataGridView.GetRowIdOfBoundItem(func); + private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); + private DataGridViewCell getCell(DataGridViewCellFormattingEventArgs e) => _dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex]; - private DataGridViewCell getCell(DataGridViewCellFormattingEventArgs e) => getCell(e.RowIndex, e.ColumnIndex); - - private DataGridViewCell getCell(DataGridViewCellPaintingEventArgs e) => getCell(e.RowIndex, e.ColumnIndex); - - private DataGridViewCell getCell(int rowIndex, int columnIndex) => dataGridView.Rows[rowIndex].Cells[columnIndex]; + #endregion } } diff --git a/LibationWinForms/ProductsGrid.resx b/LibationWinForms/ProductsGrid.resx index d1166daf..688d741a 100644 --- a/LibationWinForms/ProductsGrid.resx +++ b/LibationWinForms/ProductsGrid.resx @@ -1,64 +1,4 @@ - - - + @@ -120,4 +60,19 @@ 17, 17 + + True + + + True + + + True + + + True + + + True + \ No newline at end of file diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs new file mode 100644 index 00000000..1a0e1d97 --- /dev/null +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibationWinForms +{ + class SortableBindingList2 : BindingList where T : IObjectMemberComparable + { + private ObjectMemberComparer Comparer = new(); + + private bool isSorted; + private ListSortDirection listSortDirection; + private PropertyDescriptor propertyDescriptor; + + public SortableBindingList2() : base(new List()) { } + + public SortableBindingList2(IEnumerable enumeration) : base(new List(enumeration)) { } + + protected override bool SupportsSortingCore => true; + + protected override bool IsSortedCore => isSorted; + + protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; + + protected override ListSortDirection SortDirectionCore => listSortDirection; + + protected override bool SupportsSearchingCore => true; + + protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) + { + List itemsList = (List)Items; + + Comparer.PropertyName = property.Name; + Comparer.Direction = direction; + + itemsList.Sort(Comparer); + + propertyDescriptor = property; + listSortDirection = direction; + isSorted = true; + + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected override void RemoveSortCore() + { + isSorted = false; + propertyDescriptor = base.SortPropertyCore; + listSortDirection = base.SortDirectionCore; + + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + + protected override int FindCore(PropertyDescriptor property, object key) + { + int count = Count; + for (int i = 0; i < count; ++i) + { + T element = this[i]; + if (property.GetValue(element).Equals(key)) + { + return i; + } + } + + return -1; + } + } +} From 5f8ca9a0b528f3bf0d2d5b450beaf4ae67add550 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 19:07:00 -0600 Subject: [PATCH 06/64] Changed sort method. --- LibationWinForms/ProductsGrid.Designer.cs | 4 ++-- LibationWinForms/SortableBindingList2[T].cs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/LibationWinForms/ProductsGrid.Designer.cs b/LibationWinForms/ProductsGrid.Designer.cs index 0a74a123..ce28cfdd 100644 --- a/LibationWinForms/ProductsGrid.Designer.cs +++ b/LibationWinForms/ProductsGrid.Designer.cs @@ -91,7 +91,7 @@ this.gridEntryDataGridView.ReadOnly = true; this.gridEntryDataGridView.RowHeadersVisible = false; this.gridEntryDataGridView.RowTemplate.Height = 82; - this.gridEntryDataGridView.Size = new System.Drawing.Size(1503, 380); + this.gridEntryDataGridView.Size = new System.Drawing.Size(1505, 380); this.gridEntryDataGridView.TabIndex = 0; // // dataGridViewImageButtonBoxColumn1 @@ -216,7 +216,7 @@ this.Controls.Add(this.gridEntryDataGridView); this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); this.Name = "ProductsGrid"; - this.Size = new System.Drawing.Size(1503, 380); + this.Size = new System.Drawing.Size(1505, 380); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryDataGridView)).EndInit(); this.ResumeLayout(false); diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs index 1a0e1d97..0a2bf465 100644 --- a/LibationWinForms/SortableBindingList2[T].cs +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -36,7 +36,11 @@ namespace LibationWinForms Comparer.PropertyName = property.Name; Comparer.Direction = direction; - itemsList.Sort(Comparer); + //Array.Sort and Liat.Sort are unstable sorts. OrderBy is stable. + var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToArray(); + + itemsList.Clear(); + itemsList.AddRange(sortedItems); propertyDescriptor = property; listSortDirection = direction; From ab82e7c99cdbba3c60edebe61fd4c1c177a8027c Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 19:15:41 -0600 Subject: [PATCH 07/64] Added comment. --- LibationWinForms/ProductsGrid.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 6cea7acc..f06b8c1a 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -208,6 +208,8 @@ namespace LibationWinForms for (var r = _dataGridView.RowCount - 1; r >= 0; r--) _dataGridView.Rows[r].Visible = productIds.Contains(getGridEntry(r).AudibleProductId); } + + //Causes repainting of the DataGridView currencyManager.ResumeBinding(); VisibleCountChanged?.Invoke(this, _dataGridView.AsEnumerable().Count(r => r.Visible)); } From 2ef746a94c12b156345ad80f1144c7dae21a7866 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 20:07:15 -0600 Subject: [PATCH 08/64] Add debug constants and don't check updates in debug. Refactored cell formatting Made GridEntry thread safe Moved PictureStorage set defaults into constructor. --- LibationLauncher/LibationLauncher.csproj | 6 +- LibationLauncher/Program.cs | 16 ++++-- LibationWinForms/Dialogs/RemoveBooksDialog.cs | 56 +++---------------- .../EditTagsDataGridViewImageButtonColumn.cs | 10 +++- LibationWinForms/Form1.cs | 7 --- LibationWinForms/GridEntry.cs | 18 ++++-- LibationWinForms/ProductsGrid.cs | 30 ++-------- 7 files changed, 49 insertions(+), 94 deletions(-) diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 5af2cd25..33159e83 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,11 @@ win-x64 - 5.4.9.79 + 5.4.9.120 + + + + TRACE;DEBUG diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs index 2fa3c87a..ab4c6e1c 100644 --- a/LibationLauncher/Program.cs +++ b/LibationLauncher/Program.cs @@ -59,7 +59,10 @@ namespace LibationLauncher ensureSerilogConfig(config); configureLogging(config); logStartupState(config); + +#if !DEBUG checkForUpdate(config); +#endif Application.Run(new Form1()); } @@ -145,7 +148,7 @@ namespace LibationLauncher CancelInstallation(); } - #region migrate to v5.0.0 re-register device if device info not in settings +#region migrate to v5.0.0 re-register device if device info not in settings private static void migrate_to_v5_0_0(Configuration config) { if (!config.Exists(nameof(config.AllowLibationFixup))) @@ -187,9 +190,9 @@ namespace LibationLauncher } } } - #endregion +#endregion - #region migrate to v5.2.0 +#region migrate to v5.2.0 // get rid of meta-directories, combine DownloadsInProgressEnum and DecryptInProgressEnum => InProgress private static void migrate_to_v5_2_0__pre_config() { @@ -231,9 +234,9 @@ namespace LibationLauncher if (!config.Exists(nameof(config.DecryptToLossy))) config.DecryptToLossy = false; } - #endregion +#endregion - #region migrate to v5.4.1 see comment +#region migrate to v5.4.1 see comment // this 'migration' is a bit different. it intentionally runs each time Libation is started. its job will be fulfilled when I eventually // implement the portion which removes FilePaths.json, at which time this method will be a proper migration // @@ -297,7 +300,7 @@ namespace LibationLauncher debugStopwatch.Stop(); var debugTotal = debugStopwatch.Elapsed; } - #endregion +#endregion private static void ensureSerilogConfig(Configuration config) { @@ -418,6 +421,7 @@ namespace LibationLauncher if (latest is null) return; + var latestVersionString = latest.TagName.Trim('v'); if (!Version.TryParse(latestVersionString, out var latestRelease)) return; diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs index 7bfafb8c..04e7af16 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -39,8 +39,8 @@ namespace LibationWinForms.Dialogs dataGridView1.BindingContextChanged += (s, e) => UpdateSelection(); var orderedGridEntries = _libraryBooks - .Select(lb => new RemovableGridEntry(new GridEntry(lb))) - .OrderByDescending(ge => ge.GridEntry.PurchaseDate) + .Select(lb => new RemovableGridEntry(lb)) + .OrderByDescending(ge => ge.PurchaseDate) .ToList(); _removableGridEntries = orderedGridEntries.ToSortableBindingList(); @@ -65,7 +65,7 @@ namespace LibationWinForms.Dialogs { var rmovedBooks = await LibraryCommands.FindInactiveBooks((account) => new WinformResponder(account), _libraryBooks, _accounts); - var removable = _removableGridEntries.Where(rge => rmovedBooks.Count(rb => rb.Book.AudibleProductId == rge.GridEntry.AudibleProductId) == 1); + var removable = _removableGridEntries.Where(rge => rmovedBooks.Count(rb => rb.Book.AudibleProductId == rge.AudibleProductId) == 1); if (removable.Count() == 0) return; @@ -110,7 +110,7 @@ namespace LibationWinForms.Dialogs var libBooks = context.GetLibrary_Flat_NoTracking(); - var removeLibraryBooks = libBooks.Where(lb => selected.Count(rge => rge.GridEntry.AudibleProductId == lb.Book.AudibleProductId) == 1).ToArray(); + var removeLibraryBooks = libBooks.Where(lb => selected.Count(rge => rge.AudibleProductId == lb.Book.AudibleProductId) == 1).ToArray(); context.Library.RemoveRange(removeLibraryBooks); context.SaveChanges(); BooksRemoved = true; @@ -141,11 +141,8 @@ namespace LibationWinForms.Dialogs } - internal class RemovableGridEntry : INotifyPropertyChanged + internal class RemovableGridEntry : GridEntry { - public event PropertyChangedEventHandler PropertyChanged; - public GridEntry GridEntry { get; } - public bool Remove { get @@ -160,51 +157,12 @@ namespace LibationWinForms.Dialogs NotifyPropertyChanged(); } } - } - public Image Cover - { - get - { - return _cover; - } - set - { - _cover = value; - NotifyPropertyChanged(); - } - } - public string Title => GridEntry.Title; - public string Authors => GridEntry.Authors; - public string Misc => GridEntry.Misc; - public string DatePurchased => GridEntry.PurchaseDate; + } private bool _remove = false; - private Image _cover; - public RemovableGridEntry(GridEntry gridEntry) + public RemovableGridEntry(LibraryBook libraryBook) :base(libraryBook) { - GridEntry = gridEntry; - - var picDef = new FileManager.PictureDefinition(GridEntry.LibraryBook.Book.PictureId, FileManager.PictureSize._80x80); - (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); - - if (isDefault) - FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; - - _cover = ImageReader.ToImage(picture); } - - private void PictureStorage_PictureCached(object sender, string pictureId) - { - if (pictureId == GridEntry.LibraryBook.Book.PictureId) - { - Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); - FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; - } - } - - private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } } diff --git a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs index fdee638f..58f39545 100644 --- a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs +++ b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs @@ -12,15 +12,23 @@ namespace LibationWinForms internal class EditTagsDataGridViewImageButtonCell : DataGridViewImageButtonCell { private static readonly Bitmap ButtonImage = Properties.Resources.edit_tags_25x25; + private static readonly Color HiddenForeColor = Color.LightGray; + protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) { - if (((string)value).Length == 0) + var valueString = (string)value; + + DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = valueString?.Contains("hidden") == true ? HiddenForeColor : DataGridView.DefaultCellStyle.ForeColor; + + if (valueString.Length == 0) { base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts); DrawImage(graphics, ButtonImage, cellBounds); } else + { base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, value, formattedValue, errorText, cellStyle, advancedBorderStyle, paintParts); + } } } } diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index 4221082c..56d44638 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -48,13 +48,6 @@ namespace LibationWinForms this.Load += (_, __) => RestoreSizeAndLocation(); this.Load += (_, __) => RefreshImportMenu(); - // start background service - this.Load += (_, __) => startBackgroundImageDownloader(); - } - - private static void startBackgroundImageDownloader() - { - // load default/missing cover images. this will also initiate the background image downloader var format = System.Drawing.Imaging.ImageFormat.Jpeg; PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format)); PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format)); diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index d20c6553..f9de6708 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -5,9 +5,11 @@ using System.ComponentModel; using System.Drawing; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; using ApplicationServices; using DataLayer; using Dinah.Core.Drawing; +using Dinah.Core.Windows.Forms; namespace LibationWinForms { @@ -32,6 +34,7 @@ namespace LibationWinForms public event PropertyChangedEventHandler PropertyChanged; private Book Book => LibraryBook.Book; + private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; private Image _cover; public GridEntry(LibraryBook libraryBook) @@ -72,16 +75,23 @@ namespace LibationWinForms { if (pictureId == Book.PictureId) { + //GridEntry SHOULD be UI-ignorant, but PropertyChanged Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; } } - private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") + => SyncContext.Post( + args => OnPropertyChangedAsync(args as AsyncCompletedEventArgs), + new AsyncCompletedEventArgs(null, false, new PropertyChangedEventArgs(propertyName)) + ); - #region Data Source properties - public Image Cover + private void OnPropertyChangedAsync(AsyncCompletedEventArgs e) => + PropertyChanged?.Invoke(this, e.UserState as PropertyChangedEventArgs); + + #region Data Source properties + public Image Cover { get { diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index f06b8c1a..3f02b2e6 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -1,5 +1,4 @@ using System; -using System.Drawing; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; @@ -38,7 +37,6 @@ namespace LibationWinForms // sorting breaks filters. must reapply filters after sorting _dataGridView.Sorted += (_, __) => Filter(); - _dataGridView.CellFormatting += HiddenFormatting; _dataGridView.CellContentClick += DataGridView_CellContentClick; EnableDoubleBuffering(); @@ -133,7 +131,7 @@ namespace LibationWinForms var orderedGridEntries = lib .Select(lb => new GridEntry(lb)).ToList() // default load order - .OrderByDescending(ge => ge.PurchaseDate) + .OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate))) //// more advanced example: sort by author, then series, then title //.OrderBy(ge => ge.Authors) // .ThenBy(ge => ge.Series) @@ -155,10 +153,10 @@ namespace LibationWinForms public void RefreshRow(string productId) { - var rowId = getRowIndex((ge) => ge.AudibleProductId == productId); + var rowIndex = getRowIndex((ge) => ge.AudibleProductId == productId); // update cells incl Liberate button text - _dataGridView.InvalidateRow(rowId); + _dataGridView.InvalidateRow(rowIndex); // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change Filter(); @@ -166,28 +164,9 @@ namespace LibationWinForms BackupCountsChanged?.Invoke(this, EventArgs.Empty); } - #region format text cells. ie: not buttons - - private void HiddenFormatting(object sender, DataGridViewCellFormattingEventArgs e) - { - var dgv = (DataGridView)sender; - // no action needed for buttons - if (e.RowIndex < 0 || dgv.Columns[e.ColumnIndex] is DataGridViewButtonColumn) - return; - - var isHidden = getGridEntry(e.RowIndex).TagsEnumerated.Contains("hidden"); - - getCell(e).Style - = isHidden - ? new DataGridViewCellStyle { ForeColor = Color.LightGray } - : dgv.DefaultCellStyle; - } - #endregion - #endregion - - #region filter + #region Filter string _filterSearchString; private void Filter() => Filter(_filterSearchString); @@ -220,7 +199,6 @@ namespace LibationWinForms private int getRowIndex(Func func) => _dataGridView.GetRowIdOfBoundItem(func); private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); - private DataGridViewCell getCell(DataGridViewCellFormattingEventArgs e) => _dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex]; #endregion } From c7454ea5d2fb09a36bc2a69b593d69412c386511 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 23:11:37 -0600 Subject: [PATCH 09/64] Updated RemoveBooksDialog to use latest GridEntry --- ApplicationServices/LibraryCommands.cs | 2 +- .../Dialogs/RemoveBooksDialog.Designer.cs | 95 ++++++++++--------- LibationWinForms/Dialogs/RemoveBooksDialog.cs | 87 +++++++++-------- .../Dialogs/RemoveBooksDialog.resx | 2 +- LibationWinForms/GridEntry.cs | 4 +- 5 files changed, 97 insertions(+), 93 deletions(-) diff --git a/ApplicationServices/LibraryCommands.cs b/ApplicationServices/LibraryCommands.cs index 4672b942..c8f1a05f 100644 --- a/ApplicationServices/LibraryCommands.cs +++ b/ApplicationServices/LibraryCommands.cs @@ -39,7 +39,7 @@ namespace ApplicationServices var libraryItems = await scanAccountsAsync(loginCallbackFactoryFunc, accounts); Log.Logger.Information($"GetAllLibraryItems: Total count {libraryItems.Count}"); - var missingBookList = existingLibrary.Where(b => libraryItems.Count(i => i.DtoItem.Asin == b.Book.AudibleProductId) == 0).ToList(); + var missingBookList = existingLibrary.Where(b => !libraryItems.Any(i => i.DtoItem.Asin == b.Book.AudibleProductId)).ToList(); return missingBookList; } diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs index d4484ce3..af5435cc 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs @@ -30,17 +30,17 @@ namespace LibationWinForms.Dialogs private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle(); + System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); this.dataGridView1 = new System.Windows.Forms.DataGridView(); - this.gridEntryBindingSource = new System.Windows.Forms.BindingSource(this.components); - this.btnRemoveBooks = new System.Windows.Forms.Button(); - this.label1 = new System.Windows.Forms.Label(); 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.DatePurchased = 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.dataGridView1)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit(); this.SuspendLayout(); @@ -58,16 +58,16 @@ namespace LibationWinForms.Dialogs this.titleDataGridViewTextBoxColumn, this.authorsDataGridViewTextBoxColumn, this.miscDataGridViewTextBoxColumn, - this.DatePurchased}); + this.purchaseDateGridViewTextBoxColumn}); this.dataGridView1.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.dataGridView1.DefaultCellStyle = dataGridViewCellStyle1; + 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.dataGridView1.DefaultCellStyle = dataGridViewCellStyle2; this.dataGridView1.Location = new System.Drawing.Point(0, 0); this.dataGridView1.Name = "dataGridView1"; this.dataGridView1.RowHeadersVisible = false; @@ -75,32 +75,6 @@ namespace LibationWinForms.Dialogs this.dataGridView1.Size = new System.Drawing.Size(800, 409); this.dataGridView1.TabIndex = 0; // - // 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(169, 15); - this.label1.TabIndex = 2; - this.label1.Text = "{0} books selected for removal."; - // // removeDataGridViewCheckBoxColumn // this.removeDataGridViewCheckBoxColumn.DataPropertyName = "Remove"; @@ -109,6 +83,7 @@ namespace LibationWinForms.Dialogs 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; @@ -146,13 +121,39 @@ namespace LibationWinForms.Dialogs this.miscDataGridViewTextBoxColumn.ReadOnly = true; this.miscDataGridViewTextBoxColumn.Width = 150; // - // DatePurchased + // purchaseDateGridViewTextBoxColumn // - this.DatePurchased.DataPropertyName = "DatePurchased"; - this.DatePurchased.HeaderText = "Date Purchased"; - this.DatePurchased.Name = "DatePurchased"; - this.DatePurchased.ReadOnly = true; - this.DatePurchased.Width = 120; + 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 // @@ -183,6 +184,6 @@ namespace LibationWinForms.Dialogs private System.Windows.Forms.DataGridViewTextBoxColumn titleDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewTextBoxColumn authorsDataGridViewTextBoxColumn; private System.Windows.Forms.DataGridViewTextBoxColumn miscDataGridViewTextBoxColumn; - private System.Windows.Forms.DataGridViewTextBoxColumn DatePurchased; + private System.Windows.Forms.DataGridViewTextBoxColumn purchaseDateGridViewTextBoxColumn; } } \ No newline at end of file diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs index 04e7af16..5c7a9849 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -6,12 +6,8 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; -using System.Drawing; using System.Linq; using System.Windows.Forms; -using Dinah.Core.DataBinding; -using System.Runtime.CompilerServices; -using Dinah.Core.Drawing; using System.Collections; namespace LibationWinForms.Dialogs @@ -22,7 +18,7 @@ namespace LibationWinForms.Dialogs private Account[] _accounts { get; } private List _libraryBooks; - private SortableBindingList _removableGridEntries; + private SortableBindingList2 _removableGridEntries; private string _labelFormat; private int SelectedCount => SelectedEntries?.Count() ?? 0; private IEnumerable SelectedEntries => _removableGridEntries?.Where(b => b.Remove); @@ -31,6 +27,7 @@ namespace LibationWinForms.Dialogs { _libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking(); _accounts = accounts; + InitializeComponent(); _labelFormat = label1.Text; @@ -40,10 +37,10 @@ namespace LibationWinForms.Dialogs var orderedGridEntries = _libraryBooks .Select(lb => new RemovableGridEntry(lb)) - .OrderByDescending(ge => ge.PurchaseDate) + .OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate))) .ToList(); - _removableGridEntries = orderedGridEntries.ToSortableBindingList(); + _removableGridEntries = new SortableBindingList2(orderedGridEntries); gridEntryBindingSource.DataSource = _removableGridEntries; dataGridView1.Enabled = false; @@ -52,9 +49,7 @@ namespace LibationWinForms.Dialogs private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) { if (e.ColumnIndex == 0) - { UpdateSelection(); - } } private async void RemoveBooksDialog_Shown(object sender, EventArgs e) @@ -65,9 +60,9 @@ namespace LibationWinForms.Dialogs { var rmovedBooks = await LibraryCommands.FindInactiveBooks((account) => new WinformResponder(account), _libraryBooks, _accounts); - var removable = _removableGridEntries.Where(rge => rmovedBooks.Count(rb => rb.Book.AudibleProductId == rge.AudibleProductId) == 1); + var removable = _removableGridEntries.Where(rge => rmovedBooks.Any(rb => rb.Book.AudibleProductId == rge.AudibleProductId)); - if (removable.Count() == 0) + if (!removable.Any()) return; foreach (var r in removable) @@ -90,15 +85,18 @@ namespace LibationWinForms.Dialogs private void btnRemoveBooks_Click(object sender, EventArgs e) { - var selected = SelectedEntries.ToList(); + var selectedBooks = SelectedEntries.ToList(); - if (selected.Count == 0) return; + if (selectedBooks.Count == 0) return; - string titles = string.Join("\r\n", selected.Select(rge => "-" + rge.Title)); + string titles = string.Join("\r\n", selectedBooks.Select(rge => "-" + rge.Title)); + + string thisThese = selectedBooks.Count > 1 ? "these" : "this"; + string bookBooks = selectedBooks.Count > 1 ? "books" : "book"; var result = MessageBox.Show( this, - $"Are you sure you want to remove the following {selected.Count} books from Libation's library?\r\n\r\n{titles}", + $"Are you sure you want to remove {thisThese} {selectedBooks.Count} {bookBooks} from Libation's library?\r\n\r\n{titles}", "Remove books from Libation?", MessageBoxButtons.YesNo, MessageBoxIcon.Question, @@ -110,14 +108,16 @@ namespace LibationWinForms.Dialogs var libBooks = context.GetLibrary_Flat_NoTracking(); - var removeLibraryBooks = libBooks.Where(lb => selected.Count(rge => rge.AudibleProductId == lb.Book.AudibleProductId) == 1).ToArray(); + var removeLibraryBooks = libBooks.Where(lb => selectedBooks.Any(rge => rge.AudibleProductId == lb.Book.AudibleProductId)).ToArray(); + context.Library.RemoveRange(removeLibraryBooks); context.SaveChanges(); - BooksRemoved = true; - foreach (var rEntry in selected) + foreach (var rEntry in selectedBooks) _removableGridEntries.Remove(rEntry); + BooksRemoved = removeLibraryBooks.Length > 0; + UpdateSelection(); } } @@ -125,44 +125,47 @@ namespace LibationWinForms.Dialogs { dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Descending); var selectedCount = SelectedCount; - label1.Text = string.Format(_labelFormat, selectedCount); + label1.Text = string.Format(_labelFormat, selectedCount, selectedCount != 1 ? "s" : string.Empty); btnRemoveBooks.Enabled = selectedCount > 0; } } - class CompareBool : IComparer - { - public int Compare(object x, object y) - { - var rge1 = x as RemovableGridEntry; - var rge2 = y as RemovableGridEntry; - - return rge1.Remove.CompareTo(rge2.Remove); - } - } - internal class RemovableGridEntry : GridEntry { + private static readonly IComparer BoolComparer = new ObjectComparer(); + + private bool _remove = false; + public RemovableGridEntry(LibraryBook libraryBook) : base(libraryBook) { } + public bool Remove - { - get - { + { + get + { return _remove; - } - set - { + } + set + { if (_remove != value) { _remove = value; NotifyPropertyChanged(); } } - } - - private bool _remove = false; - - public RemovableGridEntry(LibraryBook libraryBook) :base(libraryBook) - { } + + public override object GetMemberValue(string propertyName) + { + if (propertyName == nameof(Remove)) + return Remove; + return base.GetMemberValue(propertyName); + } + + public override IComparer GetComparer(Type propertyType) + { + if (propertyType == typeof(bool)) + return BoolComparer; + + return base.GetComparer(propertyType); + } } } diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.resx b/LibationWinForms/Dialogs/RemoveBooksDialog.resx index 303f91a6..9b5e228b 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.resx +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.resx @@ -57,7 +57,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + True diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index f9de6708..9e1076fe 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -126,8 +126,8 @@ namespace LibationWinForms private Dictionary> _compareValues { get; } private static Dictionary _objectComparers; - public object GetMemberValue(string propertyName) => _compareValues[propertyName](); - public IComparer GetComparer(Type propertyType) => _objectComparers[propertyType]; + public virtual object GetMemberValue(string propertyName) => _compareValues[propertyName](); + public virtual IComparer GetComparer(Type propertyType) => _objectComparers[propertyType]; /// /// Instantiate comparers for every type needed to sort columns. From 6aefdfca9db7e69ea6f9fdf1144921dc868125c8 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 23:25:41 -0600 Subject: [PATCH 10/64] Fixed tool strip menu for book removal. --- LibationWinForms/Form1.Designer.cs | 36 +++++++++++++++++++++++------- LibationWinForms/Form1.cs | 33 ++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/LibationWinForms/Form1.Designer.cs b/LibationWinForms/Form1.Designer.cs index e4659ea3..3762f817 100644 --- a/LibationWinForms/Form1.Designer.cs +++ b/LibationWinForms/Form1.Designer.cs @@ -39,6 +39,9 @@ this.scanLibraryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.scanLibraryOfAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.scanLibraryOfSomeAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeLibraryBooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeSomeAccountsToolStripMenuItem = 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(); @@ -58,7 +61,6 @@ this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.addFilterBtn = new System.Windows.Forms.Button(); - this.removeLibraryBooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); this.SuspendLayout(); @@ -163,6 +165,29 @@ this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts..."; this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click); // + // removeLibraryBooksToolStripMenuItem + // + this.removeLibraryBooksToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.removeAllAccountsToolStripMenuItem, + this.removeSomeAccountsToolStripMenuItem}); + this.removeLibraryBooksToolStripMenuItem.Name = "removeLibraryBooksToolStripMenuItem"; + this.removeLibraryBooksToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.removeLibraryBooksToolStripMenuItem.Text = "Remove Library Books"; + // + // removeAllAccountsToolStripMenuItem + // + this.removeAllAccountsToolStripMenuItem.Name = "removeAllAccountsToolStripMenuItem"; + 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(180, 22); + this.removeSomeAccountsToolStripMenuItem.Text = "Some Accounts"; + this.removeSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeSomeAccountsToolStripMenuItem_Click); + // // liberateToolStripMenuItem // this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -311,13 +336,6 @@ this.addFilterBtn.UseVisualStyleBackColor = true; this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click); // - // removeLibraryBooksToolStripMenuItem - // - 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); - // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); @@ -378,5 +396,7 @@ private System.Windows.Forms.ToolStripMenuItem exportLibraryToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem convertAllM4bToMp3ToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem removeLibraryBooksToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem removeAllAccountsToolStripMenuItem; + private System.Windows.Forms.ToolStripMenuItem removeSomeAccountsToolStripMenuItem; } } diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index 56d44638..10387143 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -293,6 +293,16 @@ namespace LibationWinForms scanLibraryToolStripMenuItem.Visible = count == 1; scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1; scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1; + + removeLibraryBooksToolStripMenuItem.Visible = count != 0; + + if (count == 1) + { + removeLibraryBooksToolStripMenuItem.Click += removeThisAccountToolStripMenuItem_Click; + } + + removeSomeAccountsToolStripMenuItem.Visible = count > 1; + removeAllAccountsToolStripMenuItem.Visible = count > 1; } private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e) @@ -449,12 +459,33 @@ namespace LibationWinForms private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); #endregion - private void removeLibraryBooksToolStripMenuItem_Click(object sender, EventArgs e) + + private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) { using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); scanLibrariesRemovedBooks(firstAccount); } + + private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var allAccounts = persister.AccountsSettings.GetAll(); + scanLibrariesRemovedBooks(allAccounts.ToArray()); + } + + private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var scanAccountsDialog = new ScanAccountsDialog(this); + + if (scanAccountsDialog.ShowDialog() != DialogResult.OK) + return; + + if (!scanAccountsDialog.CheckedAccounts.Any()) + return; + + scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); + } private void scanLibrariesRemovedBooks(params Account[] accounts) { using var dialog = new RemoveBooksDialog(accounts); From a4cb934611bef64e935eac2e804edb522228c074 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 23:31:59 -0600 Subject: [PATCH 11/64] Updated resource file. --- .../Dialogs/RemoveBooksDialog.resx | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.resx b/LibationWinForms/Dialogs/RemoveBooksDialog.resx index 9b5e228b..774ae444 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.resx +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.resx @@ -1,4 +1,64 @@ - + + + @@ -52,15 +112,9 @@ 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - True - - - 17, 17 - \ No newline at end of file From 6f184273b81dcbcb5105743efe748ab153cb21ca Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 9 Aug 2021 23:48:32 -0600 Subject: [PATCH 12/64] Removed unnecessary declarations. --- LibationWinForms/ProductsGrid.Designer.cs | 4 +--- LibationWinForms/ProductsGrid.resx | 15 --------------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/LibationWinForms/ProductsGrid.Designer.cs b/LibationWinForms/ProductsGrid.Designer.cs index ce28cfdd..f1338213 100644 --- a/LibationWinForms/ProductsGrid.Designer.cs +++ b/LibationWinForms/ProductsGrid.Designer.cs @@ -58,6 +58,7 @@ // this.gridEntryDataGridView.AllowUserToAddRows = false; this.gridEntryDataGridView.AllowUserToDeleteRows = false; + this.gridEntryDataGridView.AllowUserToResizeRows = false; this.gridEntryDataGridView.AutoGenerateColumns = false; this.gridEntryDataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; this.gridEntryDataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] { @@ -227,9 +228,6 @@ private System.Windows.Forms.BindingSource gridEntryBindingSource; private System.Windows.Forms.DataGridView gridEntryDataGridView; - private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn8; - private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn9; - private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn10; private LiberateDataGridViewImageButtonColumn dataGridViewImageButtonBoxColumn1; private System.Windows.Forms.DataGridViewImageColumn dataGridViewImageColumn1; private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1; diff --git a/LibationWinForms/ProductsGrid.resx b/LibationWinForms/ProductsGrid.resx index 688d741a..8598c799 100644 --- a/LibationWinForms/ProductsGrid.resx +++ b/LibationWinForms/ProductsGrid.resx @@ -60,19 +60,4 @@ 17, 17 - - True - - - True - - - True - - - True - - - True - \ No newline at end of file From 235d0acedeac85278e7f7d1a87294b8ba92e567a Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 00:05:18 -0600 Subject: [PATCH 13/64] Removed unnecessary code. --- LibationWinForms/ProductsGrid.cs | 6 +++--- LibationWinForms/SortableBindingList2[T].cs | 17 ----------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 3f02b2e6..482f2218 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -181,15 +181,15 @@ namespace LibationWinForms var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); // https://stackoverflow.com/a/18942430 - var currencyManager = (CurrencyManager)BindingContext[_dataGridView.DataSource]; - currencyManager.SuspendBinding(); + 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); } //Causes repainting of the DataGridView - currencyManager.ResumeBinding(); + bindingContext.ResumeBinding(); VisibleCountChanged?.Invoke(this, _dataGridView.AsEnumerable().Count(r => r.Visible)); } diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs index 0a2bf465..8cc76d7b 100644 --- a/LibationWinForms/SortableBindingList2[T].cs +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -27,8 +27,6 @@ namespace LibationWinForms protected override ListSortDirection SortDirectionCore => listSortDirection; - protected override bool SupportsSearchingCore => true; - protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) { List itemsList = (List)Items; @@ -57,20 +55,5 @@ namespace LibationWinForms OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); } - - protected override int FindCore(PropertyDescriptor property, object key) - { - int count = Count; - for (int i = 0; i < count; ++i) - { - T element = this[i]; - if (property.GetValue(element).Equals(key)) - { - return i; - } - } - - return -1; - } } } From d770109d8609436d4ee6ea0578a0a58cae3ac3e7 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 00:15:07 -0600 Subject: [PATCH 14/64] Removed double buffering. --- LibationWinForms/ProductsGrid.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 482f2218..5879ac9c 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -38,19 +38,8 @@ namespace LibationWinForms // sorting breaks filters. must reapply filters after sorting _dataGridView.Sorted += (_, __) => Filter(); _dataGridView.CellContentClick += DataGridView_CellContentClick; - - EnableDoubleBuffering(); } - private void EnableDoubleBuffering() - { - var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - - //var before = (bool)propertyInfo.GetValue(dataGridView); - propertyInfo.SetValue(_dataGridView, true, null); - //var after = (bool)propertyInfo.GetValue(dataGridView); - } - #region Button controls private async void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) From 0b1d513f50690e5336277984df5e3f4f45e5dc17 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 00:18:43 -0600 Subject: [PATCH 15/64] Reorganized. --- LibationWinForms/Form1.cs | 70 +++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index 10387143..b48abc32 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -337,6 +337,40 @@ namespace LibationWinForms scanLibraries(scanAccountsDialog.CheckedAccounts); } + private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); + scanLibrariesRemovedBooks(firstAccount); + } + + private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var allAccounts = persister.AccountsSettings.GetAll(); + scanLibrariesRemovedBooks(allAccounts.ToArray()); + } + + private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var scanAccountsDialog = new ScanAccountsDialog(this); + + if (scanAccountsDialog.ShowDialog() != DialogResult.OK) + return; + + if (!scanAccountsDialog.CheckedAccounts.Any()) + return; + + scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); + } + private void scanLibrariesRemovedBooks(params Account[] accounts) + { + using var dialog = new RemoveBooksDialog(accounts); + dialog.ShowDialog(); + + if (dialog.BooksRemoved) + reloadGrid(); + } private void scanLibraries(IEnumerable accounts) => scanLibraries(accounts.ToArray()); private void scanLibraries(params Account[] accounts) @@ -458,41 +492,5 @@ namespace LibationWinForms private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); #endregion - - - private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); - scanLibrariesRemovedBooks(firstAccount); - } - - private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var allAccounts = persister.AccountsSettings.GetAll(); - scanLibrariesRemovedBooks(allAccounts.ToArray()); - } - - private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var scanAccountsDialog = new ScanAccountsDialog(this); - - if (scanAccountsDialog.ShowDialog() != DialogResult.OK) - return; - - if (!scanAccountsDialog.CheckedAccounts.Any()) - return; - - scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); - } - private void scanLibrariesRemovedBooks(params Account[] accounts) - { - using var dialog = new RemoveBooksDialog(accounts); - dialog.ShowDialog(); - - if (dialog.BooksRemoved) - reloadGrid(); - } } } From 5c8ad72a5eec6e65439f8db0c331bdb4d252c88b Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 00:24:21 -0600 Subject: [PATCH 16/64] Removed unnecessary using. --- LibationWinForms/GridEntry.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 9e1076fe..1ddb77e9 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -9,7 +9,6 @@ using System.Threading; using ApplicationServices; using DataLayer; using Dinah.Core.Drawing; -using Dinah.Core.Windows.Forms; namespace LibationWinForms { @@ -84,8 +83,7 @@ namespace LibationWinForms protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") => SyncContext.Post( args => OnPropertyChangedAsync(args as AsyncCompletedEventArgs), - new AsyncCompletedEventArgs(null, false, new PropertyChangedEventArgs(propertyName)) - ); + new AsyncCompletedEventArgs(null, false, new PropertyChangedEventArgs(propertyName))); private void OnPropertyChangedAsync(AsyncCompletedEventArgs e) => PropertyChanged?.Invoke(this, e.UserState as PropertyChangedEventArgs); From 957bec1c7ff5834bfe895402a0d2b2e5464fc67e Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 00:28:48 -0600 Subject: [PATCH 17/64] Removed excessive declarations. --- LibationWinForms/GridEntry.cs | 6 +----- LibationWinForms/ProductsGrid.cs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 1ddb77e9..5113d10f 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -23,10 +23,6 @@ namespace LibationWinForms [Browsable(false)] public string AudibleProductId => Book.AudibleProductId; [Browsable(false)] - public string Tags => Book.UserDefinedItem.Tags; - [Browsable(false)] - public IEnumerable TagsEnumerated => Book.UserDefinedItem.TagsEnumerated; - [Browsable(false)] public LibraryBook LibraryBook { get; } #endregion @@ -114,7 +110,7 @@ namespace LibationWinForms public string Misc { get; } public string Description { get; } - public string DisplayTags => string.Join("\r\n", TagsEnumerated); + public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book)); #endregion diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 5879ac9c..5b97bbff 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -80,7 +80,7 @@ namespace LibationWinForms private void EditTags_Click(GridEntry liveGridEntry) { - var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.Tags); + var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.LibraryBook.Book.UserDefinedItem.Tags); if (bookDetailsForm.ShowDialog() != DialogResult.OK) return; From f81552565a08bcad146827b6d7af9bfe2ca915c6 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 00:33:24 -0600 Subject: [PATCH 18/64] Re-added double buffering. --- LibationWinForms/ProductsGrid.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 5b97bbff..10af0c41 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -38,7 +38,15 @@ namespace LibationWinForms // sorting breaks filters. must reapply filters after sorting _dataGridView.Sorted += (_, __) => Filter(); _dataGridView.CellContentClick += DataGridView_CellContentClick; - } + + EnableDoubleBuffering(); + } + private void EnableDoubleBuffering() + { + var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + + propertyInfo.SetValue(_dataGridView, true, null); + } #region Button controls From e1dfefbadf321a4a5c7d2a355c188f4c20a0bcde Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 10:17:02 -0600 Subject: [PATCH 19/64] Comments and renaming. --- LibationWinForms/GridEntry.cs | 10 ++++------ LibationWinForms/IObjectMemberComparable.cs | 4 ++-- LibationWinForms/ObjectMemberComparer[T].cs | 2 +- LibationWinForms/ProductsGrid.cs | 8 ++++---- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 5113d10f..25db3669 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -14,9 +14,6 @@ namespace LibationWinForms { internal class GridEntry : INotifyPropertyChanged, IObjectMemberComparable { - public const string LIBERATE_COLUMN_NAME = "Liberate"; - public const string EDIT_TAGS_COLUMN_NAME = "DisplayTags"; - #region implementation properties // hide from public fields from Data Source GUI with [Browsable(false)] @@ -120,8 +117,8 @@ namespace LibationWinForms private Dictionary> _compareValues { get; } private static Dictionary _objectComparers; - public virtual object GetMemberValue(string propertyName) => _compareValues[propertyName](); - public virtual IComparer GetComparer(Type propertyType) => _objectComparers[propertyType]; + public virtual object GetMemberValue(string memberName) => _compareValues[memberName](); + public virtual IComparer GetMemberComparer(Type memberType) => _objectComparers[memberType]; /// /// Instantiate comparers for every type needed to sort columns. @@ -137,6 +134,7 @@ namespace LibationWinForms { typeof(LiberatedState), new ObjectComparer() }, }; } + /// /// Create getters for all member values by name /// @@ -277,7 +275,7 @@ namespace LibationWinForms } private static string GetStarString(Rating rating) - => (rating?.FirstScore != null && rating?.FirstScore > 0f) + => (rating?.FirstScore > 0f) ? rating?.ToStarString() : ""; diff --git a/LibationWinForms/IObjectMemberComparable.cs b/LibationWinForms/IObjectMemberComparable.cs index 8bc5b762..ade585ab 100644 --- a/LibationWinForms/IObjectMemberComparable.cs +++ b/LibationWinForms/IObjectMemberComparable.cs @@ -5,7 +5,7 @@ namespace LibationWinForms { interface IObjectMemberComparable { - IComparer GetComparer(Type propertyType); - object GetMemberValue(string valueName); + IComparer GetMemberComparer(Type memberType); + object GetMemberValue(string memberName); } } diff --git a/LibationWinForms/ObjectMemberComparer[T].cs b/LibationWinForms/ObjectMemberComparer[T].cs index 00f29510..aea41e21 100644 --- a/LibationWinForms/ObjectMemberComparer[T].cs +++ b/LibationWinForms/ObjectMemberComparer[T].cs @@ -13,7 +13,7 @@ namespace LibationWinForms var val1 = x.GetMemberValue(PropertyName); var val2 = y.GetMemberValue(PropertyName); - return DirMult * x.GetComparer(val1.GetType()).Compare(val1, val2); + return DirMult * x.GetMemberComparer(val1.GetType()).Compare(val1, val2); } private int DirMult => Direction == ListSortDirection.Descending ? -1 : 1; diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 10af0c41..292d6a17 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -23,6 +23,8 @@ namespace LibationWinForms // - go to Design view // - click on Data Sources > ProductItem. drowdown: DataGridView // - drag/drop ProductItem on design surface + // AS OF AUGUST 2021 THIS DOES NOT WORK IN VS2019 WITH .NET-5 PROJECTS + public partial class ProductsGrid : UserControl { public event EventHandler VisibleCountChanged; @@ -60,10 +62,10 @@ namespace LibationWinForms switch (_dataGridView.Columns[e.ColumnIndex].DataPropertyName) { - case GridEntry.LIBERATE_COLUMN_NAME: + case nameof(liveGridEntry.Liberate): await Liberate_Click(liveGridEntry); break; - case GridEntry.EDIT_TAGS_COLUMN_NAME: + case nameof(liveGridEntry.DisplayTags): EditTags_Click(liveGridEntry); break; } @@ -140,9 +142,7 @@ namespace LibationWinForms // gridEntryBindingSource.DataSource = new SortableBindingList2(orderedGridEntries); - // // FILTER - // Filter(); BackupCountsChanged?.Invoke(this, EventArgs.Empty); From 95766a43c5392503b23aaef729a08121af4ce5b9 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 10:22:03 -0600 Subject: [PATCH 20/64] Refactoring. --- .../AsyncNotifyPropertyChanged.cs | 37 ++++ .../DataGridViewImageButtonColumn.cs | 2 +- .../Dialogs/RemoveBooksDialog.Designer.cs | 36 ++-- LibationWinForms/Dialogs/RemoveBooksDialog.cs | 35 ++- .../Dialogs/RemoveBooksDialog.resx | 65 +----- .../EditTagsDataGridViewImageButtonColumn.cs | 17 +- LibationWinForms/Form1.cs | 2 + LibationWinForms/GridEntry.cs | 203 +++++++----------- .../LiberateDataGridViewImageButtonColumn.cs | 2 +- LibationWinForms/ProductsGrid.cs | 4 +- LibationWinForms/SortableBindingList2[T].cs | 36 +++- 11 files changed, 197 insertions(+), 242 deletions(-) create mode 100644 LibationWinForms/AsyncNotifyPropertyChanged.cs diff --git a/LibationWinForms/AsyncNotifyPropertyChanged.cs b/LibationWinForms/AsyncNotifyPropertyChanged.cs new file mode 100644 index 00000000..9de0911d --- /dev/null +++ b/LibationWinForms/AsyncNotifyPropertyChanged.cs @@ -0,0 +1,37 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; + +namespace LibationWinForms +{ + public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; + private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; + private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; + + protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") + { + var propertyChangedArgs = new PropertyChangedEventArgs(propertyName); + + if (InvokeRequired) + { + SyncContext.Post( + PostPropertyChangedCallback, + new AsyncCompletedEventArgs(null, false, propertyChangedArgs)); + } + else + { + OnPropertyChanged(propertyChangedArgs); + } + } + private void PostPropertyChangedCallback(object asyncArgs) + { + var e = asyncArgs as AsyncCompletedEventArgs; + + OnPropertyChanged(e.UserState as PropertyChangedEventArgs); + } + private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); + } +} diff --git a/LibationWinForms/DataGridViewImageButtonColumn.cs b/LibationWinForms/DataGridViewImageButtonColumn.cs index 31d41780..e607a069 100644 --- a/LibationWinForms/DataGridViewImageButtonColumn.cs +++ b/LibationWinForms/DataGridViewImageButtonColumn.cs @@ -37,7 +37,7 @@ namespace LibationWinForms public class DataGridViewImageButtonCell : DataGridViewButtonCell { - protected void DrawImage(Graphics graphics, Bitmap image, Rectangle cellBounds) + protected void DrawButtonImage(Graphics graphics, Image image, Rectangle cellBounds) { var w = image.Width; var h = image.Height; diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs index af5435cc..5bf4e6c9 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.Designer.cs @@ -31,7 +31,7 @@ namespace LibationWinForms.Dialogs { this.components = new System.ComponentModel.Container(); System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle(); - this.dataGridView1 = new System.Windows.Forms.DataGridView(); + 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(); @@ -41,25 +41,25 @@ namespace LibationWinForms.Dialogs 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.dataGridView1)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this._dataGridView)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit(); this.SuspendLayout(); // - // dataGridView1 + // _dataGridView // - this.dataGridView1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + 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.dataGridView1.AutoGenerateColumns = false; - this.dataGridView1.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.dataGridView1.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.dataGridView1.DataSource = this.gridEntryBindingSource; + 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); @@ -67,13 +67,13 @@ namespace LibationWinForms.Dialogs dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight; dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText; dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.True; - this.dataGridView1.DefaultCellStyle = dataGridViewCellStyle2; - this.dataGridView1.Location = new System.Drawing.Point(0, 0); - this.dataGridView1.Name = "dataGridView1"; - this.dataGridView1.RowHeadersVisible = false; - this.dataGridView1.RowTemplate.Height = 82; - this.dataGridView1.Size = new System.Drawing.Size(800, 409); - this.dataGridView1.TabIndex = 0; + 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 // @@ -162,11 +162,11 @@ namespace LibationWinForms.Dialogs this.ClientSize = new System.Drawing.Size(800, 450); this.Controls.Add(this.label1); this.Controls.Add(this.btnRemoveBooks); - this.Controls.Add(this.dataGridView1); + this.Controls.Add(this._dataGridView); this.Name = "RemoveBooksDialog"; this.Text = "RemoveBooksDialog"; this.Shown += new System.EventHandler(this.RemoveBooksDialog_Shown); - ((System.ComponentModel.ISupportInitialize)(this.dataGridView1)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this._dataGridView)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); @@ -175,7 +175,7 @@ namespace LibationWinForms.Dialogs #endregion - private System.Windows.Forms.DataGridView dataGridView1; + private System.Windows.Forms.DataGridView _dataGridView; private System.Windows.Forms.BindingSource gridEntryBindingSource; private System.Windows.Forms.Button btnRemoveBooks; private System.Windows.Forms.Label label1; diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs index 5c7a9849..a31ca838 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -31,9 +31,9 @@ namespace LibationWinForms.Dialogs InitializeComponent(); _labelFormat = label1.Text; - dataGridView1.CellContentClick += (s, e) => dataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit); - dataGridView1.CellValueChanged += DataGridView1_CellValueChanged; - dataGridView1.BindingContextChanged += (s, e) => UpdateSelection(); + _dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit); + _dataGridView.CellValueChanged += DataGridView1_CellValueChanged; + _dataGridView.BindingContextChanged += (s, e) => UpdateSelection(); var orderedGridEntries = _libraryBooks .Select(lb => new RemovableGridEntry(lb)) @@ -43,7 +43,7 @@ namespace LibationWinForms.Dialogs _removableGridEntries = new SortableBindingList2(orderedGridEntries); gridEntryBindingSource.DataSource = _removableGridEntries; - dataGridView1.Enabled = false; + _dataGridView.Enabled = false; } private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) @@ -79,7 +79,7 @@ namespace LibationWinForms.Dialogs } finally { - dataGridView1.Enabled = true; + _dataGridView.Enabled = true; } } @@ -103,12 +103,12 @@ namespace LibationWinForms.Dialogs MessageBoxDefaultButton.Button1); 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)).ToArray(); + var removeLibraryBooks = libBooks.Where(lb => selectedBooks.Any(rge => rge.AudibleProductId == lb.Book.AudibleProductId)).ToList(); context.Library.RemoveRange(removeLibraryBooks); context.SaveChanges(); @@ -116,14 +116,14 @@ namespace LibationWinForms.Dialogs foreach (var rEntry in selectedBooks) _removableGridEntries.Remove(rEntry); - BooksRemoved = removeLibraryBooks.Length > 0; + BooksRemoved = removeLibraryBooks.Count > 0; UpdateSelection(); } } private void UpdateSelection() { - dataGridView1.Sort(dataGridView1.Columns[0], ListSortDirection.Descending); + _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; @@ -153,19 +153,18 @@ namespace LibationWinForms.Dialogs } } - public override object GetMemberValue(string propertyName) + public override object GetMemberValue(string memberName) { - if (propertyName == nameof(Remove)) + if (memberName == nameof(Remove)) return Remove; - return base.GetMemberValue(propertyName); + return base.GetMemberValue(memberName); } - public override IComparer GetComparer(Type propertyType) - { - if (propertyType == typeof(bool)) + public override IComparer GetMemberComparer(Type memberType) + { + if (memberType == typeof(bool)) return BoolComparer; - - return base.GetComparer(propertyType); - } + return base.GetMemberComparer(memberType); + } } } diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.resx b/LibationWinForms/Dialogs/RemoveBooksDialog.resx index 774ae444..a3058bc8 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.resx +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.resx @@ -1,64 +1,4 @@ - - - + @@ -117,4 +57,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs index 58f39545..1f4cf04d 100644 --- a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs +++ b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs @@ -11,19 +11,26 @@ namespace LibationWinForms internal class EditTagsDataGridViewImageButtonCell : DataGridViewImageButtonCell { - private static readonly Bitmap ButtonImage = Properties.Resources.edit_tags_25x25; + private static readonly Image ButtonImage = Properties.Resources.edit_tags_25x25; private static readonly Color HiddenForeColor = Color.LightGray; protected override void Paint(Graphics graphics, Rectangle clipBounds, Rectangle cellBounds, int rowIndex, DataGridViewElementStates elementState, object value, object formattedValue, string errorText, DataGridViewCellStyle cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle, DataGridViewPaintParts paintParts) { - var valueString = (string)value; + var tagsString = (string)value; - DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = valueString?.Contains("hidden") == true ? HiddenForeColor : DataGridView.DefaultCellStyle.ForeColor; + var foreColor = tagsString?.Contains("hidden") == true ? HiddenForeColor : DataGridView.DefaultCellStyle.ForeColor; - if (valueString.Length == 0) + if (DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor != foreColor) + { + DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = foreColor; + + DataGridView.InvalidateRow(RowIndex); + } + + if (tagsString.Length == 0) { base.Paint(graphics, clipBounds, cellBounds, rowIndex, elementState, null, null, null, cellStyle, advancedBorderStyle, paintParts); - DrawImage(graphics, ButtonImage, cellBounds); + DrawButtonImage(graphics, ButtonImage, cellBounds); } else { diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index b48abc32..30bba304 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -337,6 +337,7 @@ namespace LibationWinForms scanLibraries(scanAccountsDialog.CheckedAccounts); } + private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) { using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); @@ -363,6 +364,7 @@ namespace LibationWinForms scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); } + private void scanLibrariesRemovedBooks(params Account[] accounts) { using var dialog = new RemoveBooksDialog(accounts); diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 25db3669..64a802f7 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -4,36 +4,32 @@ using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using ApplicationServices; using DataLayer; using Dinah.Core.Drawing; namespace LibationWinForms { - internal class GridEntry : INotifyPropertyChanged, IObjectMemberComparable + internal class GridEntry : AsyncNotifyPropertyChanged, IObjectMemberComparable { - #region implementation properties - // hide from public fields from Data Source GUI with [Browsable(false)] + #region implementation properties + // hide from public fields from Data Source GUI with [Browsable(false)] - [Browsable(false)] + [Browsable(false)] public string AudibleProductId => Book.AudibleProductId; [Browsable(false)] public LibraryBook LibraryBook { get; } #endregion - public event PropertyChangedEventHandler PropertyChanged; private Book Book => LibraryBook.Book; - private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; private Image _cover; public GridEntry(LibraryBook libraryBook) { LibraryBook = libraryBook; - _compareValues = CreatePropertyValueDictionary(); + _memberValues = CreateMemberValueDictionary(); //Get cover art. If it's default, subscribe to PictureCached var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80); @@ -42,7 +38,7 @@ namespace LibationWinForms if (isDefault) FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; - //Mutable property. Set the field so PropertyChanged doesn't fire. + //Mutable property. Set the field so PropertyChanged isn't fired. _cover = ImageReader.ToImage(picture); //Immutable properties @@ -50,9 +46,9 @@ namespace LibationWinForms Title = Book.Title; Series = Book.SeriesNames; Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min"; - MyRating = GetStarString(Book.UserDefinedItem.Rating); + MyRating = ValueOrDefault(Book.UserDefinedItem.Rating?.ToStarString(), ""); PurchaseDate = libraryBook.DateAdded.ToString("d"); - ProductRating = GetStarString(Book.Rating); + ProductRating = ValueOrDefault(Book.Rating?.ToStarString(), ""); Authors = Book.AuthorNames; Narrators = Book.NarratorNames; Category = string.Join(" > ", Book.CategoriesNames); @@ -60,28 +56,20 @@ namespace LibationWinForms Description = GetDescriptionDisplay(Book); } - //DisplayTags and Liberate are live. + //DisplayTags and Liberate properties are live. } - private void PictureStorage_PictureCached(object sender, string pictureId) + private void PictureStorage_PictureCached(object sender, string pictureId) { if (pictureId == Book.PictureId) { - //GridEntry SHOULD be UI-ignorant, but PropertyChanged Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; } } - protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") - => SyncContext.Post( - args => OnPropertyChangedAsync(args as AsyncCompletedEventArgs), - new AsyncCompletedEventArgs(null, false, new PropertyChangedEventArgs(propertyName))); - - private void OnPropertyChangedAsync(AsyncCompletedEventArgs e) => - PropertyChanged?.Invoke(this, e.UserState as PropertyChangedEventArgs); - #region Data Source properties + public Image Cover { get @@ -106,7 +94,6 @@ namespace LibationWinForms public string Category { get; } public string Misc { get; } public string Description { get; } - public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book)); @@ -114,49 +101,43 @@ namespace LibationWinForms #region Data Sorting - private Dictionary> _compareValues { get; } - private static Dictionary _objectComparers; - - public virtual object GetMemberValue(string memberName) => _compareValues[memberName](); - public virtual IComparer GetMemberComparer(Type memberType) => _objectComparers[memberType]; + private Dictionary> _memberValues { get; } /// - /// Instantiate comparers for every type needed to sort columns. + /// Create getters for all member object values by name /// - static GridEntry() + Dictionary> CreateMemberValueDictionary() => new() { - _objectComparers = new Dictionary() - { - { typeof(string), new ObjectComparer() }, - { typeof(int), new ObjectComparer() }, - { typeof(float), new ObjectComparer() }, - { typeof(DateTime), new ObjectComparer() }, - { typeof(LiberatedState), new ObjectComparer() }, - }; - } + { nameof(Title), () => GetSortName(Book.Title) }, + { nameof(Series), () => GetSortName(Book.SeriesNames) }, + { nameof(Length), () => Book.LengthInMinutes }, + { nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore }, + { nameof(PurchaseDate), () => LibraryBook.DateAdded }, + { nameof(ProductRating), () => Book.Rating.FirstScore }, + { nameof(Authors), () => Authors }, + { nameof(Narrators), () => Narrators }, + { nameof(Description), () => Description }, + { nameof(Category), () => Category }, + { nameof(Misc), () => Misc }, + { nameof(DisplayTags), () => DisplayTags }, + { nameof(Liberate), () => Liberate.Item1 } + }; - /// - /// Create getters for all member values by name - /// - Dictionary> CreatePropertyValueDictionary() => new() - { - { nameof(Title), () => getSortName(Book.Title)}, - { nameof(Series),() => getSortName(Book.SeriesNames)}, - { nameof(Length), () => Book.LengthInMinutes}, - { nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore}, - { nameof(PurchaseDate), () => LibraryBook.DateAdded}, - { nameof(ProductRating), () => Book.Rating.FirstScore}, - { nameof(Authors), () => Authors}, - { nameof(Narrators), () => Narrators}, - { nameof(Description), () => Description}, - { nameof(Category), () => Category}, - { nameof(Misc), () => Misc}, - { nameof(DisplayTags), () => DisplayTags}, - { nameof(Liberate), () => Liberate.Item1} - }; + // Instantiate comparers for every exposed member object type. + private static readonly Dictionary _memberTypeComparers = new() + { + { typeof(string), new ObjectComparer() }, + { typeof(int), new ObjectComparer() }, + { typeof(float), new ObjectComparer() }, + { typeof(DateTime), new ObjectComparer() }, + { typeof(LiberatedState), new ObjectComparer() }, + }; - private static readonly string[] sortPrefixIgnores = { "the", "a", "an" }; - private static string getSortName(string unformattedName) + public virtual object GetMemberValue(string memberName) => _memberValues[memberName](); + public virtual IComparer GetMemberComparer(Type memberType) => _memberTypeComparers[memberType]; + + private static readonly string[] _sortPrefixIgnores = { "the", "a", "an" }; + private static string GetSortName(string unformattedName) { var sortName = unformattedName .Replace("|", "") @@ -164,98 +145,71 @@ namespace LibationWinForms .ToLowerInvariant() .Trim(); - if (sortPrefixIgnores.Any(prefix => sortName.StartsWith(prefix + " "))) + if (_sortPrefixIgnores.Any(prefix => sortName.StartsWith(prefix + " "))) sortName = sortName.Substring(sortName.IndexOf(" ") + 1).TrimStart(); return sortName; } - #endregion + #endregion #region Static library display functions - + public static (string mouseoverText, Bitmap buttonImage) GetLiberateDisplay(LiberatedState liberatedStatus, PdfState pdfStatus) { - string text; - Bitmap image; - - // get mouseover text + (string libState, string image_lib) = liberatedStatus switch { - var libState = liberatedStatus switch - { - LiberatedState.Liberated => "Liberated", - LiberatedState.PartialDownload => "File has been at least\r\npartially downloaded", - LiberatedState.NotDownloaded => "Book NOT downloaded", - _ => throw new Exception("Unexpected liberation state") - }; + LiberatedState.Liberated => ("Liberated", "green"), + LiberatedState.PartialDownload => ("File has been at least\r\npartially downloaded", "yellow"), + LiberatedState.NotDownloaded => ("Book NOT downloaded", "red"), + _ => throw new Exception("Unexpected liberation state") + }; - var pdfState = pdfStatus switch - { - PdfState.Downloaded => "\r\nPDF downloaded", - PdfState.NotDownloaded => "\r\nPDF NOT downloaded", - PdfState.NoPdf => "", - _ => throw new Exception("Unexpected PDF state") - }; - - text = libState + pdfState; - - if (liberatedStatus == LiberatedState.NotDownloaded || - liberatedStatus == LiberatedState.PartialDownload || - pdfStatus == PdfState.NotDownloaded) - text += "\r\nClick to complete"; - - } - - // get image + (string pdfState, string image_pdf) = pdfStatus switch { - var image_lib - = liberatedStatus == LiberatedState.NotDownloaded ? "red" - : liberatedStatus == LiberatedState.PartialDownload ? "yellow" - : liberatedStatus == LiberatedState.Liberated ? "green" - : throw new Exception("Unexpected liberation state"); - var image_pdf - = pdfStatus == PdfState.NoPdf ? "" - : pdfStatus == PdfState.NotDownloaded ? "_pdf_no" - : pdfStatus == PdfState.Downloaded ? "_pdf_yes" - : throw new Exception("Unexpected PDF state"); + PdfState.Downloaded => ("\r\nPDF downloaded", "_pdf_yes"), + PdfState.NotDownloaded => ("\r\nPDF NOT downloaded", "_pdf_no"), + PdfState.NoPdf => ("", ""), + _ => throw new Exception("Unexpected PDF state") + }; - image = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}"); - } + var mouseoverText = libState + pdfState; - return (text, image); + if (liberatedStatus == LiberatedState.NotDownloaded || + liberatedStatus == LiberatedState.PartialDownload || + pdfStatus == PdfState.NotDownloaded) + mouseoverText += "\r\nClick to complete"; + + var buttonImage = (Bitmap)Properties.Resources.ResourceManager.GetObject($"liberate_{image_lib}{image_pdf}"); + + return (mouseoverText, buttonImage); } /// /// This information should not change during lifetime, so call only once. /// private static string GetDescriptionDisplay(Book book) - { + { var doc = new HtmlAgilityPack.HtmlDocument(); doc.LoadHtml(book.Description); var noHtml = doc.DocumentNode.InnerText; - return - noHtml.Length < 63? - noHtml : + return + noHtml.Length < 63 ? + noHtml : noHtml.Substring(0, 60) + "..."; } /// /// This information should not change during lifetime, so call only once. + /// Maximum of 5 text rows will fit in 80-pixel row height. /// private static string GetMiscDisplay(LibraryBook libraryBook) { - // max 5 text rows - var details = new List(); - var locale - = string.IsNullOrWhiteSpace(libraryBook.Book.Locale) - ? "[unknown]" - : libraryBook.Book.Locale; - var acct - = string.IsNullOrWhiteSpace(libraryBook.Account) - ? "[unknown]" - : libraryBook.Account; + var locale = ValueOrDefault(libraryBook.Book.Locale, "[unknown]"); + var acct = ValueOrDefault(libraryBook.Account, "[unknown]"); + details.Add($"Account: {locale} - {acct}"); if (libraryBook.Book.HasPdf) @@ -274,11 +228,10 @@ namespace LibationWinForms return string.Join("\r\n", details); } - private static string GetStarString(Rating rating) - => (rating?.FirstScore > 0f) - ? rating?.ToStarString() - : ""; + //Maybe add to Dinah StringExtensions? + private static string ValueOrDefault(string value, string defaultValue) + => string.IsNullOrWhiteSpace(value) ? defaultValue : value; #endregion - } + } } diff --git a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs index 63d64257..e57e6897 100644 --- a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs +++ b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs @@ -21,7 +21,7 @@ namespace LibationWinForms { (string mouseoverText, Bitmap buttonImage) = GridEntry.GetLiberateDisplay(liberatedState, pdfState); - DrawImage(graphics, buttonImage, cellBounds); + DrawButtonImage(graphics, buttonImage, cellBounds); ToolTipText = mouseoverText; } diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 292d6a17..0f826435 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -137,9 +137,7 @@ namespace LibationWinForms // .ThenBy(ge => ge.Title) .ToList(); - // // BIND - // gridEntryBindingSource.DataSource = new SortableBindingList2(orderedGridEntries); // FILTER @@ -192,7 +190,7 @@ namespace LibationWinForms #endregion - #region DataGridView Macros + #region DataGridView Macro private int getRowIndex(Func func) => _dataGridView.GetRowIdOfBoundItem(func); private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs index 8cc76d7b..838dda29 100644 --- a/LibationWinForms/SortableBindingList2[T].cs +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -2,29 +2,23 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace LibationWinForms { class SortableBindingList2 : BindingList where T : IObjectMemberComparable { - private ObjectMemberComparer Comparer = new(); - private bool isSorted; private ListSortDirection listSortDirection; private PropertyDescriptor propertyDescriptor; public SortableBindingList2() : base(new List()) { } - public SortableBindingList2(IEnumerable enumeration) : base(new List(enumeration)) { } + private ObjectMemberComparer Comparer { get; } = new(); protected override bool SupportsSortingCore => true; - + protected override bool SupportsSearchingCore => true; protected override bool IsSortedCore => isSorted; - protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; - protected override ListSortDirection SortDirectionCore => listSortDirection; protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) @@ -34,8 +28,8 @@ namespace LibationWinForms Comparer.PropertyName = property.Name; Comparer.Direction = direction; - //Array.Sort and Liat.Sort are unstable sorts. OrderBy is stable. - var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToArray(); + //Array.Sort() and List.Sort() are unstable sorts. OrderBy is stable. + var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList(); itemsList.Clear(); itemsList.AddRange(sortedItems); @@ -55,5 +49,27 @@ namespace LibationWinForms OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); } + //NOTE: Libation does not currently use BindingSource.Find anywhere, + //so this override may be removed (along with SupportsSearchingCore) + protected override int FindCore(PropertyDescriptor property, object key) + { + int count = Count; + + System.Collections.IComparer valueComparer = null; + + for (int i = 0; i < count; ++i) + { + T element = this[i]; + var elemValue = element.GetMemberValue(property.Name); + valueComparer ??= element.GetMemberComparer(elemValue.GetType()); + + if (valueComparer.Compare(elemValue, key) == 0) + { + return i; + } + } + + return -1; + } } } From ef35c2aee962dabb537c178426ead145617f004b Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 16:10:53 -0600 Subject: [PATCH 21/64] Code Cleanup --- LibationLauncher/LibationLauncher.csproj | 2 +- .../AsyncNotifyPropertyChanged.cs | 54 +- .../BookLiberation/AutomatedBackupsForm.cs | 44 +- .../BookLiberation/DecryptForm.cs | 82 +- .../BookLiberation/DownloadForm.cs | 92 +- .../ProcessorAutomationController.cs | 1036 +++++++++-------- LibationWinForms/Dialogs/AccountsDialog.cs | 20 +- LibationWinForms/Dialogs/AccountsDialog.resx | 59 - LibationWinForms/Dialogs/BookDetailsDialog.cs | 36 +- .../Dialogs/BookDetailsDialog.resx | 59 - .../Dialogs/DirectoryOrCustomSelectControl.cs | 8 +- .../DirectoryOrCustomSelectControl.resx | 3 +- .../Dialogs/DirectorySelectControl.cs | 8 +- .../Dialogs/DirectorySelectControl.resx | 3 +- LibationWinForms/Dialogs/EditQuickFilters.cs | 161 ++- .../Dialogs/EditQuickFilters.resx | 59 - .../Dialogs/IndexLibraryDialog.cs | 6 +- .../Dialogs/IndexLibraryDialog.resx | 59 - .../Dialogs/LibationFilesDialog.cs | 4 +- .../Dialogs/Login/AudibleLoginDialog.cs | 6 +- LibationWinForms/Dialogs/Login/MfaDialog.cs | 14 +- .../Dialogs/Login/WinformResponder.cs | 3 +- .../Dialogs/MessageBoxAlertAdminDialog.cs | 4 +- LibationWinForms/Dialogs/RemoveBooksDialog.cs | 44 +- .../Dialogs/RemoveBooksDialog.resx | 6 +- .../Dialogs/ScanAccountsDialog.cs | 11 +- .../Dialogs/ScanAccountsDialog.resx | 59 - .../Dialogs/SearchSyntaxDialog.cs | 24 +- .../Dialogs/SearchSyntaxDialog.resx | 59 - LibationWinForms/Dialogs/SettingsDialog.cs | 16 +- LibationWinForms/Dialogs/SettingsDialog.resx | 3 +- LibationWinForms/Dialogs/SetupDialog.resx | 3 +- LibationWinForms/Form1.cs | 772 ++++++------ LibationWinForms/GridEntry.cs | 10 +- LibationWinForms/IObjectMemberComparable.cs | 2 +- LibationWinForms/MessageBoxAlertAdmin.cs | 8 +- .../MessageBoxWarnIfVerboseLogging.cs | 5 +- LibationWinForms/ObjectComparer[T].cs | 2 +- LibationWinForms/ObjectMemberComparer[T].cs | 24 +- LibationWinForms/ProductsGrid.cs | 290 ++--- LibationWinForms/SortableBindingList2[T].cs | 109 +- 41 files changed, 1451 insertions(+), 1818 deletions(-) diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 33159e83..7cda4a7d 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.120 + 5.4.9.140 diff --git a/LibationWinForms/AsyncNotifyPropertyChanged.cs b/LibationWinForms/AsyncNotifyPropertyChanged.cs index 9de0911d..1c8c8775 100644 --- a/LibationWinForms/AsyncNotifyPropertyChanged.cs +++ b/LibationWinForms/AsyncNotifyPropertyChanged.cs @@ -4,34 +4,34 @@ using System.Threading; namespace LibationWinForms { - public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged - { - public event PropertyChangedEventHandler PropertyChanged; - private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; - private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; - private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; + public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; + private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; + private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; - protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") - { - var propertyChangedArgs = new PropertyChangedEventArgs(propertyName); + protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") + { + var propertyChangedArgs = new PropertyChangedEventArgs(propertyName); - if (InvokeRequired) - { - SyncContext.Post( - PostPropertyChangedCallback, - new AsyncCompletedEventArgs(null, false, propertyChangedArgs)); - } - else - { - OnPropertyChanged(propertyChangedArgs); - } - } - private void PostPropertyChangedCallback(object asyncArgs) - { - var e = asyncArgs as AsyncCompletedEventArgs; + if (InvokeRequired) + { + SyncContext.Post( + PostPropertyChangedCallback, + new AsyncCompletedEventArgs(null, false, propertyChangedArgs)); + } + else + { + OnPropertyChanged(propertyChangedArgs); + } + } + private void PostPropertyChangedCallback(object asyncArgs) + { + var e = asyncArgs as AsyncCompletedEventArgs; - OnPropertyChanged(e.UserState as PropertyChangedEventArgs); - } - private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); - } + OnPropertyChanged(e.UserState as PropertyChangedEventArgs); + } + private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); + } } diff --git a/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs b/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs index 95261858..85842e28 100644 --- a/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs +++ b/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs @@ -1,34 +1,34 @@ -using System; +using Dinah.Core.Windows.Forms; +using System; using System.Windows.Forms; -using Dinah.Core.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class AutomatedBackupsForm : Form - { - public bool KeepGoingChecked => keepGoingCb.Checked; + public partial class AutomatedBackupsForm : Form + { + public bool KeepGoingChecked => keepGoingCb.Checked; - public bool KeepGoing => keepGoingCb.Enabled && keepGoingCb.Checked; + public bool KeepGoing => keepGoingCb.Enabled && keepGoingCb.Checked; - public AutomatedBackupsForm() - { - InitializeComponent(); - } + public AutomatedBackupsForm() + { + InitializeComponent(); + } - public void WriteLine(string text) - { - if (!IsDisposed) - logTb.UIThread(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); - } + public void WriteLine(string text) + { + if (!IsDisposed) + logTb.UIThread(() => logTb.AppendText($"{DateTime.Now} {text}{Environment.NewLine}")); + } public void FinalizeUI() - { - keepGoingCb.Enabled = false; + { + keepGoingCb.Enabled = false; - if (!IsDisposed) - logTb.AppendText(""); - } + if (!IsDisposed) + logTb.AppendText(""); + } - private void AutomatedBackupsForm_FormClosing(object sender, FormClosingEventArgs e) => keepGoingCb.Checked = false; - } + private void AutomatedBackupsForm_FormClosing(object sender, FormClosingEventArgs e) => keepGoingCb.Checked = false; + } } diff --git a/LibationWinForms/BookLiberation/DecryptForm.cs b/LibationWinForms/BookLiberation/DecryptForm.cs index e6919b16..baaca4c7 100644 --- a/LibationWinForms/BookLiberation/DecryptForm.cs +++ b/LibationWinForms/BookLiberation/DecryptForm.cs @@ -1,54 +1,54 @@ -using System; +using Dinah.Core.Windows.Forms; +using System; using System.Windows.Forms; -using Dinah.Core.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class DecryptForm : Form - { + public partial class DecryptForm : Form + { public DecryptForm() => InitializeComponent(); - // book info - private string title; - private string authorNames; - private string narratorNames; + // book info + private string title; + private string authorNames; + private string narratorNames; - public void SetTitle(string actionName, string title) - { - this.UIThread(() => this.Text = actionName + " " + title); - this.title = title; - updateBookInfo(); - } - public void SetAuthorNames(string authorNames) - { - this.authorNames = authorNames; - updateBookInfo(); - } - public void SetNarratorNames(string narratorNames) - { - this.narratorNames = narratorNames; - updateBookInfo(); - } + public void SetTitle(string actionName, string title) + { + this.UIThread(() => this.Text = actionName + " " + title); + this.title = title; + updateBookInfo(); + } + public void SetAuthorNames(string authorNames) + { + this.authorNames = authorNames; + updateBookInfo(); + } + public void SetNarratorNames(string narratorNames) + { + this.narratorNames = narratorNames; + updateBookInfo(); + } - // thread-safe UI updates - private void updateBookInfo() - => bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"); + // thread-safe UI updates + private void updateBookInfo() + => bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"); - public void SetCoverImage(System.Drawing.Image coverImage) - => pictureBox1.UIThread(() => pictureBox1.Image = coverImage); + public void SetCoverImage(System.Drawing.Image coverImage) + => pictureBox1.UIThread(() => pictureBox1.Image = coverImage); - public void UpdateProgress(int percentage) - { - if (percentage == 0) - updateRemainingTime(0); - else - progressBar1.UIThread(() => progressBar1.Value = percentage); - } + public void UpdateProgress(int percentage) + { + if (percentage == 0) + updateRemainingTime(0); + else + progressBar1.UIThread(() => progressBar1.Value = percentage); + } - public void UpdateRemainingTime(TimeSpan remaining) - => updateRemainingTime((int)remaining.TotalSeconds); + public void UpdateRemainingTime(TimeSpan remaining) + => updateRemainingTime((int)remaining.TotalSeconds); - private void updateRemainingTime(int remaining) - => remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec"); - } + private void updateRemainingTime(int remaining) + => remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec"); + } } diff --git a/LibationWinForms/BookLiberation/DownloadForm.cs b/LibationWinForms/BookLiberation/DownloadForm.cs index ffdf28e0..e8bcde73 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.cs +++ b/LibationWinForms/BookLiberation/DownloadForm.cs @@ -1,59 +1,59 @@ -using System; +using Dinah.Core.Windows.Forms; +using System; using System.Windows.Forms; -using Dinah.Core.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class DownloadForm : Form - { - public DownloadForm() - { - InitializeComponent(); + public partial class DownloadForm : Form + { + public DownloadForm() + { + InitializeComponent(); - progressLbl.Text = ""; - filenameLbl.Text = ""; - } + progressLbl.Text = ""; + filenameLbl.Text = ""; + } - // thread-safe UI updates - public void UpdateFilename(string title) => filenameLbl.UIThread(() => filenameLbl.Text = title); + // thread-safe UI updates + public void UpdateFilename(string title) => filenameLbl.UIThread(() => filenameLbl.Text = title); - public void DownloadProgressChanged(long BytesReceived, long? TotalBytesToReceive) - { - // this won't happen with download file. it will happen with download string - if (!TotalBytesToReceive.HasValue || TotalBytesToReceive.Value <= 0) - return; + public void DownloadProgressChanged(long BytesReceived, long? TotalBytesToReceive) + { + // this won't happen with download file. it will happen with download string + if (!TotalBytesToReceive.HasValue || TotalBytesToReceive.Value <= 0) + return; - progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive.Value:#,##0}"); + progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive.Value:#,##0}"); - var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.Value.ToString()) * 100.0; - var i = int.Parse(Math.Truncate(d).ToString()); - progressBar1.UIThread(() => progressBar1.Value = i); + var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.Value.ToString()) * 100.0; + var i = int.Parse(Math.Truncate(d).ToString()); + progressBar1.UIThread(() => progressBar1.Value = i); - lastDownloadProgress = DateTime.Now; - } + lastDownloadProgress = DateTime.Now; + } - #region timer - private Timer timer { get; } = new Timer { Interval = 1000 }; - private void DownloadForm_Load(object sender, EventArgs e) - { - timer.Tick += new EventHandler(timer_Tick); - timer.Start(); - } - private DateTime lastDownloadProgress = DateTime.Now; - 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); - if (lastUpdateLbl.Visible) - { - var diff = DateTime.Now - lastDownloadProgress; - var min = (int)diff.TotalMinutes; - var minText = min > 0 ? $"{min}min " : ""; + #region timer + private Timer timer { get; } = new Timer { Interval = 1000 }; + private void DownloadForm_Load(object sender, EventArgs e) + { + timer.Tick += new EventHandler(timer_Tick); + timer.Start(); + } + private DateTime lastDownloadProgress = DateTime.Now; + 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); + 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"); - } - } - private void DownloadForm_FormClosing(object sender, FormClosingEventArgs e) => timer.Stop(); - #endregion - } + lastUpdateLbl.UIThread(() => lastUpdateLbl.Text = $"Frozen? Last download activity: {minText}{diff.Seconds}sec ago"); + } + } + private void DownloadForm_FormClosing(object sender, FormClosingEventArgs e) => timer.Stop(); + #endregion + } } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index f378bae7..7496d81a 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -1,505 +1,506 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; -using DataLayer; +using DataLayer; using Dinah.Core; using Dinah.Core.ErrorHandling; using Dinah.Core.Windows.Forms; using FileLiberator; +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; namespace LibationWinForms.BookLiberation { - // decouple serilog and form. include convenience factory method - public class LogMe - { - public event EventHandler LogInfo; - public event EventHandler LogErrorString; - public event EventHandler<(Exception, string)> LogError; + // decouple serilog and form. include convenience factory method + public class LogMe + { + public event EventHandler LogInfo; + public event EventHandler LogErrorString; + public event EventHandler<(Exception, string)> LogError; - private LogMe() - { - LogInfo += (_, text) => Serilog.Log.Logger.Information($"Automated backup: {text}"); - LogErrorString += (_, text) => Serilog.Log.Logger.Error(text); - LogError += (_, tuple) => Serilog.Log.Logger.Error(tuple.Item1, tuple.Item2 ?? "Automated backup: error"); - } + private LogMe() + { + LogInfo += (_, text) => Serilog.Log.Logger.Information($"Automated backup: {text}"); + LogErrorString += (_, text) => Serilog.Log.Logger.Error(text); + LogError += (_, tuple) => Serilog.Log.Logger.Error(tuple.Item1, tuple.Item2 ?? "Automated backup: error"); + } - public static LogMe RegisterForm(AutomatedBackupsForm form = null) - { - var logMe = new LogMe(); + public static LogMe RegisterForm(AutomatedBackupsForm form = null) + { + var logMe = new LogMe(); - if (form is null) - return logMe; + if (form is null) + return logMe; - logMe.LogInfo += (_, text) => form?.WriteLine(text); + logMe.LogInfo += (_, text) => form?.WriteLine(text); - logMe.LogErrorString += (_, text) => form?.WriteLine(text); - - logMe.LogError += (_, tuple) => - { - form?.WriteLine(tuple.Item2 ?? "Automated backup: error"); - form?.WriteLine("ERROR: " + tuple.Item1.Message); - }; - - return logMe; - } - - public void Info(string text) => LogInfo?.Invoke(this, text); - public void Error(string text) => LogErrorString?.Invoke(this, text); - public void Error(Exception ex, string text = null) => LogError?.Invoke(this, (ex, text)); - } - - public static class ProcessorAutomationController - { - public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler completedAction = null) - { - Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId }); - - var backupBook = getWiredUpBackupBook(completedAction); - - (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook); - - // continue even if libraryBook is null. we'll display even that in the processing box - await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync(); - - unsubscribeEvents(); - } - - public static async Task BackupAllBooksAsync(EventHandler completedAction = null) - { - Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync)); - - var backupBook = getWiredUpBackupBook(completedAction); - var automatedBackupsForm = new AutomatedBackupsForm(); - - (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook, automatedBackupsForm); - - await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync(); - - unsubscribeEvents(); - } - - public static async Task ConvertAllBooksAsync() - { - Serilog.Log.Logger.Information("Begin " + nameof(ConvertAllBooksAsync)); - - var convertBook = new ConvertToMp3(); - convertBook.Begin += (_, l) => wireUpEvents(convertBook, l, "Converting"); - - var automatedBackupsForm = new AutomatedBackupsForm(); - - var logMe = LogMe.RegisterForm(automatedBackupsForm); - - void statusUpdate(object _, string str) => logMe.Info("- " + str); - void convertBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Begin: {libraryBook.Book}"); - void convertBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - convertBook.Begin += convertBookBegin; - convertBook.StatusUpdate += statusUpdate; - convertBook.Completed += convertBookCompleted; - - await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); - - convertBook.Begin -= convertBookBegin; - convertBook.StatusUpdate -= statusUpdate; - convertBook.Completed -= convertBookCompleted; - } - - private static BackupBook getWiredUpBackupBook(EventHandler completedAction) - { - var backupBook = new BackupBook(); - - backupBook.DownloadDecryptBook.Begin += (_, l) => wireUpEvents(backupBook.DownloadDecryptBook, l); - backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf); - - if (completedAction != null) - { - backupBook.DownloadDecryptBook.Completed += completedAction; - backupBook.DownloadPdf.Completed += completedAction; - } - - return backupBook; - } - - private static (Action unsubscribeEvents, LogMe) attachToBackupsForm(BackupBook backupBook, AutomatedBackupsForm automatedBackupsForm = null) - { - #region create logger - var logMe = LogMe.RegisterForm(automatedBackupsForm); - #endregion - - #region define how model actions will affect form behavior - void statusUpdate(object _, string str) => logMe.Info("- " + str); - void decryptBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Begin: {libraryBook.Book}"); - // extra line after book is completely finished - void decryptBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - void downloadPdfBegin(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Begin: {libraryBook.Book}"); - // extra line after book is completely finished - void downloadPdfCompleted(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - #endregion - - #region subscribe new form to model's events - backupBook.DownloadDecryptBook.Begin += decryptBookBegin; - backupBook.DownloadDecryptBook.StatusUpdate += statusUpdate; - backupBook.DownloadDecryptBook.Completed += decryptBookCompleted; - backupBook.DownloadPdf.Begin += downloadPdfBegin; - backupBook.DownloadPdf.StatusUpdate += statusUpdate; - backupBook.DownloadPdf.Completed += downloadPdfCompleted; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - Action unsubscribe = () => - { - backupBook.DownloadDecryptBook.Begin -= decryptBookBegin; - backupBook.DownloadDecryptBook.StatusUpdate -= statusUpdate; - backupBook.DownloadDecryptBook.Completed -= decryptBookCompleted; - backupBook.DownloadPdf.Begin -= downloadPdfBegin; - backupBook.DownloadPdf.StatusUpdate -= statusUpdate; - backupBook.DownloadPdf.Completed -= downloadPdfCompleted; - }; - #endregion - - return (unsubscribe, logMe); - } - - public static async Task BackupAllPdfsAsync(EventHandler completedAction = null) - { - Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); - - var downloadPdf = getWiredUpDownloadPdf(completedAction); - - (AutomatedBackupsForm automatedBackupsForm, LogMe logMe) = attachToBackupsForm(downloadPdf); - await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); - } - - private static DownloadPdf getWiredUpDownloadPdf(EventHandler completedAction) - { - var downloadPdf = new DownloadPdf(); - - downloadPdf.Begin += (_, __) => wireUpEvents(downloadPdf); - - if (completedAction != null) - downloadPdf.Completed += completedAction; - - return downloadPdf; - } - - public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) - { - var downloadDialog = new DownloadForm(); - downloadDialog.UpdateFilename(destination); - downloadDialog.Show(); - - new System.Threading.Thread(() => - { - var downloadFile = new DownloadFile(); - - downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.UIThread(() => - downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive) - ); - downloadFile.DownloadCompleted += (_, __) => downloadDialog.UIThread(() => - { - downloadDialog.Close(); - if (showDownloadCompletedDialog) - MessageBox.Show("File downloaded"); - }); - - downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult(); - }) - { IsBackground = true } - .Start(); - } - - // subscribed to Begin event because a new form should be created+processed+closed on each iteration - private static void wireUpEvents(IDownloadableProcessable downloadable) - { - #region create form - var downloadDialog = new DownloadForm(); - #endregion - - // extra complexity for wiring up download form: - // case 1: download is needed - // dialog created. subscribe to events - // downloadable.DownloadBegin fires. shows dialog - // downloadable.DownloadCompleted fires. closes dialog. which fires FormClosing, FormClosed, Disposed - // Disposed unsubscribe from events - // case 2: download is not needed - // dialog created. subscribe to events - // dialog is never shown nor closed - // downloadable.Completed fires. disposes dialog and unsubscribes from events - - #region define how model actions will affect form behavior - void downloadBegin(object _, string str) - { - downloadDialog.UpdateFilename(str); - downloadDialog.Show(); - } - - // close form on DOWNLOAD completed, not final Completed. Else for BackupBook this form won't close until DECRYPT is also complete - void fileDownloadCompleted(object _, string __) => downloadDialog.Close(); - - void downloadProgressChanged(object _, Dinah.Core.Net.Http.DownloadProgress progress) - => downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive); - - void unsubscribe(object _ = null, EventArgs __ = null) - { - downloadable.DownloadBegin -= downloadBegin; - downloadable.DownloadCompleted -= fileDownloadCompleted; - downloadable.DownloadProgressChanged -= downloadProgressChanged; - downloadable.Completed -= dialogDispose; - } - - // unless we dispose, if the form is created but un-used/never-shown then weird UI stuff can happen - // also, since event unsubscribe occurs on FormClosing and an unused form is never closed, then the events will never be unsubscribed - void dialogDispose(object _, object __) - { - if (!downloadDialog.IsDisposed) - downloadDialog.Dispose(); - } - #endregion - - #region subscribe new form to model's events - downloadable.DownloadBegin += downloadBegin; - downloadable.DownloadCompleted += fileDownloadCompleted; - downloadable.DownloadProgressChanged += downloadProgressChanged; - downloadable.Completed += dialogDispose; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - // FormClosing is more UI safe but won't fire unless the form is shown and closed - // if form was shown, Disposed will fire for FormClosing, FormClosed, and Disposed - // if not shown, it will still fire for Disposed - downloadDialog.Disposed += unsubscribe; - #endregion - } - - // subscribed to Begin event because a new form should be created+processed+closed on each iteration - private static void wireUpEvents(IDecryptable decryptBook, LibraryBook libraryBook, string actionName = "Decrypting") - { - #region create form - var decryptDialog = new DecryptForm(); - #endregion - - #region Set initially displayed book properties from library info. - decryptDialog.SetTitle(actionName, libraryBook.Book.Title); - decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors)); - decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames)); - decryptDialog.SetCoverImage( - WindowsDesktopUtilities.WinAudibleImageServer.GetImage( - libraryBook.Book.PictureId, - FileManager.PictureSize._80x80 - )); - #endregion - - #region define how model actions will affect form behavior - void decryptBegin(object _, string __) => decryptDialog.Show(); - - void titleDiscovered(object _, string title) => decryptDialog.SetTitle(actionName, title); - void authorsDiscovered(object _, string authors) => decryptDialog.SetAuthorNames(authors); - void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators); - void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(Dinah.Core.Drawing.ImageReader.ToImage(coverBytes)); - void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage); - void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining); - void decryptCompleted(object _, string __) => decryptDialog.Close(); - - void requestCoverArt(object _, Action setCoverArtDelegate) - { - var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); - (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); - - if (isDefault) - { - void pictureCached(object _, string pictureId) - { - if (pictureId == libraryBook.Book.PictureId) - { - FileManager.PictureStorage.PictureCached -= pictureCached; - - var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); - (_, picture) = FileManager.PictureStorage.GetPicture(picDef); - - setCoverArtDelegate(picture); - } - }; - FileManager.PictureStorage.PictureCached += pictureCached; - } - else - setCoverArtDelegate(picture); - } - #endregion - - #region subscribe new form to model's events - decryptBook.DecryptBegin += decryptBegin; - - decryptBook.TitleDiscovered += titleDiscovered; - decryptBook.AuthorsDiscovered += authorsDiscovered; - decryptBook.NarratorsDiscovered += narratorsDiscovered; - decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered; - decryptBook.UpdateProgress += updateProgress; - decryptBook.UpdateRemainingTime += updateRemainingTime; - decryptBook.RequestCoverArt += requestCoverArt; - - decryptBook.DecryptCompleted += decryptCompleted; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - decryptDialog.FormClosing += (_, __) => - { - decryptBook.DecryptBegin -= decryptBegin; - - decryptBook.TitleDiscovered -= titleDiscovered; - decryptBook.AuthorsDiscovered -= authorsDiscovered; - decryptBook.NarratorsDiscovered -= narratorsDiscovered; - decryptBook.CoverImageFilepathDiscovered -= coverImageFilepathDiscovered; - decryptBook.UpdateProgress -= updateProgress; - decryptBook.UpdateRemainingTime -= updateRemainingTime; - decryptBook.RequestCoverArt -= requestCoverArt; - - decryptBook.DecryptCompleted -= decryptCompleted; - decryptBook.Cancel(); - }; - #endregion - } - - private static (AutomatedBackupsForm, LogMe) attachToBackupsForm(IDownloadableProcessable downloadable) - { - #region create form and logger - var automatedBackupsForm = new AutomatedBackupsForm(); - var logMe = LogMe.RegisterForm(automatedBackupsForm); - #endregion - - #region define how model actions will affect form behavior - void begin(object _, LibraryBook libraryBook) => logMe.Info($"Begin: {libraryBook.Book}"); - void statusUpdate(object _, string str) => logMe.Info("- " + str); - // extra line after book is completely finished - void completed(object _, LibraryBook libraryBook) => logMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); - #endregion - - #region subscribe new form to model's events - downloadable.Begin += begin; - downloadable.StatusUpdate += statusUpdate; - downloadable.Completed += completed; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - automatedBackupsForm.FormClosing += (_, __) => - { - downloadable.Begin -= begin; - downloadable.StatusUpdate -= statusUpdate; - downloadable.Completed -= completed; - }; - #endregion - - return (automatedBackupsForm, logMe); - } - } - - abstract class BackupRunner - { - protected LogMe LogMe { get; } - protected IProcessable Processable { get; } - protected AutomatedBackupsForm AutomatedBackupsForm { get; } - - protected BackupRunner(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm = null) - { - LogMe = logMe; - Processable = processable; - AutomatedBackupsForm = automatedBackupsForm; - } - - protected abstract Task RunAsync(); - - protected abstract string SkipDialogText { get; } - protected abstract MessageBoxButtons SkipDialogButtons { get; } - protected abstract DialogResult CreateSkipFileResult { get; } - - public async Task RunBackupAsync() - { - AutomatedBackupsForm?.Show(); - - try - { - await RunAsync(); - } - catch (Exception ex) - { - LogMe.Error(ex); - } - - AutomatedBackupsForm?.FinalizeUI(); - LogMe.Info("DONE"); - } - - protected async Task ProcessOneAsync(Func> func, LibraryBook libraryBook) - { - string logMessage; - - try - { - var statusHandler = await func(libraryBook); - - if (statusHandler.IsSuccess) - return true; - - foreach (var errorMessage in statusHandler.Errors) - LogMe.Error(errorMessage); - - logMessage = statusHandler.Errors.Aggregate((a, b) => $"{a}\r\n{b}"); - } - catch (Exception ex) - { - LogMe.Error(ex); - - logMessage = ex.Message + "\r\n|\r\n" + ex.StackTrace; - } - - LogMe.Error("ERROR. All books have not been processed. Most recent book: processing failed"); - - string details; - try - { - static string trunc(string str) - => string.IsNullOrWhiteSpace(str) ? "[empty]" - : (str.Length > 50) ? $"{str.Truncate(47)}..." - : str; - - details = + logMe.LogErrorString += (_, text) => form?.WriteLine(text); + + logMe.LogError += (_, tuple) => + { + form?.WriteLine(tuple.Item2 ?? "Automated backup: error"); + form?.WriteLine("ERROR: " + tuple.Item1.Message); + }; + + return logMe; + } + + public void Info(string text) => LogInfo?.Invoke(this, text); + public void Error(string text) => LogErrorString?.Invoke(this, text); + public void Error(Exception ex, string text = null) => LogError?.Invoke(this, (ex, text)); + } + + public static class ProcessorAutomationController + { + public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler completedAction = null) + { + Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId }); + + var backupBook = getWiredUpBackupBook(completedAction); + + (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook); + + // continue even if libraryBook is null. we'll display even that in the processing box + await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync(); + + unsubscribeEvents(); + } + + public static async Task BackupAllBooksAsync(EventHandler completedAction = null) + { + Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync)); + + var backupBook = getWiredUpBackupBook(completedAction); + var automatedBackupsForm = new AutomatedBackupsForm(); + + (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook, automatedBackupsForm); + + await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync(); + + unsubscribeEvents(); + } + + public static async Task ConvertAllBooksAsync() + { + Serilog.Log.Logger.Information("Begin " + nameof(ConvertAllBooksAsync)); + + var convertBook = new ConvertToMp3(); + convertBook.Begin += (_, l) => wireUpEvents(convertBook, l, "Converting"); + + var automatedBackupsForm = new AutomatedBackupsForm(); + + var logMe = LogMe.RegisterForm(automatedBackupsForm); + + void statusUpdate(object _, string str) => logMe.Info("- " + str); + void convertBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Begin: {libraryBook.Book}"); + void convertBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + convertBook.Begin += convertBookBegin; + convertBook.StatusUpdate += statusUpdate; + convertBook.Completed += convertBookCompleted; + + await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); + + convertBook.Begin -= convertBookBegin; + convertBook.StatusUpdate -= statusUpdate; + convertBook.Completed -= convertBookCompleted; + } + + private static BackupBook getWiredUpBackupBook(EventHandler completedAction) + { + var backupBook = new BackupBook(); + + backupBook.DownloadDecryptBook.Begin += (_, l) => wireUpEvents(backupBook.DownloadDecryptBook, l); + backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf); + + if (completedAction != null) + { + backupBook.DownloadDecryptBook.Completed += completedAction; + backupBook.DownloadPdf.Completed += completedAction; + } + + return backupBook; + } + + private static (Action unsubscribeEvents, LogMe) attachToBackupsForm(BackupBook backupBook, AutomatedBackupsForm automatedBackupsForm = null) + { + #region create logger + var logMe = LogMe.RegisterForm(automatedBackupsForm); + #endregion + + #region define how model actions will affect form behavior + void statusUpdate(object _, string str) => logMe.Info("- " + str); + void decryptBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Begin: {libraryBook.Book}"); + // extra line after book is completely finished + void decryptBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + void downloadPdfBegin(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Begin: {libraryBook.Book}"); + // extra line after book is completely finished + void downloadPdfCompleted(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + #endregion + + #region subscribe new form to model's events + backupBook.DownloadDecryptBook.Begin += decryptBookBegin; + backupBook.DownloadDecryptBook.StatusUpdate += statusUpdate; + backupBook.DownloadDecryptBook.Completed += decryptBookCompleted; + backupBook.DownloadPdf.Begin += downloadPdfBegin; + backupBook.DownloadPdf.StatusUpdate += statusUpdate; + backupBook.DownloadPdf.Completed += downloadPdfCompleted; + #endregion + + #region when form closes, unsubscribe from model's events + // unsubscribe so disposed forms aren't still trying to receive notifications + Action unsubscribe = () => + { + backupBook.DownloadDecryptBook.Begin -= decryptBookBegin; + backupBook.DownloadDecryptBook.StatusUpdate -= statusUpdate; + backupBook.DownloadDecryptBook.Completed -= decryptBookCompleted; + backupBook.DownloadPdf.Begin -= downloadPdfBegin; + backupBook.DownloadPdf.StatusUpdate -= statusUpdate; + backupBook.DownloadPdf.Completed -= downloadPdfCompleted; + }; + #endregion + + return (unsubscribe, logMe); + } + + public static async Task BackupAllPdfsAsync(EventHandler completedAction = null) + { + Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); + + var downloadPdf = getWiredUpDownloadPdf(completedAction); + + (AutomatedBackupsForm automatedBackupsForm, LogMe logMe) = attachToBackupsForm(downloadPdf); + await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); + } + + private static DownloadPdf getWiredUpDownloadPdf(EventHandler completedAction) + { + var downloadPdf = new DownloadPdf(); + + downloadPdf.Begin += (_, __) => wireUpEvents(downloadPdf); + + if (completedAction != null) + downloadPdf.Completed += completedAction; + + return downloadPdf; + } + + public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) + { + var downloadDialog = new DownloadForm(); + downloadDialog.UpdateFilename(destination); + downloadDialog.Show(); + + new System.Threading.Thread(() => + { + var downloadFile = new DownloadFile(); + + downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.UIThread(() => + downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive) + ); + downloadFile.DownloadCompleted += (_, __) => downloadDialog.UIThread(() => + { + downloadDialog.Close(); + if (showDownloadCompletedDialog) + MessageBox.Show("File downloaded"); + }); + + downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult(); + }) + { IsBackground = true } + .Start(); + } + + // subscribed to Begin event because a new form should be created+processed+closed on each iteration + private static void wireUpEvents(IDownloadableProcessable downloadable) + { + #region create form + var downloadDialog = new DownloadForm(); + #endregion + + // extra complexity for wiring up download form: + // case 1: download is needed + // dialog created. subscribe to events + // downloadable.DownloadBegin fires. shows dialog + // downloadable.DownloadCompleted fires. closes dialog. which fires FormClosing, FormClosed, Disposed + // Disposed unsubscribe from events + // case 2: download is not needed + // dialog created. subscribe to events + // dialog is never shown nor closed + // downloadable.Completed fires. disposes dialog and unsubscribes from events + + #region define how model actions will affect form behavior + void downloadBegin(object _, string str) + { + downloadDialog.UpdateFilename(str); + downloadDialog.Show(); + } + + // close form on DOWNLOAD completed, not final Completed. Else for BackupBook this form won't close until DECRYPT is also complete + void fileDownloadCompleted(object _, string __) => downloadDialog.Close(); + + void downloadProgressChanged(object _, Dinah.Core.Net.Http.DownloadProgress progress) + => downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive); + + void unsubscribe(object _ = null, EventArgs __ = null) + { + downloadable.DownloadBegin -= downloadBegin; + downloadable.DownloadCompleted -= fileDownloadCompleted; + downloadable.DownloadProgressChanged -= downloadProgressChanged; + downloadable.Completed -= dialogDispose; + } + + // unless we dispose, if the form is created but un-used/never-shown then weird UI stuff can happen + // also, since event unsubscribe occurs on FormClosing and an unused form is never closed, then the events will never be unsubscribed + void dialogDispose(object _, object __) + { + if (!downloadDialog.IsDisposed) + downloadDialog.Dispose(); + } + #endregion + + #region subscribe new form to model's events + downloadable.DownloadBegin += downloadBegin; + downloadable.DownloadCompleted += fileDownloadCompleted; + downloadable.DownloadProgressChanged += downloadProgressChanged; + downloadable.Completed += dialogDispose; + #endregion + + #region when form closes, unsubscribe from model's events + // unsubscribe so disposed forms aren't still trying to receive notifications + // FormClosing is more UI safe but won't fire unless the form is shown and closed + // if form was shown, Disposed will fire for FormClosing, FormClosed, and Disposed + // if not shown, it will still fire for Disposed + downloadDialog.Disposed += unsubscribe; + #endregion + } + + // subscribed to Begin event because a new form should be created+processed+closed on each iteration + private static void wireUpEvents(IDecryptable decryptBook, LibraryBook libraryBook, string actionName = "Decrypting") + { + #region create form + var decryptDialog = new DecryptForm(); + #endregion + + #region Set initially displayed book properties from library info. + decryptDialog.SetTitle(actionName, libraryBook.Book.Title); + decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors)); + decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames)); + decryptDialog.SetCoverImage( + WindowsDesktopUtilities.WinAudibleImageServer.GetImage( + libraryBook.Book.PictureId, + FileManager.PictureSize._80x80 + )); + #endregion + + #region define how model actions will affect form behavior + void decryptBegin(object _, string __) => decryptDialog.Show(); + + void titleDiscovered(object _, string title) => decryptDialog.SetTitle(actionName, title); + void authorsDiscovered(object _, string authors) => decryptDialog.SetAuthorNames(authors); + void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators); + void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(Dinah.Core.Drawing.ImageReader.ToImage(coverBytes)); + void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage); + void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining); + void decryptCompleted(object _, string __) => decryptDialog.Close(); + + void requestCoverArt(object _, Action setCoverArtDelegate) + { + var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); + (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); + + if (isDefault) + { + void pictureCached(object _, string pictureId) + { + if (pictureId == libraryBook.Book.PictureId) + { + FileManager.PictureStorage.PictureCached -= pictureCached; + + var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); + (_, picture) = FileManager.PictureStorage.GetPicture(picDef); + + setCoverArtDelegate(picture); + } + }; + FileManager.PictureStorage.PictureCached += pictureCached; + } + else + setCoverArtDelegate(picture); + } + #endregion + + #region subscribe new form to model's events + decryptBook.DecryptBegin += decryptBegin; + + decryptBook.TitleDiscovered += titleDiscovered; + decryptBook.AuthorsDiscovered += authorsDiscovered; + decryptBook.NarratorsDiscovered += narratorsDiscovered; + decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered; + decryptBook.UpdateProgress += updateProgress; + decryptBook.UpdateRemainingTime += updateRemainingTime; + decryptBook.RequestCoverArt += requestCoverArt; + + decryptBook.DecryptCompleted += decryptCompleted; + #endregion + + #region when form closes, unsubscribe from model's events + // unsubscribe so disposed forms aren't still trying to receive notifications + decryptDialog.FormClosing += (_, __) => + { + decryptBook.DecryptBegin -= decryptBegin; + + decryptBook.TitleDiscovered -= titleDiscovered; + decryptBook.AuthorsDiscovered -= authorsDiscovered; + decryptBook.NarratorsDiscovered -= narratorsDiscovered; + decryptBook.CoverImageFilepathDiscovered -= coverImageFilepathDiscovered; + decryptBook.UpdateProgress -= updateProgress; + decryptBook.UpdateRemainingTime -= updateRemainingTime; + decryptBook.RequestCoverArt -= requestCoverArt; + + decryptBook.DecryptCompleted -= decryptCompleted; + decryptBook.Cancel(); + }; + #endregion + } + + private static (AutomatedBackupsForm, LogMe) attachToBackupsForm(IDownloadableProcessable downloadable) + { + #region create form and logger + var automatedBackupsForm = new AutomatedBackupsForm(); + var logMe = LogMe.RegisterForm(automatedBackupsForm); + #endregion + + #region define how model actions will affect form behavior + void begin(object _, LibraryBook libraryBook) => logMe.Info($"Begin: {libraryBook.Book}"); + void statusUpdate(object _, string str) => logMe.Info("- " + str); + // extra line after book is completely finished + void completed(object _, LibraryBook libraryBook) => logMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); + #endregion + + #region subscribe new form to model's events + downloadable.Begin += begin; + downloadable.StatusUpdate += statusUpdate; + downloadable.Completed += completed; + #endregion + + #region when form closes, unsubscribe from model's events + // unsubscribe so disposed forms aren't still trying to receive notifications + automatedBackupsForm.FormClosing += (_, __) => + { + downloadable.Begin -= begin; + downloadable.StatusUpdate -= statusUpdate; + downloadable.Completed -= completed; + }; + #endregion + + return (automatedBackupsForm, logMe); + } + } + + internal abstract class BackupRunner + { + protected LogMe LogMe { get; } + protected IProcessable Processable { get; } + protected AutomatedBackupsForm AutomatedBackupsForm { get; } + + protected BackupRunner(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm = null) + { + LogMe = logMe; + Processable = processable; + AutomatedBackupsForm = automatedBackupsForm; + } + + protected abstract Task RunAsync(); + + protected abstract string SkipDialogText { get; } + protected abstract MessageBoxButtons SkipDialogButtons { get; } + protected abstract DialogResult CreateSkipFileResult { get; } + + public async Task RunBackupAsync() + { + AutomatedBackupsForm?.Show(); + + try + { + await RunAsync(); + } + catch (Exception ex) + { + LogMe.Error(ex); + } + + AutomatedBackupsForm?.FinalizeUI(); + LogMe.Info("DONE"); + } + + protected async Task ProcessOneAsync(Func> func, LibraryBook libraryBook) + { + string logMessage; + + try + { + var statusHandler = await func(libraryBook); + + if (statusHandler.IsSuccess) + return true; + + foreach (var errorMessage in statusHandler.Errors) + LogMe.Error(errorMessage); + + logMessage = statusHandler.Errors.Aggregate((a, b) => $"{a}\r\n{b}"); + } + catch (Exception ex) + { + LogMe.Error(ex); + + logMessage = ex.Message + "\r\n|\r\n" + ex.StackTrace; + } + + LogMe.Error("ERROR. All books have not been processed. Most recent book: processing failed"); + + string details; + try + { + static string trunc(string str) + => string.IsNullOrWhiteSpace(str) ? "[empty]" + : (str.Length > 50) ? $"{str.Truncate(47)}..." + : str; + + details = $@" Title: {libraryBook.Book.Title} ID: {libraryBook.Book.AudibleProductId} Author: {trunc(libraryBook.Book.AuthorNames)} Narr: {trunc(libraryBook.Book.NarratorNames)}"; - } - catch - { - details = "[Error retrieving details]"; - } + } + catch + { + details = "[Error retrieving details]"; + } - var dialogResult = MessageBox.Show(string.Format(SkipDialogText, details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question); + var dialogResult = MessageBox.Show(string.Format(SkipDialogText, details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question); - if (dialogResult == DialogResult.Abort) - return false; + if (dialogResult == DialogResult.Abort) + return false; - if (dialogResult == CreateSkipFileResult) - { - ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error, null); - var path = FileManager.AudibleFileStorage.Audio.CreateSkipFile(libraryBook.Book.Title, libraryBook.Book.AudibleProductId, logMessage); - LogMe.Info($@" + if (dialogResult == CreateSkipFileResult) + { + ApplicationServices.LibraryCommands.UpdateBook(libraryBook, LiberatedStatus.Error, null); + var path = FileManager.AudibleFileStorage.Audio.CreateSkipFile(libraryBook.Book.Title, libraryBook.Book.AudibleProductId, logMessage); + LogMe.Info($@" Created new 'skip' file [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title} {path} ".Trim()); - } + } - return true; - } - } - class BackupSingle : BackupRunner - { - private LibraryBook _libraryBook { get; } + return true; + } + } - protected override string SkipDialogText => @" + internal class BackupSingle : BackupRunner + { + private LibraryBook _libraryBook { get; } + + protected override string SkipDialogText => @" An error occurred while trying to process this book. Skip this book permanently? {0} @@ -507,24 +508,25 @@ An error occurred while trying to process this book. Skip this book permanently? - Click NO to skip the book this time only. We'll try again later. ".Trim(); - protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.YesNo; - protected override DialogResult CreateSkipFileResult => DialogResult.Yes; + protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.YesNo; + protected override DialogResult CreateSkipFileResult => DialogResult.Yes; - public BackupSingle(LogMe logMe, IProcessable processable, LibraryBook libraryBook) - : base(logMe, processable) - { - _libraryBook = libraryBook; - } + public BackupSingle(LogMe logMe, IProcessable processable, LibraryBook libraryBook) + : base(logMe, processable) + { + _libraryBook = libraryBook; + } - protected override async Task RunAsync() - { - if (_libraryBook is not null) - await ProcessOneAsync(Processable.ProcessSingleAsync, _libraryBook); - } - } - class BackupLoop : BackupRunner - { - protected override string SkipDialogText => @" + protected override async Task RunAsync() + { + if (_libraryBook is not null) + await ProcessOneAsync(Processable.ProcessSingleAsync, _libraryBook); + } + } + + internal class BackupLoop : BackupRunner + { + protected override string SkipDialogText => @" An error occurred while trying to process this book. {0} @@ -534,33 +536,33 @@ An error occurred while trying to process this book. - IGNORE: Permanently ignore this book. Continue processing books. (Will not try this book again later.) ".Trim(); - protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; - protected override DialogResult CreateSkipFileResult => DialogResult.Ignore; + protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; + protected override DialogResult CreateSkipFileResult => DialogResult.Ignore; - public BackupLoop(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm) - : base(logMe, processable, automatedBackupsForm) { } + public BackupLoop(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm) + : base(logMe, processable, automatedBackupsForm) { } - 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()) - { - var keepGoing = await ProcessOneAsync(Processable.ProcessBookAsync_NoValidation, libraryBook); - if (!keepGoing) - return; + 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()) + { + var keepGoing = await ProcessOneAsync(Processable.ProcessBookAsync_NoValidation, libraryBook); + if (!keepGoing) + return; - if (AutomatedBackupsForm.IsDisposed) - break; + if (AutomatedBackupsForm.IsDisposed) + break; - if (!AutomatedBackupsForm.KeepGoing) - { - if (!AutomatedBackupsForm.KeepGoingChecked) - LogMe.Info("'Keep going' is unchecked"); - return; - } - } + if (!AutomatedBackupsForm.KeepGoing) + { + if (!AutomatedBackupsForm.KeepGoingChecked) + LogMe.Info("'Keep going' is unchecked"); + return; + } + } - LogMe.Info("Done. All books have been processed"); - } - } + LogMe.Info("Done. All books have been processed"); + } + } } diff --git a/LibationWinForms/Dialogs/AccountsDialog.cs b/LibationWinForms/Dialogs/AccountsDialog.cs index 0884bcfc..cfc5988e 100644 --- a/LibationWinForms/Dialogs/AccountsDialog.cs +++ b/LibationWinForms/Dialogs/AccountsDialog.cs @@ -1,21 +1,21 @@ -using System; +using AudibleApi; +using InternalUtilities; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; -using AudibleApi; -using InternalUtilities; namespace LibationWinForms.Dialogs { public partial class AccountsDialog : Form { - const string COL_Delete = nameof(DeleteAccount); - const string COL_LibraryScan = nameof(LibraryScan); - const string COL_AccountId = nameof(AccountId); - const string COL_AccountName = nameof(AccountName); - const string COL_Locale = nameof(Locale); + private const string COL_Delete = nameof(DeleteAccount); + private const string COL_LibraryScan = nameof(LibraryScan); + private const string COL_AccountId = nameof(AccountId); + private const string COL_AccountName = nameof(AccountName); + private const string COL_Locale = nameof(Locale); - Form1 _parent { get; } + private Form1 _parent { get; } public AccountsDialog(Form1 parent) { @@ -100,7 +100,7 @@ namespace LibationWinForms.Dialogs this.Close(); } - class AccountDto + private class AccountDto { public string AccountId { get; set; } public string AccountName { get; set; } diff --git a/LibationWinForms/Dialogs/AccountsDialog.resx b/LibationWinForms/Dialogs/AccountsDialog.resx index 18b23138..f1117452 100644 --- a/LibationWinForms/Dialogs/AccountsDialog.resx +++ b/LibationWinForms/Dialogs/AccountsDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/BookDetailsDialog.cs b/LibationWinForms/Dialogs/BookDetailsDialog.cs index 27c5ab21..9a415200 100644 --- a/LibationWinForms/Dialogs/BookDetailsDialog.cs +++ b/LibationWinForms/Dialogs/BookDetailsDialog.cs @@ -3,25 +3,25 @@ using System.Windows.Forms; namespace LibationWinForms.Dialogs { - public partial class BookDetailsDialog : Form - { - public string NewTags { get; private set; } + public partial class BookDetailsDialog : Form + { + public string NewTags { get; private set; } - public BookDetailsDialog() - { - InitializeComponent(); - } - public BookDetailsDialog(string title, string rawTags) : this() - { - this.Text = $"Edit Tags - {title}"; + public BookDetailsDialog() + { + InitializeComponent(); + } + public BookDetailsDialog(string title, string rawTags) : this() + { + this.Text = $"Edit Tags - {title}"; - this.newTagsTb.Text = rawTags; - } + this.newTagsTb.Text = rawTags; + } - private void SaveBtn_Click(object sender, EventArgs e) - { - NewTags = this.newTagsTb.Text; - DialogResult = DialogResult.OK; - } - } + private void SaveBtn_Click(object sender, EventArgs e) + { + NewTags = this.newTagsTb.Text; + DialogResult = DialogResult.OK; + } + } } diff --git a/LibationWinForms/Dialogs/BookDetailsDialog.resx b/LibationWinForms/Dialogs/BookDetailsDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/BookDetailsDialog.resx +++ b/LibationWinForms/Dialogs/BookDetailsDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs index 5684d485..c0b5b28c 100644 --- a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs +++ b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs @@ -1,9 +1,7 @@ -using System; +using FileManager; +using System; using System.Collections.Generic; -using System.Linq; using System.Windows.Forms; -using Dinah.Core; -using FileManager; namespace LibationWinForms.Dialogs { @@ -58,7 +56,7 @@ namespace LibationWinForms.Dialogs = knownDir != Configuration.KnownDirectories.None // this could be a well known dir which isn't an option in this particular dropdown. This will always be true of LibationFiles && this.directorySelectControl.SelectDirectory(knownDir); - + customDirectoryRb.Checked = !isKnown; knownDirectoryRb.Checked = isKnown; this.customTb.Text = isKnown ? "" : customDir; diff --git a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.resx b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.resx +++ b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/DirectorySelectControl.cs b/LibationWinForms/Dialogs/DirectorySelectControl.cs index ab8def7a..639886ba 100644 --- a/LibationWinForms/Dialogs/DirectorySelectControl.cs +++ b/LibationWinForms/Dialogs/DirectorySelectControl.cs @@ -1,9 +1,9 @@ -using System; +using Dinah.Core; +using FileManager; +using System; using System.Collections.Generic; using System.Linq; using System.Windows.Forms; -using Dinah.Core; -using FileManager; namespace LibationWinForms.Dialogs { @@ -13,7 +13,7 @@ namespace LibationWinForms.Dialogs { public string Description { get; } public Configuration.KnownDirectories Value { get; } - private DirectorySelectControl _parentControl; + private readonly DirectorySelectControl _parentControl; public string FullPath => _parentControl.AddSubDirectoryToPath(Configuration.GetKnownDirectoryPath(Value)); diff --git a/LibationWinForms/Dialogs/DirectorySelectControl.resx b/LibationWinForms/Dialogs/DirectorySelectControl.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/DirectorySelectControl.resx +++ b/LibationWinForms/Dialogs/DirectorySelectControl.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/EditQuickFilters.cs b/LibationWinForms/Dialogs/EditQuickFilters.cs index abb183ce..999e9b2f 100644 --- a/LibationWinForms/Dialogs/EditQuickFilters.cs +++ b/LibationWinForms/Dialogs/EditQuickFilters.cs @@ -1,101 +1,100 @@ -using System; +using FileManager; +using System; using System.Linq; using System.Windows.Forms; -using FileManager; namespace LibationWinForms.Dialogs { public partial class EditQuickFilters : Form - { - const string BLACK_UP_POINTING_TRIANGLE = "\u25B2"; - const string BLACK_DOWN_POINTING_TRIANGLE = "\u25BC"; + { + private const string BLACK_UP_POINTING_TRIANGLE = "\u25B2"; + private const string BLACK_DOWN_POINTING_TRIANGLE = "\u25BC"; + private const string COL_Original = nameof(Original); + private const string COL_Delete = nameof(Delete); + private const string COL_Filter = nameof(Filter); + private const string COL_MoveUp = nameof(MoveUp); + private const string COL_MoveDown = nameof(MoveDown); - const string COL_Original = nameof(Original); - const string COL_Delete = nameof(Delete); - const string COL_Filter = nameof(Filter); - const string COL_MoveUp = nameof(MoveUp); - const string COL_MoveDown = nameof(MoveDown); + private Form1 _parent { get; } - Form1 _parent { get; } + public EditQuickFilters(Form1 parent) + { + _parent = parent; - public EditQuickFilters(Form1 parent) - { - _parent = parent; + InitializeComponent(); - InitializeComponent(); + dataGridView1.Columns[COL_Filter].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; - dataGridView1.Columns[COL_Filter].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; + populateGridValues(); + } - populateGridValues(); - } + private void populateGridValues() + { + var filters = QuickFilters.Filters; + if (!filters.Any()) + return; - private void populateGridValues() - { - var filters = QuickFilters.Filters; - if (!filters.Any()) - return; + foreach (var filter in filters) + dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE); + } - foreach (var filter in filters) - dataGridView1.Rows.Add(filter, "X", filter, BLACK_UP_POINTING_TRIANGLE, BLACK_DOWN_POINTING_TRIANGLE); - } + private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e) + { + e.Row.Cells[COL_Delete].Value = "X"; + e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE; + e.Row.Cells[COL_MoveDown].Value = BLACK_DOWN_POINTING_TRIANGLE; + } - private void dataGridView1_DefaultValuesNeeded(object sender, DataGridViewRowEventArgs e) - { - e.Row.Cells[COL_Delete].Value = "X"; - e.Row.Cells[COL_MoveUp].Value = BLACK_UP_POINTING_TRIANGLE; - e.Row.Cells[COL_MoveDown].Value = BLACK_DOWN_POINTING_TRIANGLE; - } + private void saveBtn_Click(object sender, EventArgs e) + { + var list = dataGridView1.Rows + .OfType() + .Select(r => r.Cells[COL_Filter].Value?.ToString()) + .ToList(); + QuickFilters.ReplaceAll(list); - private void saveBtn_Click(object sender, EventArgs e) - { - var list = dataGridView1.Rows - .OfType() - .Select(r => r.Cells[COL_Filter].Value?.ToString()) - .ToList(); - QuickFilters.ReplaceAll(list); + _parent.UpdateFilterDropDown(); + this.DialogResult = DialogResult.OK; + this.Close(); + } - _parent.UpdateFilterDropDown(); - this.DialogResult = DialogResult.OK; - this.Close(); - } + private void cancelBtn_Click(object sender, EventArgs e) + { + this.DialogResult = DialogResult.Cancel; + this.Close(); + } - private void cancelBtn_Click(object sender, EventArgs e) - { - this.DialogResult = DialogResult.Cancel; - this.Close(); - } + private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) + { + var dgv = (DataGridView)sender; - private void DataGridView1_CellContentClick(object sender, DataGridViewCellEventArgs e) - { - var dgv = (DataGridView)sender; - - var col = dgv.Columns[e.ColumnIndex]; - if (col is DataGridViewButtonColumn && e.RowIndex >= 0) - { - var row = dgv.Rows[e.RowIndex]; - switch (col.Name) - { - case COL_Delete: - // if final/edit row: do nothing - if (e.RowIndex < dgv.RowCount - 1) - dgv.Rows.Remove(row); - break; - case COL_MoveUp: - // if top: do nothing - if (e.RowIndex < 1) - break; - dgv.Rows.Remove(row); - dgv.Rows.Insert(e.RowIndex - 1, row); - break; - case COL_MoveDown: - // if final/edit row or bottom filter row: do nothing - if (e.RowIndex >= dgv.RowCount - 2) - break; - dgv.Rows.Remove(row); - dgv.Rows.Insert(e.RowIndex + 1, row); - break; - } - } - } - } + var col = dgv.Columns[e.ColumnIndex]; + if (col is DataGridViewButtonColumn && e.RowIndex >= 0) + { + var row = dgv.Rows[e.RowIndex]; + switch (col.Name) + { + case COL_Delete: + // if final/edit row: do nothing + if (e.RowIndex < dgv.RowCount - 1) + dgv.Rows.Remove(row); + break; + case COL_MoveUp: + // if top: do nothing + if (e.RowIndex < 1) + break; + dgv.Rows.Remove(row); + dgv.Rows.Insert(e.RowIndex - 1, row); + break; + case COL_MoveDown: + // if final/edit row or bottom filter row: do nothing + if (e.RowIndex >= dgv.RowCount - 2) + break; + dgv.Rows.Remove(row); + dgv.Rows.Insert(e.RowIndex + 1, row); + break; + } + } + } + } } diff --git a/LibationWinForms/Dialogs/EditQuickFilters.resx b/LibationWinForms/Dialogs/EditQuickFilters.resx index 714d166d..9c876821 100644 --- a/LibationWinForms/Dialogs/EditQuickFilters.resx +++ b/LibationWinForms/Dialogs/EditQuickFilters.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/IndexLibraryDialog.cs b/LibationWinForms/Dialogs/IndexLibraryDialog.cs index e534cc28..0026fe79 100644 --- a/LibationWinForms/Dialogs/IndexLibraryDialog.cs +++ b/LibationWinForms/Dialogs/IndexLibraryDialog.cs @@ -1,8 +1,8 @@ -using System; -using System.Windows.Forms; -using ApplicationServices; +using ApplicationServices; using InternalUtilities; using LibationWinForms.Login; +using System; +using System.Windows.Forms; namespace LibationWinForms.Dialogs { diff --git a/LibationWinForms/Dialogs/IndexLibraryDialog.resx b/LibationWinForms/Dialogs/IndexLibraryDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/IndexLibraryDialog.resx +++ b/LibationWinForms/Dialogs/IndexLibraryDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/LibationFilesDialog.cs b/LibationWinForms/Dialogs/LibationFilesDialog.cs index c6c67383..09ec8096 100644 --- a/LibationWinForms/Dialogs/LibationFilesDialog.cs +++ b/LibationWinForms/Dialogs/LibationFilesDialog.cs @@ -1,6 +1,6 @@ -using System; +using FileManager; +using System; using System.Windows.Forms; -using FileManager; namespace LibationWinForms.Dialogs { diff --git a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs b/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs index 426bae71..68fb2a99 100644 --- a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs +++ b/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs @@ -1,7 +1,7 @@ -using System; -using System.Windows.Forms; -using Dinah.Core; +using Dinah.Core; using InternalUtilities; +using System; +using System.Windows.Forms; namespace LibationWinForms.Dialogs.Login { diff --git a/LibationWinForms/Dialogs/Login/MfaDialog.cs b/LibationWinForms/Dialogs/Login/MfaDialog.cs index add77588..e369b367 100644 --- a/LibationWinForms/Dialogs/Login/MfaDialog.cs +++ b/LibationWinForms/Dialogs/Login/MfaDialog.cs @@ -1,11 +1,5 @@ using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; namespace LibationWinForms.Dialogs.Login @@ -14,7 +8,7 @@ namespace LibationWinForms.Dialogs.Login { private RadioButton[] radioButtons { get; } - AudibleApi.MfaConfig _mfaConfig { get; } + private AudibleApi.MfaConfig _mfaConfig { get; } public MfaDialog(AudibleApi.MfaConfig mfaConfig) { @@ -32,7 +26,8 @@ namespace LibationWinForms.Dialogs.Login setRadioButton(1, this.radioButton2); setRadioButton(2, this.radioButton3); - Serilog.Log.Logger.Information("{@DebugInfo}", new { + Serilog.Log.Logger.Information("{@DebugInfo}", new + { paramButtonCount = mfaConfig.Buttons.Count, visibleRadioButtonCount = radioButtons.Count(rb => rb.Visible) }); @@ -65,7 +60,8 @@ namespace LibationWinForms.Dialogs.Login { var selected = radioButtons.FirstOrDefault(rb => rb.Checked); - Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new { + Serilog.Log.Logger.Information("Submit button clicked: {@DebugInfo}", new + { rb1_visible = radioButton1.Visible, rb1_checked = radioButton1.Checked, diff --git a/LibationWinForms/Dialogs/Login/WinformResponder.cs b/LibationWinForms/Dialogs/Login/WinformResponder.cs index 28507f4d..d57a01d7 100644 --- a/LibationWinForms/Dialogs/Login/WinformResponder.cs +++ b/LibationWinForms/Dialogs/Login/WinformResponder.cs @@ -1,5 +1,4 @@ -using System; -using AudibleApi; +using AudibleApi; using InternalUtilities; using LibationWinForms.Dialogs.Login; diff --git a/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs b/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs index 14f2e76f..6ef2e320 100644 --- a/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs +++ b/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs @@ -1,7 +1,7 @@ -using System; +using Dinah.Core; +using System; using System.Drawing; using System.Windows.Forms; -using Dinah.Core; namespace LibationWinForms.Dialogs { diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.cs b/LibationWinForms/Dialogs/RemoveBooksDialog.cs index a31ca838..40457837 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.cs +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.cs @@ -3,27 +3,27 @@ using DataLayer; 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 System.Collections; namespace LibationWinForms.Dialogs { - public partial class RemoveBooksDialog : Form + public partial class RemoveBooksDialog : Form { public bool BooksRemoved { get; private set; } private Account[] _accounts { get; } - private List _libraryBooks; - private SortableBindingList2 _removableGridEntries; - private string _labelFormat; + private readonly List _libraryBooks; + private readonly SortableBindingList2 _removableGridEntries; + private readonly string _labelFormat; private int SelectedCount => SelectedEntries?.Count() ?? 0; private IEnumerable SelectedEntries => _removableGridEntries?.Where(b => b.Remove); - public RemoveBooksDialog(params Account[] accounts) + public RemoveBooksDialog(params Account[] accounts) { _libraryBooks = DbContexts.GetContext().GetLibrary_Flat_NoTracking(); _accounts = accounts; @@ -32,8 +32,8 @@ namespace LibationWinForms.Dialogs _labelFormat = label1.Text; _dataGridView.CellContentClick += (s, e) => _dataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit); - _dataGridView.CellValueChanged += DataGridView1_CellValueChanged; - _dataGridView.BindingContextChanged += (s, e) => UpdateSelection(); + _dataGridView.CellValueChanged += DataGridView1_CellValueChanged; + _dataGridView.BindingContextChanged += (s, e) => UpdateSelection(); var orderedGridEntries = _libraryBooks .Select(lb => new RemovableGridEntry(lb)) @@ -46,7 +46,7 @@ namespace LibationWinForms.Dialogs _dataGridView.Enabled = false; } - private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) + private void DataGridView1_CellValueChanged(object sender, DataGridViewCellEventArgs e) { if (e.ColumnIndex == 0) UpdateSelection(); @@ -78,9 +78,9 @@ namespace LibationWinForms.Dialogs ex); } finally - { + { _dataGridView.Enabled = true; - } + } } private void btnRemoveBooks_Click(object sender, EventArgs e) @@ -122,15 +122,15 @@ namespace LibationWinForms.Dialogs } } 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; - } - } + } + } - internal class RemovableGridEntry : GridEntry + internal class RemovableGridEntry : GridEntry { private static readonly IComparer BoolComparer = new ObjectComparer(); @@ -153,18 +153,18 @@ namespace LibationWinForms.Dialogs } } - public override object GetMemberValue(string memberName) - { + public override object GetMemberValue(string memberName) + { if (memberName == nameof(Remove)) return Remove; - return base.GetMemberValue(memberName); - } + return base.GetMemberValue(memberName); + } - public override IComparer GetMemberComparer(Type memberType) + public override IComparer GetMemberComparer(Type memberType) { if (memberType == typeof(bool)) return BoolComparer; return base.GetMemberComparer(memberType); - } - } + } + } } diff --git a/LibationWinForms/Dialogs/RemoveBooksDialog.resx b/LibationWinForms/Dialogs/RemoveBooksDialog.resx index a3058bc8..5ffc920f 100644 --- a/LibationWinForms/Dialogs/RemoveBooksDialog.resx +++ b/LibationWinForms/Dialogs/RemoveBooksDialog.resx @@ -1,4 +1,5 @@ - + + @@ -57,7 +58,4 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - 17, 17 - \ No newline at end of file diff --git a/LibationWinForms/Dialogs/ScanAccountsDialog.cs b/LibationWinForms/Dialogs/ScanAccountsDialog.cs index 6f31ef06..eebd720e 100644 --- a/LibationWinForms/Dialogs/ScanAccountsDialog.cs +++ b/LibationWinForms/Dialogs/ScanAccountsDialog.cs @@ -1,10 +1,7 @@ -using System; +using InternalUtilities; +using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Threading.Tasks; using System.Windows.Forms; -using InternalUtilities; namespace LibationWinForms.Dialogs { @@ -12,7 +9,7 @@ namespace LibationWinForms.Dialogs { public List CheckedAccounts { get; } = new List(); - Form1 _parent { get; } + private Form1 _parent { get; } public ScanAccountsDialog(Form1 parent) { @@ -21,7 +18,7 @@ namespace LibationWinForms.Dialogs InitializeComponent(); } - class listItem + private class listItem { public Account Account { get; set; } public string Text { get; set; } diff --git a/LibationWinForms/Dialogs/ScanAccountsDialog.resx b/LibationWinForms/Dialogs/ScanAccountsDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/ScanAccountsDialog.resx +++ b/LibationWinForms/Dialogs/ScanAccountsDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/SearchSyntaxDialog.cs b/LibationWinForms/Dialogs/SearchSyntaxDialog.cs index 1300a74a..bd8a2b87 100644 --- a/LibationWinForms/Dialogs/SearchSyntaxDialog.cs +++ b/LibationWinForms/Dialogs/SearchSyntaxDialog.cs @@ -3,18 +3,18 @@ using System.Windows.Forms; namespace LibationWinForms.Dialogs { - public partial class SearchSyntaxDialog : Form - { - public SearchSyntaxDialog() - { - InitializeComponent(); + public partial class SearchSyntaxDialog : Form + { + public SearchSyntaxDialog() + { + InitializeComponent(); - label2.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchStringFields()); - label3.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields()); - label4.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields()); - label5.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields()); - } + label2.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchStringFields()); + label3.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields()); + label4.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields()); + label5.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields()); + } - private void CloseBtn_Click(object sender, EventArgs e) => this.Close(); - } + private void CloseBtn_Click(object sender, EventArgs e) => this.Close(); + } } diff --git a/LibationWinForms/Dialogs/SearchSyntaxDialog.resx b/LibationWinForms/Dialogs/SearchSyntaxDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/SearchSyntaxDialog.resx +++ b/LibationWinForms/Dialogs/SearchSyntaxDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/SettingsDialog.cs b/LibationWinForms/Dialogs/SettingsDialog.cs index fee85e1d..81e9c0c8 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.cs +++ b/LibationWinForms/Dialogs/SettingsDialog.cs @@ -1,15 +1,15 @@ -using System; +using Dinah.Core; +using FileManager; +using System; using System.IO; using System.Windows.Forms; -using Dinah.Core; -using FileManager; namespace LibationWinForms.Dialogs { public partial class SettingsDialog : Form { - Configuration config { get; } = Configuration.Instance; - Func desc { get; } = Configuration.GetDescription; + private Configuration config { get; } = Configuration.Instance; + private Func desc { get; } = Configuration.GetDescription; public SettingsDialog() => InitializeComponent(); @@ -57,13 +57,13 @@ namespace LibationWinForms.Dialogs inProgressSelectControl.SelectDirectory(config.InProgress); } - private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e) - { + private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e) + { convertLosslessRb.Enabled = allowLibationFixupCbox.Checked; convertLossyRb.Enabled = allowLibationFixupCbox.Checked; if (!allowLibationFixupCbox.Checked) - { + { convertLosslessRb.Checked = true; } } diff --git a/LibationWinForms/Dialogs/SettingsDialog.resx b/LibationWinForms/Dialogs/SettingsDialog.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.resx +++ b/LibationWinForms/Dialogs/SettingsDialog.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/SetupDialog.resx b/LibationWinForms/Dialogs/SetupDialog.resx index 859984c0..9e9a5108 100644 --- a/LibationWinForms/Dialogs/SetupDialog.resx +++ b/LibationWinForms/Dialogs/SetupDialog.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index 30bba304..6f9223d3 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; -using ApplicationServices; +using ApplicationServices; using DataLayer; using Dinah.Core; using Dinah.Core.Drawing; @@ -11,371 +6,376 @@ using Dinah.Core.Windows.Forms; 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 { - public partial class Form1 : Form - { - private string backupsCountsLbl_Format { get; } - private string pdfsCountsLbl_Format { get; } + public partial class Form1 : Form + { + private string backupsCountsLbl_Format { get; } + private string pdfsCountsLbl_Format { get; } private string visibleCountLbl_Format { get; } private string beginBookBackupsToolStripMenuItem_format { get; } private string beginPdfBackupsToolStripMenuItem_format { get; } public Form1() - { - InitializeComponent(); + { + InitializeComponent(); - // back up string formats - backupsCountsLbl_Format = backupsCountsLbl.Text; - pdfsCountsLbl_Format = pdfsCountsLbl.Text; - visibleCountLbl_Format = visibleCountLbl.Text; + // back up string formats + backupsCountsLbl_Format = backupsCountsLbl.Text; + pdfsCountsLbl_Format = pdfsCountsLbl.Text; + visibleCountLbl_Format = visibleCountLbl.Text; beginBookBackupsToolStripMenuItem_format = beginBookBackupsToolStripMenuItem.Text; - beginPdfBackupsToolStripMenuItem_format = beginPdfBackupsToolStripMenuItem.Text; + beginPdfBackupsToolStripMenuItem_format = beginPdfBackupsToolStripMenuItem.Text; - // after backing up formats: can set default/temp visible text - backupsCountsLbl.Text = "[Calculating backed up book quantities]"; - pdfsCountsLbl.Text = "[Calculating backed up PDFs]"; + // after backing up formats: can set default/temp visible text + backupsCountsLbl.Text = "[Calculating backed up book quantities]"; + pdfsCountsLbl.Text = "[Calculating backed up PDFs]"; setVisibleCount(null, 0); - if (this.DesignMode) - return; + if (this.DesignMode) + return; - // independent UI updates - this.Load += setBackupCountsAsync; - this.Load += (_, __) => RestoreSizeAndLocation(); - this.Load += (_, __) => RefreshImportMenu(); + // independent UI updates + this.Load += setBackupCountsAsync; + this.Load += (_, __) => RestoreSizeAndLocation(); + this.Load += (_, __) => RefreshImportMenu(); - var format = System.Drawing.Imaging.ImageFormat.Jpeg; - PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format)); - PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format)); - PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format)); - } + var format = System.Drawing.Imaging.ImageFormat.Jpeg; + PictureStorage.SetDefaultImage(PictureSize._80x80, Properties.Resources.default_cover_80x80.ToBytes(format)); + PictureStorage.SetDefaultImage(PictureSize._300x300, Properties.Resources.default_cover_300x300.ToBytes(format)); + PictureStorage.SetDefaultImage(PictureSize._500x500, Properties.Resources.default_cover_500x500.ToBytes(format)); + } - private void Form1_Load(object sender, EventArgs e) + private void Form1_Load(object sender, EventArgs e) { - if (this.DesignMode) - return; + if (this.DesignMode) + return; - reloadGrid(); - - // also applies filter. ONLY call AFTER loading grid - loadInitialQuickFilterState(); - } + reloadGrid(); - private void Form1_FormClosing(object sender, FormClosingEventArgs e) - { - SaveSizeAndLocation(); - } + // 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 config = Configuration.Instance; - var width = config.MainFormWidth; - var height = config.MainFormHeight; + 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; - } + // 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; + // 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 x = config.MainFormX; + var y = config.MainFormY; - var rect = new System.Drawing.Rectangle(x, y, width, height); + 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; - } + // 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; - } + // 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; + 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; + // 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; + } - config.MainFormX = location.X; - config.MainFormY = location.Y; + var config = Configuration.Instance; - config.MainFormWidth = size.Width; - config.MainFormHeight = size.Height; + config.MainFormX = location.X; + config.MainFormY = location.Y; - config.MainFormIsMaximized = this.WindowState == FormWindowState.Maximized; - } + config.MainFormWidth = size.Width; + config.MainFormHeight = size.Height; - #region reload grid - bool isProcessingGridSelect = false; - private void reloadGrid() - { - // suppressed filter while init'ing UI - var prev_isProcessingGridSelect = isProcessingGridSelect; - isProcessingGridSelect = true; - setGrid(); - isProcessingGridSelect = prev_isProcessingGridSelect; + config.MainFormIsMaximized = this.WindowState == FormWindowState.Maximized; + } - // UI init complete. now we can apply filter - doFilter(lastGoodFilter); - } + #region reload grid + private bool isProcessingGridSelect = false; + private void reloadGrid() + { + // suppressed filter while init'ing UI + var prev_isProcessingGridSelect = isProcessingGridSelect; + isProcessingGridSelect = true; + setGrid(); + isProcessingGridSelect = prev_isProcessingGridSelect; - ProductsGrid currProductsGrid; - private void setGrid() - { - SuspendLayout(); - { - if (currProductsGrid != null) - { - gridPanel.Controls.Remove(currProductsGrid); - currProductsGrid.VisibleCountChanged -= setVisibleCount; - currProductsGrid.BackupCountsChanged -= setBackupCountsAsync; - currProductsGrid.Dispose(); - } + // UI init complete. now we can apply filter + doFilter(lastGoodFilter); + } - currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill }; - currProductsGrid.VisibleCountChanged += setVisibleCount; - currProductsGrid.BackupCountsChanged += setBackupCountsAsync; - gridPanel.UIThread(() => gridPanel.Controls.Add(currProductsGrid)); - currProductsGrid.Display(); - } - ResumeLayout(); - } - #endregion + private ProductsGrid currProductsGrid; + private void setGrid() + { + SuspendLayout(); + { + if (currProductsGrid != null) + { + gridPanel.Controls.Remove(currProductsGrid); + currProductsGrid.VisibleCountChanged -= setVisibleCount; + currProductsGrid.BackupCountsChanged -= setBackupCountsAsync; + currProductsGrid.Dispose(); + } - #region bottom: qty books visible - private void setVisibleCount(object _, int qty) => visibleCountLbl.Text = string.Format(visibleCountLbl_Format, qty); - #endregion + currProductsGrid = new ProductsGrid { Dock = DockStyle.Fill }; + currProductsGrid.VisibleCountChanged += setVisibleCount; + currProductsGrid.BackupCountsChanged += setBackupCountsAsync; + gridPanel.UIThread(() => gridPanel.Controls.Add(currProductsGrid)); + currProductsGrid.Display(); + } + ResumeLayout(); + } + #endregion - #region bottom: backup counts - private async void setBackupCountsAsync(object _, object __) - { - LibraryCommands.LibraryStats libraryStats = null; - await Task.Run(() => libraryStats = LibraryCommands.GetCounts()); + #region bottom: qty books visible + private void setVisibleCount(object _, int qty) => visibleCountLbl.Text = string.Format(visibleCountLbl_Format, qty); + #endregion - setBookBackupCounts(libraryStats.booksFullyBackedUp, libraryStats.booksDownloadedOnly, libraryStats.booksNoProgress); - setPdfBackupCounts(libraryStats.pdfsDownloaded, libraryStats.pdfsNotDownloaded); - } - private void setBookBackupCounts(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress) - { - // enable/disable export - var hasResults = 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress); - exportLibraryToolStripMenuItem.Enabled = hasResults; + #region bottom: backup counts + private async void setBackupCountsAsync(object _, object __) + { + LibraryCommands.LibraryStats libraryStats = null; + await Task.Run(() => libraryStats = LibraryCommands.GetCounts()); - // update bottom numbers - var pending = booksNoProgress + booksDownloadedOnly; - var statusStripText - = !hasResults ? "No books. Begin by importing your library" - : pending > 0 ? string.Format(backupsCountsLbl_Format, booksNoProgress, booksDownloadedOnly, booksFullyBackedUp) - : $"All {"book".PluralizeWithCount(booksFullyBackedUp)} backed up"; + setBookBackupCounts(libraryStats.booksFullyBackedUp, libraryStats.booksDownloadedOnly, libraryStats.booksNoProgress); + setPdfBackupCounts(libraryStats.pdfsDownloaded, libraryStats.pdfsNotDownloaded); + } + private void setBookBackupCounts(int booksFullyBackedUp, int booksDownloadedOnly, int booksNoProgress) + { + // enable/disable export + var hasResults = 0 < (booksFullyBackedUp + booksDownloadedOnly + booksNoProgress); + exportLibraryToolStripMenuItem.Enabled = hasResults; - // update menu item - var menuItemText - = pending > 0 - ? $"{pending} remaining" - : "All books have been liberated"; + // update bottom numbers + var pending = booksNoProgress + booksDownloadedOnly; + var statusStripText + = !hasResults ? "No books. Begin by importing your library" + : pending > 0 ? string.Format(backupsCountsLbl_Format, booksNoProgress, booksDownloadedOnly, booksFullyBackedUp) + : $"All {"book".PluralizeWithCount(booksFullyBackedUp)} backed up"; - // update UI - statusStrip1.UIThread(() => backupsCountsLbl.Text = statusStripText); - menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0); - menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText)); - } - private void setPdfBackupCounts(int pdfsDownloaded, int pdfsNotDownloaded) - { - // update bottom numbers - var hasResults = 0 < (pdfsNotDownloaded + pdfsDownloaded); - var statusStripText - = !hasResults ? "" - : pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, pdfsNotDownloaded, pdfsDownloaded) - : $"| All {pdfsDownloaded} PDFs downloaded"; + // update menu item + var menuItemText + = pending > 0 + ? $"{pending} remaining" + : "All books have been liberated"; - // update menu item - var menuItemText - = pdfsNotDownloaded > 0 - ? $"{pdfsNotDownloaded} remaining" - : "All PDFs have been downloaded"; + // update UI + statusStrip1.UIThread(() => backupsCountsLbl.Text = statusStripText); + menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Enabled = pending > 0); + menuStrip1.UIThread(() => beginBookBackupsToolStripMenuItem.Text = string.Format(beginBookBackupsToolStripMenuItem_format, menuItemText)); + } + private void setPdfBackupCounts(int pdfsDownloaded, int pdfsNotDownloaded) + { + // update bottom numbers + var hasResults = 0 < (pdfsNotDownloaded + pdfsDownloaded); + var statusStripText + = !hasResults ? "" + : pdfsNotDownloaded > 0 ? string.Format(pdfsCountsLbl_Format, pdfsNotDownloaded, pdfsDownloaded) + : $"| All {pdfsDownloaded} PDFs downloaded"; - // update UI - statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText); - menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = pdfsNotDownloaded > 0); - menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText)); - } - #endregion + // update menu item + var menuItemText + = pdfsNotDownloaded > 0 + ? $"{pdfsNotDownloaded} remaining" + : "All PDFs have been downloaded"; - #region filter - private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog(); + // update UI + statusStrip1.UIThread(() => pdfsCountsLbl.Text = statusStripText); + menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Enabled = pdfsNotDownloaded > 0); + menuStrip1.UIThread(() => beginPdfBackupsToolStripMenuItem.Text = string.Format(beginPdfBackupsToolStripMenuItem_format, menuItemText)); + } + #endregion - private void AddFilterBtn_Click(object sender, EventArgs e) - { - QuickFilters.Add(this.filterSearchTb.Text); - UpdateFilterDropDown(); - } + #region filter + private void filterHelpBtn_Click(object sender, EventArgs e) => new SearchSyntaxDialog().ShowDialog(); - private void filterSearchTb_KeyPress(object sender, KeyPressEventArgs e) - { - if (e.KeyChar == (char)Keys.Return) - { - doFilter(); + private void AddFilterBtn_Click(object sender, EventArgs e) + { + QuickFilters.Add(this.filterSearchTb.Text); + UpdateFilterDropDown(); + } - // silence the 'ding' - e.Handled = true; - } - } - private void filterBtn_Click(object sender, EventArgs e) => doFilter(); + private void filterSearchTb_KeyPress(object sender, KeyPressEventArgs e) + { + if (e.KeyChar == (char)Keys.Return) + { + doFilter(); - string lastGoodFilter = ""; - private void doFilter(string filterString) - { - this.filterSearchTb.Text = filterString; - doFilter(); - } - private void doFilter() - { - if (isProcessingGridSelect || currProductsGrid == null) - return; + // silence the 'ding' + e.Handled = true; + } + } + private void filterBtn_Click(object sender, EventArgs e) => doFilter(); - try - { - currProductsGrid.Filter(filterSearchTb.Text); - lastGoodFilter = filterSearchTb.Text; - } - catch (Exception ex) - { - MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error); + private string lastGoodFilter = ""; + private void doFilter(string filterString) + { + this.filterSearchTb.Text = filterString; + doFilter(); + } + private void doFilter() + { + if (isProcessingGridSelect || currProductsGrid == null) + return; - // re-apply last good filter - doFilter(lastGoodFilter); - } - } - #endregion + try + { + currProductsGrid.Filter(filterSearchTb.Text); + lastGoodFilter = filterSearchTb.Text; + } + catch (Exception ex) + { + MessageBox.Show($"Bad filter string:\r\n\r\n{ex.Message}", "Bad filter string", MessageBoxButtons.OK, MessageBoxIcon.Error); - #region Import menu - public void RefreshImportMenu() - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var count = persister.AccountsSettings.Accounts.Count; + // re-apply last good filter + doFilter(lastGoodFilter); + } + } + #endregion - noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0; - scanLibraryToolStripMenuItem.Visible = count == 1; - scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1; - scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1; + #region Import menu + public void RefreshImportMenu() + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var count = persister.AccountsSettings.Accounts.Count; - removeLibraryBooksToolStripMenuItem.Visible = count != 0; + noAccountsYetAddAccountToolStripMenuItem.Visible = count == 0; + scanLibraryToolStripMenuItem.Visible = count == 1; + scanLibraryOfAllAccountsToolStripMenuItem.Visible = count > 1; + scanLibraryOfSomeAccountsToolStripMenuItem.Visible = count > 1; - if (count == 1) - { - removeLibraryBooksToolStripMenuItem.Click += removeThisAccountToolStripMenuItem_Click; - } + removeLibraryBooksToolStripMenuItem.Visible = count != 0; - removeSomeAccountsToolStripMenuItem.Visible = count > 1; - removeAllAccountsToolStripMenuItem.Visible = count > 1; - } + if (count == 1) + { + removeLibraryBooksToolStripMenuItem.Click += removeThisAccountToolStripMenuItem_Click; + } - private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e) - { - MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account"); - new AccountsDialog(this).ShowDialog(); - } + removeSomeAccountsToolStripMenuItem.Visible = count > 1; + removeAllAccountsToolStripMenuItem.Visible = count > 1; + } - private void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); - scanLibraries(firstAccount); - } + private void noAccountsYetAddAccountToolStripMenuItem_Click(object sender, EventArgs e) + { + MessageBox.Show("To load your Audible library, come back here to the Import menu after adding your account"); + new AccountsDialog(this).ShowDialog(); + } - private void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var allAccounts = persister.AccountsSettings.GetAll(); - scanLibraries(allAccounts); - } + private void scanLibraryToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); + scanLibraries(firstAccount); + } - private void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var scanAccountsDialog = new ScanAccountsDialog(this); + private void scanLibraryOfAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var allAccounts = persister.AccountsSettings.GetAll(); + scanLibraries(allAccounts); + } - if (scanAccountsDialog.ShowDialog() != DialogResult.OK) - return; + private void scanLibraryOfSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var scanAccountsDialog = new ScanAccountsDialog(this); - if (!scanAccountsDialog.CheckedAccounts.Any()) - return; + if (scanAccountsDialog.ShowDialog() != DialogResult.OK) + return; - scanLibraries(scanAccountsDialog.CheckedAccounts); - } + if (!scanAccountsDialog.CheckedAccounts.Any()) + return; - private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); - scanLibrariesRemovedBooks(firstAccount); - } + scanLibraries(scanAccountsDialog.CheckedAccounts); + } - private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); - var allAccounts = persister.AccountsSettings.GetAll(); - scanLibrariesRemovedBooks(allAccounts.ToArray()); - } + private void removeThisAccountToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var firstAccount = persister.AccountsSettings.GetAll().FirstOrDefault(); + scanLibrariesRemovedBooks(firstAccount); + } - private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) - { - using var scanAccountsDialog = new ScanAccountsDialog(this); + private void removeAllAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var persister = AudibleApiStorage.GetAccountsSettingsPersister(); + var allAccounts = persister.AccountsSettings.GetAll(); + scanLibrariesRemovedBooks(allAccounts.ToArray()); + } - if (scanAccountsDialog.ShowDialog() != DialogResult.OK) - return; + private void removeSomeAccountsToolStripMenuItem_Click(object sender, EventArgs e) + { + using var scanAccountsDialog = new ScanAccountsDialog(this); - if (!scanAccountsDialog.CheckedAccounts.Any()) - return; + if (scanAccountsDialog.ShowDialog() != DialogResult.OK) + return; - scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); - } + if (!scanAccountsDialog.CheckedAccounts.Any()) + return; - private void scanLibrariesRemovedBooks(params Account[] accounts) - { - using var dialog = new RemoveBooksDialog(accounts); - dialog.ShowDialog(); + scanLibrariesRemovedBooks(scanAccountsDialog.CheckedAccounts.ToArray()); + } - if (dialog.BooksRemoved) - reloadGrid(); - } + private void scanLibrariesRemovedBooks(params Account[] accounts) + { + using var dialog = new RemoveBooksDialog(accounts); + dialog.ShowDialog(); - private void scanLibraries(IEnumerable accounts) => scanLibraries(accounts.ToArray()); - private void scanLibraries(params Account[] accounts) + if (dialog.BooksRemoved) + reloadGrid(); + } + + private void scanLibraries(IEnumerable accounts) => scanLibraries(accounts.ToArray()); + private void scanLibraries(params Account[] accounts) { using var dialog = new IndexLibraryDialog(accounts); dialog.ShowDialog(); @@ -387,112 +387,112 @@ namespace LibationWinForms if (totalProcessed > 0) reloadGrid(); - } - #endregion + } + #endregion - #region liberate menu - private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow); + #region liberate menu + private async void beginBookBackupsToolStripMenuItem_Click(object sender, EventArgs e) + => await BookLiberation.ProcessorAutomationController.BackupAllBooksAsync(updateGridRow); - private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow); + private async void beginPdfBackupsToolStripMenuItem_Click(object sender, EventArgs e) + => await BookLiberation.ProcessorAutomationController.BackupAllPdfsAsync(updateGridRow); - private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e) - => await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync(); + private async void convertAllM4bToMp3ToolStripMenuItem_Click(object sender, EventArgs e) + => await BookLiberation.ProcessorAutomationController.ConvertAllBooksAsync(); - private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId); - #endregion + private void updateGridRow(object _, LibraryBook libraryBook) => currProductsGrid.RefreshRow(libraryBook.Book.AudibleProductId); + #endregion - #region Export menu - private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e) - { - try - { - var saveFileDialog = new SaveFileDialog - { - Title = "Where to export Library", - Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*" - }; + #region Export menu + private void exportLibraryToolStripMenuItem_Click(object sender, EventArgs e) + { + try + { + var saveFileDialog = new SaveFileDialog + { + Title = "Where to export Library", + Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*" + }; - if (saveFileDialog.ShowDialog() != DialogResult.OK) - return; + if (saveFileDialog.ShowDialog() != DialogResult.OK) + return; - // FilterIndex is 1-based, NOT 0-based - switch (saveFileDialog.FilterIndex) - { - case 1: // xlsx - default: - LibraryExporter.ToXlsx(saveFileDialog.FileName); - break; - case 2: // csv - LibraryExporter.ToCsv(saveFileDialog.FileName); - break; - case 3: // json - LibraryExporter.ToJson(saveFileDialog.FileName); - break; - } + // FilterIndex is 1-based, NOT 0-based + switch (saveFileDialog.FilterIndex) + { + case 1: // xlsx + default: + LibraryExporter.ToXlsx(saveFileDialog.FileName); + break; + case 2: // csv + LibraryExporter.ToCsv(saveFileDialog.FileName); + break; + case 3: // json + LibraryExporter.ToJson(saveFileDialog.FileName); + break; + } - MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName); - } - catch (Exception ex) - { - MessageBoxAlertAdmin.Show("Error attempting to export your library.", "Error exporting", ex); - } - } - #endregion + MessageBox.Show("Library exported to:\r\n" + saveFileDialog.FileName); + } + catch (Exception ex) + { + MessageBoxAlertAdmin.Show("Error attempting to export your library.", "Error exporting", ex); + } + } + #endregion - #region quick filters menu - private void loadInitialQuickFilterState() - { - // set inital state. do once only - firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault; + #region quick filters menu + private void loadInitialQuickFilterState() + { + // set inital state. do once only + firstFilterIsDefaultToolStripMenuItem.Checked = QuickFilters.UseDefault; - // load default filter. do once only - if (QuickFilters.UseDefault) - doFilter(QuickFilters.Filters.FirstOrDefault()); + // load default filter. do once only + if (QuickFilters.UseDefault) + doFilter(QuickFilters.Filters.FirstOrDefault()); - // do after every save - UpdateFilterDropDown(); - } + // do after every save + UpdateFilterDropDown(); + } - private void FirstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e) - { - firstFilterIsDefaultToolStripMenuItem.Checked = !firstFilterIsDefaultToolStripMenuItem.Checked; - QuickFilters.UseDefault = firstFilterIsDefaultToolStripMenuItem.Checked; - } + private void FirstFilterIsDefaultToolStripMenuItem_Click(object sender, EventArgs e) + { + firstFilterIsDefaultToolStripMenuItem.Checked = !firstFilterIsDefaultToolStripMenuItem.Checked; + QuickFilters.UseDefault = firstFilterIsDefaultToolStripMenuItem.Checked; + } - object quickFilterTag { get; } = new object(); - public void UpdateFilterDropDown() - { - // remove old - for (var i = quickFiltersToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--) - { - var menuItem = quickFiltersToolStripMenuItem.DropDownItems[i]; - if (menuItem.Tag == quickFilterTag) - quickFiltersToolStripMenuItem.DropDownItems.Remove(menuItem); - } + private object quickFilterTag { get; } = new object(); + public void UpdateFilterDropDown() + { + // remove old + for (var i = quickFiltersToolStripMenuItem.DropDownItems.Count - 1; i >= 0; i--) + { + var menuItem = quickFiltersToolStripMenuItem.DropDownItems[i]; + if (menuItem.Tag == quickFilterTag) + quickFiltersToolStripMenuItem.DropDownItems.Remove(menuItem); + } - // re-populate - var index = 0; - foreach (var filter in QuickFilters.Filters) - { - var menuItem = new ToolStripMenuItem - { - Tag = quickFilterTag, - Text = $"&{++index}: {filter}" - }; - menuItem.Click += (_, __) => doFilter(filter); - quickFiltersToolStripMenuItem.DropDownItems.Add(menuItem); - } - } + // re-populate + var index = 0; + foreach (var filter in QuickFilters.Filters) + { + var menuItem = new ToolStripMenuItem + { + Tag = quickFilterTag, + Text = $"&{++index}: {filter}" + }; + menuItem.Click += (_, __) => doFilter(filter); + quickFiltersToolStripMenuItem.DropDownItems.Add(menuItem); + } + } - private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters(this).ShowDialog(); - #endregion + private void EditQuickFiltersToolStripMenuItem_Click(object sender, EventArgs e) => new EditQuickFilters(this).ShowDialog(); + #endregion - #region settings menu - private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog(this).ShowDialog(); + #region settings menu + private void accountsToolStripMenuItem_Click(object sender, EventArgs e) => new AccountsDialog(this).ShowDialog(); - private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); - #endregion - } + private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); + #endregion + } } diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 64a802f7..ae04e870 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -1,12 +1,12 @@ -using System; +using ApplicationServices; +using DataLayer; +using Dinah.Core.Drawing; +using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; -using ApplicationServices; -using DataLayer; -using Dinah.Core.Drawing; namespace LibationWinForms { @@ -106,7 +106,7 @@ namespace LibationWinForms /// /// Create getters for all member object values by name /// - Dictionary> CreateMemberValueDictionary() => new() + private Dictionary> CreateMemberValueDictionary() => new() { { nameof(Title), () => GetSortName(Book.Title) }, { nameof(Series), () => GetSortName(Book.SeriesNames) }, diff --git a/LibationWinForms/IObjectMemberComparable.cs b/LibationWinForms/IObjectMemberComparable.cs index ade585ab..bfacb0cd 100644 --- a/LibationWinForms/IObjectMemberComparable.cs +++ b/LibationWinForms/IObjectMemberComparable.cs @@ -3,7 +3,7 @@ using System.Collections; namespace LibationWinForms { - interface IObjectMemberComparable + internal interface IObjectMemberComparable { IComparer GetMemberComparer(Type memberType); object GetMemberValue(string memberName); diff --git a/LibationWinForms/MessageBoxAlertAdmin.cs b/LibationWinForms/MessageBoxAlertAdmin.cs index 8e97f09c..5ba8cbde 100644 --- a/LibationWinForms/MessageBoxAlertAdmin.cs +++ b/LibationWinForms/MessageBoxAlertAdmin.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using LibationWinForms.Dialogs; +using LibationWinForms.Dialogs; +using System; namespace LibationWinForms { diff --git a/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs b/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs index 7b33a22c..c4f04ee6 100644 --- a/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs +++ b/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs @@ -1,7 +1,6 @@ -using System; -using System.Windows.Forms; -using Dinah.Core.Logging; +using Dinah.Core.Logging; using Serilog; +using System.Windows.Forms; namespace LibationWinForms { diff --git a/LibationWinForms/ObjectComparer[T].cs b/LibationWinForms/ObjectComparer[T].cs index 2bc69d8d..2ca0ddb9 100644 --- a/LibationWinForms/ObjectComparer[T].cs +++ b/LibationWinForms/ObjectComparer[T].cs @@ -3,7 +3,7 @@ using System.Collections; namespace LibationWinForms { - class ObjectComparer : IComparer where T : IComparable + internal class ObjectComparer : IComparer where T : IComparable { public int Compare(object x, object y) => ((T)x).CompareTo((T)y); } diff --git a/LibationWinForms/ObjectMemberComparer[T].cs b/LibationWinForms/ObjectMemberComparer[T].cs index aea41e21..3024477e 100644 --- a/LibationWinForms/ObjectMemberComparer[T].cs +++ b/LibationWinForms/ObjectMemberComparer[T].cs @@ -3,19 +3,19 @@ using System.ComponentModel; namespace LibationWinForms { - class ObjectMemberComparer : IComparer where T : IObjectMemberComparable - { - public ListSortDirection Direction { get; set; } = ListSortDirection.Ascending; - public string PropertyName { get; set; } + internal class ObjectMemberComparer : IComparer where T : IObjectMemberComparable + { + public ListSortDirection Direction { get; set; } = ListSortDirection.Ascending; + public string PropertyName { get; set; } - public int Compare(T x, T y) - { - var val1 = x.GetMemberValue(PropertyName); - var val2 = y.GetMemberValue(PropertyName); + public int Compare(T x, T y) + { + var val1 = x.GetMemberValue(PropertyName); + var val2 = y.GetMemberValue(PropertyName); - return DirMult * x.GetMemberComparer(val1.GetType()).Compare(val1, val2); - } + return DirMult * x.GetMemberComparer(val1.GetType()).Compare(val1, val2); + } - private int DirMult => Direction == ListSortDirection.Descending ? -1 : 1; - } + private int DirMult => Direction == ListSortDirection.Descending ? -1 : 1; + } } diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 0f826435..4afe1f64 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -1,200 +1,200 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; -using ApplicationServices; +using ApplicationServices; using DataLayer; using Dinah.Core; using Dinah.Core.Windows.Forms; using LibationWinForms.Dialogs; +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; namespace LibationWinForms { - // INSTRUCTIONS TO UPDATE DATA_GRID_VIEW - // - delete current DataGridView - // - view > other windows > data sources - // - refresh - // OR - // - Add New Data Source - // Object. Next - // LibationWinForms - // AudibleDTO - // GridEntry - // - go to Design view - // - click on Data Sources > ProductItem. drowdown: DataGridView - // - drag/drop ProductItem on design surface - // AS OF AUGUST 2021 THIS DOES NOT WORK IN VS2019 WITH .NET-5 PROJECTS + // INSTRUCTIONS TO UPDATE DATA_GRID_VIEW + // - delete current DataGridView + // - view > other windows > data sources + // - refresh + // OR + // - Add New Data Source + // Object. Next + // LibationWinForms + // AudibleDTO + // GridEntry + // - go to Design view + // - click on Data Sources > ProductItem. drowdown: DataGridView + // - drag/drop ProductItem on design surface + // AS OF AUGUST 2021 THIS DOES NOT WORK IN VS2019 WITH .NET-5 PROJECTS - public partial class ProductsGrid : UserControl - { - public event EventHandler VisibleCountChanged; - public event EventHandler BackupCountsChanged; + public partial class ProductsGrid : UserControl + { + public event EventHandler VisibleCountChanged; + public event EventHandler BackupCountsChanged; - // alias - private DataGridView _dataGridView => gridEntryDataGridView; + // alias + private DataGridView _dataGridView => gridEntryDataGridView; public ProductsGrid() { InitializeComponent(); - // sorting breaks filters. must reapply filters after sorting - _dataGridView.Sorted += (_, __) => Filter(); - _dataGridView.CellContentClick += DataGridView_CellContentClick; + // sorting breaks filters. must reapply filters after sorting + _dataGridView.Sorted += (_, __) => Filter(); + _dataGridView.CellContentClick += DataGridView_CellContentClick; - EnableDoubleBuffering(); - } - private void EnableDoubleBuffering() - { - var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + EnableDoubleBuffering(); + } + private void EnableDoubleBuffering() + { + var propertyInfo = _dataGridView.GetType().GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); - propertyInfo.SetValue(_dataGridView, true, null); - } + propertyInfo.SetValue(_dataGridView, true, null); + } - #region Button controls + #region Button controls - private async void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) - { - // handle grid button click: https://stackoverflow.com/a/13687844 - if (e.RowIndex < 0 || _dataGridView.Columns[e.ColumnIndex] is not DataGridViewButtonColumn) - return; + private async void DataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) + { + // handle grid button click: https://stackoverflow.com/a/13687844 + if (e.RowIndex < 0 || _dataGridView.Columns[e.ColumnIndex] is not DataGridViewButtonColumn) + return; - var liveGridEntry = getGridEntry(e.RowIndex); + var liveGridEntry = getGridEntry(e.RowIndex); - switch (_dataGridView.Columns[e.ColumnIndex].DataPropertyName) - { - case nameof(liveGridEntry.Liberate): - await Liberate_Click(liveGridEntry); - break; - case nameof(liveGridEntry.DisplayTags): - EditTags_Click(liveGridEntry); - break; - } - } + switch (_dataGridView.Columns[e.ColumnIndex].DataPropertyName) + { + case nameof(liveGridEntry.Liberate): + await Liberate_Click(liveGridEntry); + break; + case nameof(liveGridEntry.DisplayTags): + EditTags_Click(liveGridEntry); + break; + } + } - private async Task Liberate_Click(GridEntry liveGridEntry) - { - var libraryBook = liveGridEntry.LibraryBook; + private async Task Liberate_Click(GridEntry liveGridEntry) + { + var libraryBook = liveGridEntry.LibraryBook; - // liberated: open explorer to file - if (TransitionalFileLocator.Audio_Exists(libraryBook.Book)) - { - var filePath = TransitionalFileLocator.Audio_GetPath(libraryBook.Book); - if (!Go.To.File(filePath)) - MessageBox.Show($"File not found:\r\n{filePath}"); - return; - } + // liberated: open explorer to file + if (TransitionalFileLocator.Audio_Exists(libraryBook.Book)) + { + var filePath = TransitionalFileLocator.Audio_GetPath(libraryBook.Book); + if (!Go.To.File(filePath)) + MessageBox.Show($"File not found:\r\n{filePath}"); + return; + } - // else: liberate - await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId)); - } + // else: liberate + await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook, (_, __) => RefreshRow(libraryBook.Book.AudibleProductId)); + } - private void EditTags_Click(GridEntry liveGridEntry) - { - var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.LibraryBook.Book.UserDefinedItem.Tags); - if (bookDetailsForm.ShowDialog() != DialogResult.OK) - return; + private void EditTags_Click(GridEntry liveGridEntry) + { + var bookDetailsForm = new BookDetailsDialog(liveGridEntry.Title, liveGridEntry.LibraryBook.Book.UserDefinedItem.Tags); + if (bookDetailsForm.ShowDialog() != DialogResult.OK) + return; - var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags); - if (qtyChanges == 0) - return; + var qtyChanges = LibraryCommands.UpdateTags(liveGridEntry.LibraryBook.Book, bookDetailsForm.NewTags); + if (qtyChanges == 0) + return; - //Re-apply filters - Filter(); - } + //Re-apply filters + Filter(); + } - #endregion + #endregion - #region UI display functions + #region UI display functions private bool hasBeenDisplayed = false; - public void Display() - { - if (hasBeenDisplayed) - return; - hasBeenDisplayed = true; + public void Display() + { + if (hasBeenDisplayed) + return; + hasBeenDisplayed = true; - // - // transform into sorted GridEntry.s BEFORE binding - // - using var context = DbContexts.GetContext(); - var lib = context.GetLibrary_Flat_NoTracking(); + // + // transform into sorted GridEntry.s BEFORE binding + // + using var context = DbContexts.GetContext(); + var lib = context.GetLibrary_Flat_NoTracking(); - // if no data. hide all columns. return - if (!lib.Any()) - { - for (var i = _dataGridView.ColumnCount - 1; i >= 0; i--) - _dataGridView.Columns.RemoveAt(i); - return; - } + // if no data. hide all columns. return + if (!lib.Any()) + { + for (var i = _dataGridView.ColumnCount - 1; i >= 0; i--) + _dataGridView.Columns.RemoveAt(i); + return; + } - var orderedGridEntries = lib - .Select(lb => new GridEntry(lb)).ToList() - // default load order - .OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate))) - //// more advanced example: sort by author, then series, then title - //.OrderBy(ge => ge.Authors) - // .ThenBy(ge => ge.Series) - // .ThenBy(ge => ge.Title) - .ToList(); + var orderedGridEntries = lib + .Select(lb => new GridEntry(lb)).ToList() + // default load order + .OrderByDescending(ge => (DateTime)ge.GetMemberValue(nameof(ge.PurchaseDate))) + //// more advanced example: sort by author, then series, then title + //.OrderBy(ge => ge.Authors) + // .ThenBy(ge => ge.Series) + // .ThenBy(ge => ge.Title) + .ToList(); - // BIND - gridEntryBindingSource.DataSource = new SortableBindingList2(orderedGridEntries); + // BIND + gridEntryBindingSource.DataSource = new SortableBindingList2(orderedGridEntries); - // FILTER - Filter(); + // FILTER + Filter(); - BackupCountsChanged?.Invoke(this, EventArgs.Empty); - } + BackupCountsChanged?.Invoke(this, EventArgs.Empty); + } - public void RefreshRow(string productId) - { - var rowIndex = getRowIndex((ge) => ge.AudibleProductId == productId); + public void RefreshRow(string productId) + { + var rowIndex = getRowIndex((ge) => ge.AudibleProductId == productId); - // update cells incl Liberate button text - _dataGridView.InvalidateRow(rowIndex); + // update cells incl Liberate button text + _dataGridView.InvalidateRow(rowIndex); - // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change - Filter(); + // needed in case filtering by -IsLiberated and it gets changed to Liberated. want to immediately show the change + Filter(); - BackupCountsChanged?.Invoke(this, EventArgs.Empty); - } + BackupCountsChanged?.Invoke(this, EventArgs.Empty); + } - #endregion + #endregion - #region Filter + #region Filter - string _filterSearchString; - private void Filter() => Filter(_filterSearchString); - public void Filter(string searchString) - { - _filterSearchString = searchString; + private string _filterSearchString; + private void Filter() => Filter(_filterSearchString); + public void Filter(string searchString) + { + _filterSearchString = searchString; if (_dataGridView.Rows.Count == 0) return; - var searchResults = SearchEngineCommands.Search(searchString); - var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); + var searchResults = SearchEngineCommands.Search(searchString); + var productIds = searchResults.Docs.Select(d => d.ProductId).ToList(); - // https://stackoverflow.com/a/18942430 - 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); - } + // https://stackoverflow.com/a/18942430 + 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); + } - //Causes repainting of the DataGridView - bindingContext.ResumeBinding(); + //Causes repainting of the DataGridView + bindingContext.ResumeBinding(); VisibleCountChanged?.Invoke(this, _dataGridView.AsEnumerable().Count(r => r.Visible)); - } + } - #endregion + #endregion - #region DataGridView Macro + #region DataGridView Macro - private int getRowIndex(Func func) => _dataGridView.GetRowIdOfBoundItem(func); - private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); + private int getRowIndex(Func func) => _dataGridView.GetRowIdOfBoundItem(func); + private GridEntry getGridEntry(int rowIndex) => _dataGridView.GetBoundItem(rowIndex); - #endregion - } + #endregion + } } diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs index 838dda29..377cdf86 100644 --- a/LibationWinForms/SortableBindingList2[T].cs +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -1,75 +1,74 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; namespace LibationWinForms { - class SortableBindingList2 : BindingList where T : IObjectMemberComparable - { - private bool isSorted; - private ListSortDirection listSortDirection; - private PropertyDescriptor propertyDescriptor; + internal class SortableBindingList2 : BindingList where T : IObjectMemberComparable + { + private bool isSorted; + private ListSortDirection listSortDirection; + private PropertyDescriptor propertyDescriptor; - public SortableBindingList2() : base(new List()) { } - public SortableBindingList2(IEnumerable enumeration) : base(new List(enumeration)) { } + public SortableBindingList2() : base(new List()) { } + public SortableBindingList2(IEnumerable enumeration) : base(new List(enumeration)) { } - private ObjectMemberComparer Comparer { get; } = new(); - protected override bool SupportsSortingCore => true; - protected override bool SupportsSearchingCore => true; - protected override bool IsSortedCore => isSorted; - protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; - protected override ListSortDirection SortDirectionCore => listSortDirection; + private ObjectMemberComparer Comparer { get; } = new(); + protected override bool SupportsSortingCore => true; + protected override bool SupportsSearchingCore => true; + protected override bool IsSortedCore => isSorted; + protected override PropertyDescriptor SortPropertyCore => propertyDescriptor; + protected override ListSortDirection SortDirectionCore => listSortDirection; - protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) - { - List itemsList = (List)Items; + protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction) + { + List itemsList = (List)Items; - Comparer.PropertyName = property.Name; - Comparer.Direction = direction; + Comparer.PropertyName = property.Name; + Comparer.Direction = direction; - //Array.Sort() and List.Sort() are unstable sorts. OrderBy is stable. - var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList(); + //Array.Sort() and List.Sort() are unstable sorts. OrderBy is stable. + var sortedItems = itemsList.OrderBy((ge) => ge, Comparer).ToList(); - itemsList.Clear(); - itemsList.AddRange(sortedItems); + itemsList.Clear(); + itemsList.AddRange(sortedItems); - propertyDescriptor = property; - listSortDirection = direction; - isSorted = true; + propertyDescriptor = property; + listSortDirection = direction; + isSorted = true; - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } - protected override void RemoveSortCore() - { - isSorted = false; - propertyDescriptor = base.SortPropertyCore; - listSortDirection = base.SortDirectionCore; + protected override void RemoveSortCore() + { + isSorted = false; + propertyDescriptor = base.SortPropertyCore; + listSortDirection = base.SortDirectionCore; - OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); - } - //NOTE: Libation does not currently use BindingSource.Find anywhere, - //so this override may be removed (along with SupportsSearchingCore) - protected override int FindCore(PropertyDescriptor property, object key) - { - int count = Count; + OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); + } + //NOTE: Libation does not currently use BindingSource.Find anywhere, + //so this override may be removed (along with SupportsSearchingCore) + protected override int FindCore(PropertyDescriptor property, object key) + { + int count = Count; - System.Collections.IComparer valueComparer = null; + System.Collections.IComparer valueComparer = null; - for (int i = 0; i < count; ++i) - { - T element = this[i]; - var elemValue = element.GetMemberValue(property.Name); - valueComparer ??= element.GetMemberComparer(elemValue.GetType()); + for (int i = 0; i < count; ++i) + { + T element = this[i]; + var elemValue = element.GetMemberValue(property.Name); + valueComparer ??= element.GetMemberComparer(elemValue.GetType()); - if (valueComparer.Compare(elemValue, key) == 0) - { - return i; - } - } + if (valueComparer.Compare(elemValue, key) == 0) + { + return i; + } + } - return -1; - } - } + return -1; + } + } } From 7bdf71a29b969f4717b4c2fab4db58a577f8f358 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 16:33:59 -0600 Subject: [PATCH 22/64] Code Cleanup --- .../BookLiberation/AutomatedBackupsForm.cs | 1 + .../BookLiberation/AutomatedBackupsForm.resx | 59 ------------------- .../BookLiberation/DecryptForm.cs | 1 + .../BookLiberation/DecryptForm.resx | 3 +- .../BookLiberation/DownloadForm.cs | 1 + .../BookLiberation/DownloadForm.resx | 59 ------------------- LibationWinForms/Dialogs/BookDetailsDialog.cs | 1 + .../Dialogs/DirectoryOrCustomSelectControl.cs | 1 + .../Dialogs/IndexLibraryDialog.cs | 1 + .../Dialogs/LibationFilesDialog.cs | 1 + .../Dialogs/Login/ApprovalNeededDialog.cs | 1 + .../Dialogs/Login/AudibleLoginDialog.cs | 1 + .../Dialogs/Login/CaptchaDialog.cs | 1 + .../Dialogs/Login/WinformResponder.cs | 2 + .../Dialogs/Login/_2faCodeDialog.cs | 1 + .../Dialogs/MessageBoxAlertAdminDialog.cs | 1 + .../Dialogs/ScanAccountsDialog.cs | 1 + .../Dialogs/SearchSyntaxDialog.cs | 1 + LibationWinForms/Dialogs/SettingsDialog.cs | 1 + LibationWinForms/Dialogs/SetupDialog.cs | 1 + LibationWinForms/Form1.resx | 3 +- .../LiberateDataGridViewImageButtonColumn.cs | 1 + LibationWinForms/MessageBoxAlertAdmin.cs | 1 + .../MessageBoxWarnIfVerboseLogging.cs | 4 +- LibationWinForms/ProductsGrid.resx | 3 +- LibationWinForms/SortableBindingList2[T].cs | 5 +- 26 files changed, 32 insertions(+), 124 deletions(-) diff --git a/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs b/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs index 85842e28..14a114e7 100644 --- a/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs +++ b/LibationWinForms/BookLiberation/AutomatedBackupsForm.cs @@ -1,5 +1,6 @@ using Dinah.Core.Windows.Forms; using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.BookLiberation diff --git a/LibationWinForms/BookLiberation/AutomatedBackupsForm.resx b/LibationWinForms/BookLiberation/AutomatedBackupsForm.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/BookLiberation/AutomatedBackupsForm.resx +++ b/LibationWinForms/BookLiberation/AutomatedBackupsForm.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/BookLiberation/DecryptForm.cs b/LibationWinForms/BookLiberation/DecryptForm.cs index baaca4c7..d39089e7 100644 --- a/LibationWinForms/BookLiberation/DecryptForm.cs +++ b/LibationWinForms/BookLiberation/DecryptForm.cs @@ -1,5 +1,6 @@ using Dinah.Core.Windows.Forms; using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.BookLiberation diff --git a/LibationWinForms/BookLiberation/DecryptForm.resx b/LibationWinForms/BookLiberation/DecryptForm.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/BookLiberation/DecryptForm.resx +++ b/LibationWinForms/BookLiberation/DecryptForm.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/BookLiberation/DownloadForm.cs b/LibationWinForms/BookLiberation/DownloadForm.cs index e8bcde73..7a260a05 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.cs +++ b/LibationWinForms/BookLiberation/DownloadForm.cs @@ -1,5 +1,6 @@ using Dinah.Core.Windows.Forms; using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.BookLiberation diff --git a/LibationWinForms/BookLiberation/DownloadForm.resx b/LibationWinForms/BookLiberation/DownloadForm.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.resx +++ b/LibationWinForms/BookLiberation/DownloadForm.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/BookDetailsDialog.cs b/LibationWinForms/Dialogs/BookDetailsDialog.cs index 9a415200..04a5d29e 100644 --- a/LibationWinForms/Dialogs/BookDetailsDialog.cs +++ b/LibationWinForms/Dialogs/BookDetailsDialog.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.Dialogs diff --git a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs index c0b5b28c..e30f44c2 100644 --- a/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs +++ b/LibationWinForms/Dialogs/DirectoryOrCustomSelectControl.cs @@ -1,5 +1,6 @@ using FileManager; using System; +using System.Linq; using System.Collections.Generic; using System.Windows.Forms; diff --git a/LibationWinForms/Dialogs/IndexLibraryDialog.cs b/LibationWinForms/Dialogs/IndexLibraryDialog.cs index 0026fe79..1adb538a 100644 --- a/LibationWinForms/Dialogs/IndexLibraryDialog.cs +++ b/LibationWinForms/Dialogs/IndexLibraryDialog.cs @@ -2,6 +2,7 @@ using InternalUtilities; using LibationWinForms.Login; using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.Dialogs diff --git a/LibationWinForms/Dialogs/LibationFilesDialog.cs b/LibationWinForms/Dialogs/LibationFilesDialog.cs index 09ec8096..f827beb8 100644 --- a/LibationWinForms/Dialogs/LibationFilesDialog.cs +++ b/LibationWinForms/Dialogs/LibationFilesDialog.cs @@ -1,5 +1,6 @@ using FileManager; using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.Dialogs diff --git a/LibationWinForms/Dialogs/Login/ApprovalNeededDialog.cs b/LibationWinForms/Dialogs/Login/ApprovalNeededDialog.cs index 0d08e58b..a98c8bda 100644 --- a/LibationWinForms/Dialogs/Login/ApprovalNeededDialog.cs +++ b/LibationWinForms/Dialogs/Login/ApprovalNeededDialog.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.Dialogs.Login diff --git a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs b/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs index 68fb2a99..2e366e88 100644 --- a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs +++ b/LibationWinForms/Dialogs/Login/AudibleLoginDialog.cs @@ -1,6 +1,7 @@ using Dinah.Core; using InternalUtilities; using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.Dialogs.Login diff --git a/LibationWinForms/Dialogs/Login/CaptchaDialog.cs b/LibationWinForms/Dialogs/Login/CaptchaDialog.cs index e9b3becb..ad5264d4 100644 --- a/LibationWinForms/Dialogs/Login/CaptchaDialog.cs +++ b/LibationWinForms/Dialogs/Login/CaptchaDialog.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Drawing; using System.IO; using System.Windows.Forms; diff --git a/LibationWinForms/Dialogs/Login/WinformResponder.cs b/LibationWinForms/Dialogs/Login/WinformResponder.cs index d57a01d7..cc73d713 100644 --- a/LibationWinForms/Dialogs/Login/WinformResponder.cs +++ b/LibationWinForms/Dialogs/Login/WinformResponder.cs @@ -1,6 +1,8 @@ using AudibleApi; using InternalUtilities; using LibationWinForms.Dialogs.Login; +using System; +using System.Linq; namespace LibationWinForms.Login { diff --git a/LibationWinForms/Dialogs/Login/_2faCodeDialog.cs b/LibationWinForms/Dialogs/Login/_2faCodeDialog.cs index 7634c271..4156468a 100644 --- a/LibationWinForms/Dialogs/Login/_2faCodeDialog.cs +++ b/LibationWinForms/Dialogs/Login/_2faCodeDialog.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.Dialogs.Login diff --git a/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs b/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs index 6ef2e320..c4b44d92 100644 --- a/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs +++ b/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.cs @@ -1,5 +1,6 @@ using Dinah.Core; using System; +using System.Linq; using System.Drawing; using System.Windows.Forms; diff --git a/LibationWinForms/Dialogs/ScanAccountsDialog.cs b/LibationWinForms/Dialogs/ScanAccountsDialog.cs index eebd720e..c5453cfb 100644 --- a/LibationWinForms/Dialogs/ScanAccountsDialog.cs +++ b/LibationWinForms/Dialogs/ScanAccountsDialog.cs @@ -1,5 +1,6 @@ using InternalUtilities; using System; +using System.Linq; using System.Collections.Generic; using System.Windows.Forms; diff --git a/LibationWinForms/Dialogs/SearchSyntaxDialog.cs b/LibationWinForms/Dialogs/SearchSyntaxDialog.cs index bd8a2b87..ed897b11 100644 --- a/LibationWinForms/Dialogs/SearchSyntaxDialog.cs +++ b/LibationWinForms/Dialogs/SearchSyntaxDialog.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.Dialogs diff --git a/LibationWinForms/Dialogs/SettingsDialog.cs b/LibationWinForms/Dialogs/SettingsDialog.cs index 81e9c0c8..371385db 100644 --- a/LibationWinForms/Dialogs/SettingsDialog.cs +++ b/LibationWinForms/Dialogs/SettingsDialog.cs @@ -1,6 +1,7 @@ using Dinah.Core; using FileManager; using System; +using System.Linq; using System.IO; using System.Windows.Forms; diff --git a/LibationWinForms/Dialogs/SetupDialog.cs b/LibationWinForms/Dialogs/SetupDialog.cs index 95882edd..f863da84 100644 --- a/LibationWinForms/Dialogs/SetupDialog.cs +++ b/LibationWinForms/Dialogs/SetupDialog.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Windows.Forms; namespace LibationWinForms.Dialogs diff --git a/LibationWinForms/Form1.resx b/LibationWinForms/Form1.resx index 64da6d15..0f6fc673 100644 --- a/LibationWinForms/Form1.resx +++ b/LibationWinForms/Form1.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs index e57e6897..4802fa96 100644 --- a/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs +++ b/LibationWinForms/LiberateDataGridViewImageButtonColumn.cs @@ -2,6 +2,7 @@ using System; using System.Drawing; using System.Windows.Forms; +using System.Linq; namespace LibationWinForms { diff --git a/LibationWinForms/MessageBoxAlertAdmin.cs b/LibationWinForms/MessageBoxAlertAdmin.cs index 5ba8cbde..a1423a7e 100644 --- a/LibationWinForms/MessageBoxAlertAdmin.cs +++ b/LibationWinForms/MessageBoxAlertAdmin.cs @@ -1,5 +1,6 @@ using LibationWinForms.Dialogs; using System; +using System.Linq; namespace LibationWinForms { diff --git a/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs b/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs index c4f04ee6..5e0462bc 100644 --- a/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs +++ b/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs @@ -1,4 +1,6 @@ -using Dinah.Core.Logging; +using System; +using System.Linq; +using Dinah.Core.Logging; using Serilog; using System.Windows.Forms; diff --git a/LibationWinForms/ProductsGrid.resx b/LibationWinForms/ProductsGrid.resx index 8598c799..6ee0745a 100644 --- a/LibationWinForms/ProductsGrid.resx +++ b/LibationWinForms/ProductsGrid.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs index 377cdf86..23f9a6ad 100644 --- a/LibationWinForms/SortableBindingList2[T].cs +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -1,6 +1,7 @@ -using System.Collections.Generic; -using System.ComponentModel; +using System; using System.Linq; +using System.Collections.Generic; +using System.ComponentModel; namespace LibationWinForms { From 19a710e08034593fcec37ee37c1a02430fc56be9 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 16:36:46 -0600 Subject: [PATCH 23/64] Fix xml formatting. --- .../Dialogs/LibationFilesDialog.resx | 3 +- .../Dialogs/Login/ApprovalNeededDialog.resx | 3 +- .../Dialogs/Login/AudibleLoginDialog.resx | 59 ------------------- .../Dialogs/Login/CaptchaDialog.resx | 59 ------------------- LibationWinForms/Dialogs/Login/MfaDialog.resx | 3 +- .../Dialogs/Login/_2faCodeDialog.resx | 59 ------------------- .../Dialogs/MessageBoxAlertAdminDialog.resx | 3 +- LibationWinForms/Properties/Resources.resx | 59 ------------------- 8 files changed, 8 insertions(+), 240 deletions(-) diff --git a/LibationWinForms/Dialogs/LibationFilesDialog.resx b/LibationWinForms/Dialogs/LibationFilesDialog.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/LibationFilesDialog.resx +++ b/LibationWinForms/Dialogs/LibationFilesDialog.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/Login/ApprovalNeededDialog.resx b/LibationWinForms/Dialogs/Login/ApprovalNeededDialog.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/Login/ApprovalNeededDialog.resx +++ b/LibationWinForms/Dialogs/Login/ApprovalNeededDialog.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx b/LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx +++ b/LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/Login/CaptchaDialog.resx b/LibationWinForms/Dialogs/Login/CaptchaDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/Login/CaptchaDialog.resx +++ b/LibationWinForms/Dialogs/Login/CaptchaDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/Login/MfaDialog.resx b/LibationWinForms/Dialogs/Login/MfaDialog.resx index f298a7be..e8ae276d 100644 --- a/LibationWinForms/Dialogs/Login/MfaDialog.resx +++ b/LibationWinForms/Dialogs/Login/MfaDialog.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Dialogs/Login/_2faCodeDialog.resx b/LibationWinForms/Dialogs/Login/_2faCodeDialog.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/Dialogs/Login/_2faCodeDialog.resx +++ b/LibationWinForms/Dialogs/Login/_2faCodeDialog.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.resx b/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.resx index be1522cd..fe34710c 100644 --- a/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.resx +++ b/LibationWinForms/Dialogs/MessageBoxAlertAdminDialog.resx @@ -1,4 +1,5 @@ - + + diff --git a/LibationWinForms/Properties/Resources.resx b/LibationWinForms/Properties/Resources.resx index 70761507..47825a4b 100644 --- a/LibationWinForms/Properties/Resources.resx +++ b/LibationWinForms/Properties/Resources.resx @@ -1,64 +1,5 @@  - From 54d24a7b09cf624face58d68d288526d410224aa Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 16:55:01 -0600 Subject: [PATCH 24/64] Removed WinFormsDesigner because it's not being used. --- Libation.sln | 7 ------- LibationLauncher/LibationLauncher.csproj | 2 +- .../ProcessorAutomationController.cs | 7 ++++--- LibationWinForms/GridEntry.cs | 14 ++++++++------ 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/Libation.sln b/Libation.sln index 9de9110b..191ce50a 100644 --- a/Libation.sln +++ b/Libation.sln @@ -52,8 +52,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApi.Tests", "..\audi 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}") = "Dinah.Core", "..\Dinah.Core\Dinah.Core\Dinah.Core.csproj", "{9E951521-2587-4FC6-AD26-FAA9179FB6C4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.EntityFrameworkCore", "..\Dinah.Core\Dinah.EntityFrameworkCore\Dinah.EntityFrameworkCore.csproj", "{1255D9BA-CE6E-42E4-A253-6376540B9661}" @@ -146,10 +144,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 {9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -232,7 +226,6 @@ Global {7EA01F9C-E579-4B01-A3B9-733B49DD0B60} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05} {111420E2-D4F0-4068-B46A-C4B6DCC823DC} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD} {635F00E1-AAD1-45F7-BEB7-D909AD33B9F6} = {8679CAC8-9164-4007-BDD2-F004810EDA14} - {0807616A-A77A-4B08-A65A-1582B09E114B} = {8679CAC8-9164-4007-BDD2-F004810EDA14} {9E951521-2587-4FC6-AD26-FAA9179FB6C4} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1} {1255D9BA-CE6E-42E4-A253-6376540B9661} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1} {35803735-B669-4090-9681-CC7F7FABDC71} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05} diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 7cda4a7d..973ba77e 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.140 + 5.4.9.141 diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 7496d81a..107f33ed 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -281,14 +281,15 @@ namespace LibationWinForms.BookLiberation #endregion #region Set initially displayed book properties from library info. + decryptDialog.SetTitle(actionName, libraryBook.Book.Title); decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors)); decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames)); decryptDialog.SetCoverImage( - WindowsDesktopUtilities.WinAudibleImageServer.GetImage( + WindowsDesktopUtilities.WinAudibleImageServer + .GetImage( libraryBook.Book.PictureId, - FileManager.PictureSize._80x80 - )); + FileManager.PictureSize._80x80)); #endregion #region define how model actions will affect form behavior diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index ae04e870..b64a9530 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -1,6 +1,7 @@ using ApplicationServices; using DataLayer; using Dinah.Core.Drawing; +using FileManager; using System; using System.Collections; using System.Collections.Generic; @@ -24,19 +25,19 @@ namespace LibationWinForms private Book Book => LibraryBook.Book; private Image _cover; + private PictureDefinition PictureDefinition { get; } public GridEntry(LibraryBook libraryBook) { LibraryBook = libraryBook; - + PictureDefinition = new PictureDefinition(Book.PictureId, PictureSize._80x80); _memberValues = CreateMemberValueDictionary(); //Get cover art. If it's default, subscribe to PictureCached - var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80); - (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); + (bool isDefault, byte[] picture) = PictureStorage.GetPicture(PictureDefinition); if (isDefault) - FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; + PictureStorage.PictureCached += PictureStorage_PictureCached; //Mutable property. Set the field so PropertyChanged isn't fired. _cover = ImageReader.ToImage(picture); @@ -63,8 +64,9 @@ namespace LibationWinForms { if (pictureId == Book.PictureId) { - Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); - FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; + (_, byte[] picture) = PictureStorage.GetPicture(PictureDefinition); + Cover = ImageReader.ToImage(picture); + PictureStorage.PictureCached -= PictureStorage_PictureCached; } } From d5e9e4951704b876a9d8f9fef9cae7de684dc7bb Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 17:06:58 -0600 Subject: [PATCH 25/64] Removed WinFormsDesigner project. --- WinFormsDesigner/Form1.Designer.cs | 345 --- WinFormsDesigner/Form1.cs | 16 - WinFormsDesigner/Form1.resx | 1841 ----------------- WinFormsDesigner/GridEntry.cs | 31 - WinFormsDesigner/ProductsGrid.Designer.cs | 191 -- WinFormsDesigner/ProductsGrid.cs | 27 - WinFormsDesigner/ProductsGrid.resx | 123 -- WinFormsDesigner/Program.cs | 22 - .../WinFormsDesigner.GridEntry.datasource | 10 - WinFormsDesigner/WinFormsDesigner.csproj | 72 - 10 files changed, 2678 deletions(-) delete mode 100644 WinFormsDesigner/Form1.Designer.cs delete mode 100644 WinFormsDesigner/Form1.cs delete mode 100644 WinFormsDesigner/Form1.resx delete mode 100644 WinFormsDesigner/GridEntry.cs delete mode 100644 WinFormsDesigner/ProductsGrid.Designer.cs delete mode 100644 WinFormsDesigner/ProductsGrid.cs delete mode 100644 WinFormsDesigner/ProductsGrid.resx delete mode 100644 WinFormsDesigner/Program.cs delete mode 100644 WinFormsDesigner/Properties/DataSources/WinFormsDesigner.GridEntry.datasource delete mode 100644 WinFormsDesigner/WinFormsDesigner.csproj diff --git a/WinFormsDesigner/Form1.Designer.cs b/WinFormsDesigner/Form1.Designer.cs deleted file mode 100644 index a6bd43d9..00000000 --- a/WinFormsDesigner/Form1.Designer.cs +++ /dev/null @@ -1,345 +0,0 @@ -namespace WinFormsDesigner -{ - partial class Form1 - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - 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; - } -} diff --git a/WinFormsDesigner/Form1.cs b/WinFormsDesigner/Form1.cs deleted file mode 100644 index 1585a517..00000000 --- a/WinFormsDesigner/Form1.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/WinFormsDesigner/Form1.resx b/WinFormsDesigner/Form1.resx deleted file mode 100644 index 2c4b8cf9..00000000 --- a/WinFormsDesigner/Form1.resx +++ /dev/null @@ -1,1841 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - - 132, 17 - - - - - AAABAAUAEBAAAAEAIABoBAAAVgAAACAgAAABACAAqBAAAL4EAABAQAAAAQAgAChCAABmFQAAgIAAAAEA - IAAoCAEAjlcAAAAAAAABACAA4TAAALZfAQAoAAAAEAAAACAAAAABACAAAAAAAAAEAADDDgAAww4AAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////CYmJidkAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+JiYnZ////CQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8y////ZP///3B4eHjzeXl58////3D///9k////Mv// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8PdHR063V1dev///8PAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////D3R0dOt1dXXr////DwAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///w90dHTrdXV16/// - /w8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wr///88d3d38Xh4 - ePH///88////CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///zCfn5/JQEBA/AoK - Cv8KCgr/QEBA/J+fn8n///8w////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///yl0dHTsJSUl/4uL - i/2Kior9ioqK/YqKiv0lJSX/dHR07P///ykAAAAAAAAAAAAAAAAAAAAAAAAAAP///wW0tLSvHBwc/4mJ - if0FBQX/AAAA/wAAAP8FBQX/iYmJ/RwcHP+0tLSv////BQAAAAAAAAAAAAAAAAAAAAD///8aYmJi83t7 - e/4MDAz/AAAA/wAAAP8AAAD/AAAA/wwMDP97e3v+YmJi8////xoAAAAAAAAAAAAAAAAAAAAA////OzEx - Mf6AgID9AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/gICA/TExMf7///86AAAAAAAAAAAAAAAAAAAAAP// - /00bGxv/fn5+/QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/35+fv0bGxv/////TQAAAAAAAAAAAAAAAAAA - AAD///9IISEh/9PT07v///9n////Zv///2b///9m////Zv///2fT09O7ISEh/////0gAAAAAAAAAAAAA - AAAAAAAA////LUNDQ/yfn5/J////CP///wH///8B////Af///wH///8In5+fyUNDQ/z///8tAAAAAAAA - AAAAAAAAAAAAAP///w6Dg4PhUVFR+v///3j///9m////Zv///2b///9m////eFFRUfqDg4Ph////DgAA - AAAAAAAAAAAAAAAAAAD///8B5+fnbiYmJv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8mJib/5+fnbv// - /wEAAAAAAAAAAOAHAADgBwAA/D8AAPw/AAD8PwAA+B8AAOAHAADgBwAAwAMAAMADAADAAwAAwAMAAMAD - AADAAwAAwAMAAMADAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAADDDgAAww4AAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///xT///+WNjY2/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/zY2Nv7///+W////FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////D////341NTX9AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/NTU1/f///37///8PAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8E////MP///4P///+q////r/// - /7D///+5////5gAAAP8AAAD/////5v///7n///+w////r////6r///+D////MP///wQAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8F////Ef// - /xv///8d////Hv///zn///+5AAAA/wAAAP////+5////Of///x7///8d////G////xH///8FAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wH///8B////Hv///7AAAAD/AAAA/////7D///8e////Af///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8e////rwAAAP8AAAD/////r////x7///8BAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///x7///+vAAAA/wAAAP////+v////Hv// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Hv///68AAAD/AAAA//// - /6////8e////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8e////rwAA - AP8AAAD/////r////x7///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /x7///+vAAAA/wAAAP////+v////Hv///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wH///8F////J////7QAAAD/AAAA/////7T///8n////Bf///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wH///8I////Hv///0X///9+////2QAAAP8AAAD/////2f///37///9F////Hv///wj///8BAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8D////GP///1L4+PiipKSk51tbW/sqKir/AAAA/wAAAP8qKir/W1tb+6SkpOf4+Pii////Uv// - /xj///8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////BP///yX///9/qqqq5SIiIv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIi - Iv+qqqrl////f////yX///8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wL///8h////i3Nzc/cBAQH/AAAA/yYmJv+Hh4f9xsbG+ubm5vjm5ub4xsbG+oaG - hv0lJSX/AAAA/wEBAf9zc3P3////i////yL///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Ef///3CBgYHzAAAA/wUFBf+NjY397Ozs+JOTk/1OTk7+Li4u/y4u - Lv9OTk7+k5OT/ezs7PmMjIz9BQUF/wAAAP+BgYHz////cP///xEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///85zMzM0QUFBf8CAgL/sLCw/LGxsfsVFRX/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/FRUV/7GxsfuwsLD8AgIC/wUFBf/MzMzR////Of///wQAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////D////3lZWVn7AAAA/2lpaf7BwcH7BAQE/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/BQUF/8HBwftpaWn+AAAA/1lZWfv///95////DwAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8k6+vruAgICP8EBAT/4uLi+y8v - L/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/Ly8v/+Li4vsEBAT/CAgI/+vr - 67j///8k////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A////0GmpqbmAAAA/0JC - Qv/Hx8f7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/x8fH/EJC - Qv8AAAD/pqam5v///0H///8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8H////YHJy - cvcAAAD/gYGB/YGBgf0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP+BgYH9gYGB/QAAAP9ycnL3////X////wcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wz///94Tk5O/AAAAP+tra38UlJS/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/1JSUv6tra38AAAA/05OTvz///94////DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////D////4c4ODj+AAAA/8fHx/s3Nzf/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/Nzc3/8fHx/sAAAD/OTk5/v///4f///8PAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8R////jjAwMP4AAAD/0NDQ+CwsLP4AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8tLS3+z8/P+AAAAP8wMDD+////jv///xAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///w////+JNjY2/gAAAP/Gxsbq////rv///67///+v////r/// - /6////+v////r////6////+v////r////6////+v////rv///67FxcXqAAAA/zY2Nv7///+J////DwAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////DP///3tKSkr9AAAA/6urq+j///9T////IP// - /x7///8e////Hv///x7///8e////Hv///x7///8e////Hv///x7///8g////VKurq+kAAAD/SkpK/f// - /3v///8MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8I////Y2xsbPgAAAD/g4OD9P// - /1z///8I////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wj///9dgoKC9AAA - AP9sbGz4////Y////wgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///9Fnp6e6QAA - AP9GRkb9////iv///xT///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////FP// - /4pGRkb9AAAA/5+fn+n///9F////BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /ybm5ua9BQUF/wUFBf/f39/N////SP///yD///8e////Hv///x7///8e////Hv///x7///8e////Hv// - /yD///9I39/fzQUFBf8GBgb/5ubmvf///yb///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////EP///3xVVVX8AAAA/2ZmZvz////I////sf///6////+v////r////6////+v////r/// - /6////+v////sf///8hmZmb8AAAA/1RUVPv///98////EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8E////O83NzdIGBgb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8GBgb/zc3N0v///zv///8EAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8W////hpCQkPkAAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/5CQkPn///+G////Fv///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAP4AAH/+AAB//gAAf/8AAP//4Af///AP///wD///8A////AP///wD///4Af//4AB//8A - AP/+AAB//AAAP/wAAD/4AAAf+AAAH/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AA - AA/wAAAP+AAAH/gAAB/4AAAfKAAAAEAAAACAAAAAAQAgAAAAAAAAQAAAww4AAMMOAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////B/// - /yH///9o////wq+vr/kWFhb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/FhYW/6+v - r/n////D////aP///yH///8HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wb///8g////af///8UWFhb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8XFxf/////xf///2n///8g////BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8F////HP///1////+6FhYW/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/FxcX/////7r///9f////Hf///wUAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////BP///xX///9H////l6Wl - pegWFhb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/FhYW/6ioqOj///+X////R/// - /xX///8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wL///8L////KP///17///+Z////wf///9L////Y////2f///9r////a////2v///9v////h////7f// - //oAAAD/AAAA/wAAAP8AAAD/////+v///+3////h////2////9r////a////2v///9n////Y////0v// - /8H///+Z////Xv///yj///8L////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////BP///xD///8p////Sv///2j///96////gv///4T///+F////hf// - /4b///+L////nf///8X////tAAAA/wAAAP8AAAD/AAAA/////+3////F////nf///4v///+G////hf// - /4X///+E////gv///3r///9o////Sv///yn///8Q////BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8E////DP///xb///8h////Kv// - /y7///8v////MP///zD///8x////Ov///1j///+d////4QAAAP8AAAD/AAAA/wAAAP/////h////nf// - /1j///86////Mf///zD///8w////L////y7///8q////If///xb///8M////BP///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wL///8E////B////wn///8K////C////wv///8L////Df///xf///86////i////9sAAAD/AAAA/wAA - AP8AAAD/////2////4v///86////F////w3///8L////C////wv///8K////Cf///wf///8E////AgAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8B////Af///wH///8B////Af///wP///8N////Mf// - /4b////aAAAA/wAAAP8AAAD/AAAA/////9r///+G////Mf///w3///8D////Af///wH///8B////Af// - /wH///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////C////zD///+F////2gAAAP8AAAD/AAAA/wAAAP/////a////hf///zD///8L////AQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wv///8w////hf///9oAAAD/AAAA/wAAAP8AAAD/////2v// - /4X///8w////C////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8L////MP///4X////aAAAA/wAA - AP8AAAD/AAAA/////9r///+F////MP///wv///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////C/// - /zD///+F////2gAAAP8AAAD/AAAA/wAAAP/////a////hf///zD///8L////AQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////Af///wv///8w////hf///9oAAAD/AAAA/wAAAP8AAAD/////2v///4X///8w////C/// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8L////MP///4X////aAAAA/wAAAP8AAAD/AAAA//// - /9r///+F////MP///wv///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////C////zD///+F////2gAA - AP8AAAD/AAAA/wAAAP/////a////hf///zD///8L////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wv///8w////hf///9oAAAD/AAAA/wAAAP8AAAD/////2v///4X///8w////C////wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wH///8L////MP///4X////aAAAA/wAAAP8AAAD/AAAA/////9r///+F////MP// - /wv///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////C////zD///+F////2gAAAP8AAAD/AAAA/wAA - AP/////a////hf///zD///8L////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wv///8w////hf// - /9oAAAD/AAAA/wAAAP8AAAD/////2v///4X///8w////C////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wP///8O////M////4f////aAAAA/wAAAP8AAAD/AAAA/////9r///+H////M////w7///8D////AQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////A////wb///8M////G////0H///+R////3gAAAP8AAAD/AAAA/wAAAP/////e////kf// - /0H///8b////DP///wb///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wH///8E////Cf///xH///8c////K////0P///9r////rv///+cAAAD/AAAA/wAA - AP8AAAD/////5////67///9r////Q////yv///8c////Ef///wn///8E////AQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8J////E////yT///87////WP///3b///+V////tv// - /9v////1AAAA/wAAAP8AAAD/AAAA//////X////b////tv///5X///92////WP///zv///8k////E/// - /wn///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv///z7///9k////jf// - /7H////N2dnZ5piYmPZlZWX9QEBA/wAAAP8AAAD/AAAA/wAAAP9AQED/ZWVl/ZiYmPbZ2dnm////zf// - /7H///+N////ZP///z7///8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wn///8Y////M/// - /1z///+M////uerq6t6IiIj3Kysr/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/ysrK/6IiIj36urq3v///7n///+M////XP///zP///8Y////Cf///wIAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A/// - /wv///8e////Qf///3X///+s8PDw2Xl5efkNDQ3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/w0NDf95eXn58vLy2f///6z///91////Qf// - /x7///8L////AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////A////wv///8h////Sv///4b////Atra28B4eHv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/x4e - Hv+2trbw////wP///4b///9K////If///wv///8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Av///wr///8f////S////439/f3KfHx8+QICAv8AAAD/AAAA/wAA - AP8AAAD/AAAA/wMDA/85OTn/eHh4/qampvzGxsb71tbW+tbW1vrGxsb7pqam/Hh4eP44ODj/AgIC/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AgIC/3x8fPn9/f3L////jf///0z///8f////Cv///wIAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wf///8Z////RP///4n8/PzMYWFh/AAA - AP8AAAD/AAAA/wAAAP8AAAD/FBQU/4GBgf7k5OT6////9/////b////2////9f////X////1////9f// - //b////2////9+Li4vt/f3/+FBQU/wAAAP8AAAD/AAAA/wAAAP8AAAD/YWFh/Pz8/Mz///+J////RP// - /xn///8H////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///8R////Nv// - /3n////Eb29v+gAAAP8AAAD/AAAA/wAAAP8DAwP/c3Nz/vDw8Pn////3////9/Ly8vi1tbX7hISE/WNj - Y/5TU1P+U1NT/mNjY/6EhIT9tbW1+/Pz8/j////3////9/Dw8PlycnL+AwMD/wAAAP8AAAD/AAAA/wAA - AP9ubm76////xP///3n///82////Ef///wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - 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 - e/kAAAD/AAAA/wAAAP9eXl7/////+bq6uvwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP+6urr8////+V1dXf8AAAD/AAAA/wAAAP97e3v5////tP///1j///8b////BQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wj///8l////bv///8g9PT3+AAAA/wAAAP8AAAD/qamp/f////lmZmb+AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/ZmZm/v////moqKj9AAAA/wAAAP8AAAD/Pj4+/v///8j///9t////JP// - /wj///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wH///8L////L////4L7+/vXCgoK/wAAAP8AAAD/AQEB/+jo6Pr////5ISEh/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yEhIf/////56Ojo+wAAAP8AAAD/AAAA/woK - Cv/7+/vW////gv///y////8L////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////D////zr///+T0tLS5wAAAP8AAAD/AAAA/yAg - IP/////55eXl+gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/5eXl+v// - //kgICD/AAAA/wAAAP8AAAD/0tLS5////5P///86////D////wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A////xL///9E////oaur - q/EAAAD/AAAA/wAAAP9MTEz/////+be3t/wAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/7e3t/z////5TExM/wAAAP8AAAD/AAAA/6ysrPH///+h////RP///xL///8DAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wP///8W////Tf///6yOjo73AAAA/wAAAP8AAAD/bm5u/v////mSkpL9AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+SkpL9////+W1tbf4AAAD/AAAA/wAAAP+Ojo73////rP// - /03///8W////AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8E////Gf///1X///+0eHh4+gAAAP8AAAD/AAAA/4iIiP7////4dnZ2/gAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/d3d3/v////iHh4f+AAAA/wAA - AP8AAAD/eHh4+v///7T///9V////Gf///wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Bf///xv///9b////umlpafwAAAD/AAAA/wAA - AP+ampr9////+GRkZP4AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/2Rk - ZP7////4mZmZ/QAAAP8AAAD/AAAA/2lpafz///+6////W////xv///8FAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wX///8c////Xv// - /71gYGD8AAAA/wAAAP8AAAD/oqKi/P////VaWlr+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP9bW1v+////9aGhofwAAAD/AAAA/wAAAP9hYWH8////vf///17///8c////BQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8F////HP///1////+9X19f/AAAAP8AAAD/AAAA/6Ojo/v////tWFhY/QAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/WVlZ/f///+2ioqL7AAAA/wAAAP8AAAD/X19f/P// - /73///9f////HP///wUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Bf///xv///9c////u2VlZfwAAAD/AAAA/wAAAP+dnZ36////3P// - /8z////Q////1////9n////Z////2v///9r////a////2v///9r////a////2v///9r////a////2v// - /9r////a////2v///9r////a////2v///9r////a////2f///9n////X////0P///8z////cnJyc+gAA - AP8AAAD/AAAA/2VlZfz///+7////XP///xv///8FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///8a////V////7ZxcXH6AAAA/wAA - AP8AAAD/jIyM+v///8r///+X////g////4P///+E////hf///4X///+F////hf///4X///+F////hf// - /4X///+F////hf///4X///+F////hf///4X///+F////hf///4X///+F////hf///4X///+E////g/// - /4P///+Y////youLi/oAAAD/AAAA/wAAAP9ycnL6////tv///1f///8Z////BAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8E////F/// - /1D///+vhoaG+AAAAP8AAAD/AAAA/3V1dfv////C////c////0H///8y////MP///zD///8w////MP// - /zD///8w////MP///zD///8w////MP///zD///8w////MP///zD///8w////MP///zD///8w////MP// - /zD///8w////MP///zL///9B////dP///8Nzc3P7AAAA/wAAAP8AAAD/hoaG+P///6////9Q////F/// - /wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////A////xP///9H////paGhofMAAAD/AAAA/wAAAP9VVVX9////x////2////8r////Ev// - /wz///8L////C////wv///8L////C////wv///8L////C////wv///8L////C////wv///8L////C/// - /wv///8L////C////wv///8L////C////wz///8T////LP///3D////HVFRU/QAAAP8AAAD/AAAA/6Ki - ovP///+l////R////xP///8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8Q////Pf///5jGxsbrAAAA/wAAAP8AAAD/LCws/v// - /9H///97////Lf///wz///8D////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af// - /wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8D////DP///y7///98////0iws - LP8AAAD/AAAA/wAAAP/Hx8fr////l////z3///8Q////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////DP///zL///+H8/Pz3AMD - A/8AAAD/AAAA/wQEBP/x8fHh////kP///zr///8Q////AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A/// - /xD///87////kfHx8eEDAwP/AAAA/wAAAP8EBAT/8/Pz2////4f///8y////DP///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wn///8n////c////8wuLi7+AAAA/wAAAP8AAAD/s7Oz8v///6n///9P////Gf///wUAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wX///8Z////T////6qysrLyAAAA/wAAAP8AAAD/Ly8v/v///8z///9z////J/// - /wn///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8G////Hf///13///+5bGxs+wAAAP8AAAD/AAAA/2lpafz////D////bP// - /yj///8L////A////wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af// - /wH///8B////Af///wH///8B////Af///wP///8L////KP///23////EZ2dn/AAAAP8AAAD/AAAA/21t - bfv///+5////Xf///x3///8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A////xT///9H////oLW1te8AAAD/AAAA/wAA - AP8WFhb/+Pj43v///5L///9F////HP///w////8M////C////wv///8L////C////wv///8L////C/// - /wv///8L////C////wv///8L////C////wv///8L////C////wz///8P////HP///0X///+S9vb23hUV - Ff8AAAD/AAAA/wAAAP+2trbv////oP///0b///8U////AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8N////Mv// - /4L6+vrWFhYW/wAAAP8AAAD/AAAA/56envf///++////dv///0f///81////Mf///zD///8w////MP// - /zD///8w////MP///zD///8w////MP///zD///8w////MP///zD///8w////MP///zD///8x////Nf// - /0f///92////vp2dnfcAAAD/AAAA/wAAAP8WFhb/+vr61v///4L///8y////Df///wIAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////B////yH///9h////unJycvoAAAD/AAAA/wAAAP8mJib/+Pj45v///7v///+Y////iv// - /4b///+F////hf///4X///+F////hf///4X///+F////hf///4X///+F////hf///4X///+F////hf// - /4X///+F////hv///4r///+Y////u/j4+OYlJSX/AAAA/wAAAP8AAAD/c3Nz+v///7n///9h////If// - /wf///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///8T////Qv///5Xd3d3kBgYG/wAAAP8AAAD/AAAA/4CA - gP3////t////4f///9v////a////2v///9r////a////2v///9r////a////2v///9r////a////2v// - /9r////a////2v///9r////a////2v///9r////b////4f///+1+fn79AAAA/wAAAP8AAAD/BgYG/97e - 3uP///+V////Qf///xP///8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Cv///yj///9r////v2lp - afsAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/2pqavv///+/////a////yf///8K////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wX///8W////RP///5Tr6+veGBgY/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/xkZGf/r6+ve////lP///0T///8W////BQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8C////DP///yr///9r////vbW1tfYDAwP/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wMDA/+2trb2////vf///2v///8q////DP// - /wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wf///8c////UP///6D////jkJCQ/QAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+RkZH9////4/// - /6D///9Q////HP///wf///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD//AAAAAA////8AAAAAD////wAAAAAP////AAAAAA////8AAAAAD////4AAAAAf////gAAAAB///// - gAAAAf/////gAAAH//////+AAf///////4AB////////gAH///////+AAf///////4AB////////gAH/ - //////+AAf///////4AB////////gAH///////+AAf///////4AB////////AAD///////wAAD////// - 8AAAD//////AAAAD/////4AAAAH/////AAAAAP////4AAAAAf////AAAAAA////4AAAAAB////AAAAAA - D///8AAAAAAP///gAAAAAAf//+AAAAAAB///wAAAAAAD///AAAAAAAP//4AAAAAAAf//gAAAAAAB//+A - AAAAAAH//4AAAAAAAf//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAA - AP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8A - AAAAAAD//wAD///AAP//AAP//8AA//+AAAAAAAH//4AAAAAAAf//gAAAAAAB//+AAAAAAAH//8AAAAAA - A///wAAAAAAD///gAAAAAAf//+AAAAAAB///4AAAAAAH/ygAAACAAAAAAAEAAAEAIAAAAAAAAAABAMMO - AADDDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////BP///wr///8X////LP///0////99////rf///9T////u2dnZ+05OTv8LCwv/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/CwsL/05OTv/Z2dn8////7v///9X///+t////fv///1D///8s////F////wr///8E////AQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8E////Cv///xb///8s////Uf// - /4H///+w////19bW1vQRERH/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xEREf/X19f0////2P// - /7H///+B////Uf///yz///8W////Cv///wT///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////Af///wP///8K////Fv///yz///9S////gv///7L////ZTExM/QAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/01NTf3////Z////sv///4L///9S////LP///xb///8K////A/// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A////wr///8V////Kv// - /1D///+A////sf///9gLCwv/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DAwM//// - /9j///+x////gP///1D///8q////Ff///wr///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wH///8D////Cf///xT///8o////TP///3v///+r////0wsLC/8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8MDAz/////0////6v///97////TP///yj///8U////Cf// - /wP///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8I////Ev// - /yT///9F////cf///6D////JSkpK/AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/01N - Tfz////J////oP///3H///9F////JP///xL///8I////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8P////H////zv///9i////jv///7jPz8/jEBAQ/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8RERH/0tLS4v///7j///+O////Y////zv///8f////D/// - /wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Bf// - /wz///8Z////L////1D///93////nv///8HPz8/jSUlJ/AwMDP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8MDAz/SkpK/NLS - 0uP////B////nv///3f///9Q////L////xn///8M////Bf///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////Cf///xL///8j////PP///1z///9/////oP// - /7r////N////2v///+H////l////5////+j////p////6f///+n////p////6f///+n////p////6f// - /+n////q////7P///+7////y////9/////r////9AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP/////9////+v////f////y////7v///+z////q////6f///+n////p////6f///+n////p////6f// - /+n////p////6P///+f////l////4f///9r////N////uv///6D///9/////XP///zz///8j////Ev// - /wn///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wL///8G////Df///xj///8q////Qf///13///95////kv///6b///+1////vv///8T////I////yf// - /8r////K////yv///8r////K////yv///8v////L////zP///87////S////2P///+H////r////9P// - //oAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//////r////0////6////+H////Y////0v// - /87////M////y////8v////K////yv///8r////K////yv///8r////J////yP///8T///++////tf// - /6b///+S////ef///13///9B////Kv///xj///8N////Bv///wL///8BAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////D////xr///8q////Pf// - /1L///9n////eP///4b///+Q////l////5v///+d////nv///57///+e////nv///57///+e////n/// - /6D///+h////pf///6z///+3////yP///9v////r////9wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/////9////+v////b////yP///7f///+s////pf///6H///+g////n////57///+e////nv// - /57///+e////nv///53///+b////l////5D///+G////eP///2f///9S////Pf///yr///8a////D/// - /wj///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////Av///wT///8J////D////xn///8k////Mf///z////9L////Vv///17///9k////aP// - /2r///9r////a////2v///9r////bP///2z///9s////bf///3D///92////gP///5H///+r////yP// - /+H////yAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////y////4f///8j///+r////kf// - /4D///92////cP///23///9s////bP///2z///9r////a////2v///9r////av///2j///9k////Xv// - /1b///9L////P////zH///8k////Gf///w////8J////BP///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av///wT///8I////Df// - /xP///8a////Iv///yn///8v////Nf///zn///87////Pf///z7///8+////P////z////8/////P/// - /z////9B////RP///0z///9Z////b////5H///+3////2P///+4AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/////+7////Y////t////5H///9v////Wf///0z///9E////Qf///z////8/////P/// - /z////8/////Pv///z7///89////O////zn///81////L////yn///8i////Gv///xP///8N////CP// - /wT///8C////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8B////Av///wP///8G////Cf///w3///8R////Ff///xj///8b////Hv// - /x////8g////If///yH///8h////Iv///yL///8i////Iv///yT///8o////MP///0D///9Z////gP// - /6z////S////7AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////7P///9L///+s////gP// - /1n///9A////MP///yj///8k////Iv///yL///8i////Iv///yH///8h////If///yD///8f////Hv// - /xv///8Y////Ff///xH///8N////Cf///wb///8D////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Af// - /wL///8E////Bf///wj///8J////C////w3///8O////D////w////8Q////EP///xD///8Q////EP// - /xD///8R////E////xf///8g////MP///0z///92////pf///87////qAAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP/////q////zv///6X///92////TP///zD///8g////F////xP///8R////EP// - /xD///8Q////EP///xD///8Q////D////w////8O////Df///wv///8J////CP///wX///8E////Av// - /wH///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wH///8C////Av///wP///8E////Bf// - /wX///8G////Bv///wb///8G////Bv///wb///8G////Bv///wf///8J////Dv///xf///8o////RP// - /3D///+h////zP///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////M////of// - /3D///9E////KP///xf///8O////Cf///wf///8G////Bv///wb///8G////Bv///wb///8G////Bv// - /wX///8F////BP///wP///8C////Av///wH///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8B////Af///wH///8B////Av///wL///8C////Av///wL///8C////Av// - /wL///8C////A////wX///8J////E////yT///9B////bf///6D////L////6QAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/////6f///8v///+g////bf///0H///8k////E////wn///8F////A/// - /wL///8C////Av///wL///8C////Av///wL///8C////Av///wH///8B////Af///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8B////A////wf///8R////Iv// - /z////9s////n////8v////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////y/// - /5////9s////P////yL///8R////B////wP///8B////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wH///8C////Bv///xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////K////nv///2z///8/////Iv///xD///8G////Av// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP// - /yL///8/////bP///57////K////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f// - /8r///+e////bP///z////8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////yv///57///9s////P////yL///8Q////Bv// - /wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv// - /xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//// - /+n////K////nv///2z///8/////Iv///xD///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP///yL///8/////bP///57////K////6QAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f///8r///+e////bP///z////8i////EP// - /wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av// - /wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP/////p////yv///57///9s////P////yL///8Q////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv///xD///8i////P////2z///+e////yv// - /+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////K////nv///2z///8/////Iv// - /xD///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wL///8G////EP///yL///8/////bP///57////K////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/////6f///8r///+e////bP///z////8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv///z////9s////nv// - /8r////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////yv///57///9s////P/// - /yL///8Q////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8C////Bv///xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/////+n////K////nv///2z///8/////Iv///xD///8G////AgAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP///yL///8/////bP// - /57////K////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f///8r///+e////bP// - /z////8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////Av///wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP/////p////yv///57///9s////P////yL///8Q////Bv///wIAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv///xD///8i////P/// - /2z///+e////yv///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////K////nv// - /2z///8/////Iv///xD///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wL///8G////EP///yL///8/////bP///57////K////6QAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/////6f///8r///+e////bP///z////8i////EP///wb///8CAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv// - /z////9s////nv///8r////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////yv// - /57///9s////P////yL///8Q////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8C////Bv///xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////K////nv///2z///8/////Iv///xD///8G////AgAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP// - /yL///8/////bP///57////K////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f// - /8r///+e////bP///z////8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////yv///57///9s////P////yL///8Q////Bv// - /wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv// - /xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//// - /+n////K////nv///2z///8/////Iv///xD///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP///yL///8/////bP///57////K////6QAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f///8r///+e////bP///z////8i////EP// - /wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av// - /wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP/////p////yv///57///9s////P////yL///8Q////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////B////xH///8i////P////2z///+f////y/// - /+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////L////n////2z///8/////Iv// - /xH///8H////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wP///8I////Ev///yP///9A////bf///6D////L////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/////6f///8v///+g////bf///0D///8j////Ev///wj///8D////AQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wH///8C////Bf///wr///8V////Jv///0P///9w////of// - /8z////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////zP///6H///9w////Q/// - /yb///8V////Cv///wX///8C////Af///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Af///wL///8C////BP// - /wb///8J////EP///xv///8s////Sf///3X///+l////zv///+oAAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/////+r////O////pf///3X///9J////LP///xv///8Q////Cf///wb///8E////Av// - /wL///8B////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////Af///wL///8D////Bf///wf///8K////Dv///xL///8a////Jv///zn///9V////f/// - /6v////S////7AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////7P///9L///+r////f/// - /1X///85////Jv///xr///8S////Dv///wr///8H////Bf///wP///8C////Af///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av///wP///8E////Bv///wn///8N////Ef// - /xb///8b////Iv///yz///86////Tf///2n///+P////t////9n////vAAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP/////v////2f///7f///+P////af///03///86////LP///yL///8b////Fv// - /xH///8N////Cf///wb///8E////A////wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av// - /wP///8E////B////wr///8P////FP///xr///8h////Kf///zP///8+////S////1v///9v////if// - /6j////I////4v////MAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//////P////i////yP// - /6j///+J////b////1v///9L////Pv///zP///8p////If///xr///8U////D////wr///8H////BP// - /wP///8C////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8B////Af///wL///8E////B////wv///8Q////Fv///x3///8m////MP// - /zz///9J////V////2b///92////h////5n///+u////xf///9v////s////9wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/////9////+z////b////xf///67///+Z////h////3b///9m////V/// - /0n///88////MP///yb///8d////Fv///xD///8L////B////wT///8C////Af///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8D////Bv// - /wr///8P////Fv///x7///8o////Nf///0P///9T////ZP///3X///+G////lv///6X///+0////wv// - /9D////f////7P////X////7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////7////9f// - /+z////f////0P///8L///+0////pf///5b///+G////df///2T///9T////Q////zX///8o////Hv// - /xb///8P////Cv///wb///8D////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wH///8D////Bf///wj///8N////FP///x3///8o////Nv///0b///9Y////bP// - /4D///+T////pP///7T////C////zv///9n////i////6uLi4vO1tbX6jo6O/m9vb/8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/29vb/+Ojo7+tbW1+uPj4/P////q////4v///9n////O////wv// - /7T///+k////k////4D///9s////WP///0b///82////KP///x3///8U////Df///wj///8F////A/// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////A////wb///8L////Ef// - /xn///8k////M////0T///9Y////bv///4T///+a////rv///7/////O////2f///+Pd3d3ukpKS+VZW - Vv0aGhr/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/xoaGv9XV1f9kpKS+d3d3e7////j////2f///87///+/////rv///5r///+E////bv// - /1j///9E////M////yT///8Z////Ef///wv///8G////A////wL///8BAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////Av///wT///8I////Df///xX///8f////Lf///z7///9T////av///4P///+b////sf// - /8T////U////4N/f3+1/f3/6LCws/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/y0t - Lf6AgID639/f7f///+D////U////xP///7H///+b////g////2r///9T////Pv///y3///8f////Ff// - /w3///8I////BP///wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8F////Cf///w////8Y////JP// - /zX///9K////Yf///3v///+W////rv///8T////W////47W1tfRKSkr+AgIC/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8CAgL/S0tL/rW1tfT////j////1v// - /8T///+u////lv///3v///9h////Sv///zX///8k////GP///w////8J////Bf///wL///8BAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wH///8C////Bf///wr///8R////G////yn///88////U////2////+L////pv///7/////T////4rGx - sfQxMTH+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/zIyMv6ysrL0////4v///9P///+/////pv///4v///9v////VP// - /zz///8p////G////xH///8K////Bf///wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av///wb///8L////Ev///x3///8u////Qv// - /1z///95////l////7T////L////3s7OzvE+Pj7+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8+Pj7+zs7O8f///97////L////tP///5f///95////XP///0P///8u////Hf///xL///8L////Bv// - /wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wL///8F////C////xP///8f////MP///0f///9j////gv///6L///++////1fLy8uhxcXH7AwMD/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8DAwP/cXFx+/Ly8uj////V////vv// - /6L///+C////Y////0j///8w////H////xP///8L////Bv///wL///8BAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////Bf///wr///8T////H////zL///9K////aP// - /4j///+p////xv///9zLy8vxKSkp/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/KSkp/8vLy/H////c////xv///6n///+J////aP///0r///8y////H/// - /xP///8K////Bf///wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av// - /wT///8K////Ev///x7///8x////Sv///2r///+M////rv///8v////hlJSU+AgICP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/EBAQ/z4+ - Pv9kZGT+goKC/pqamv2qqqr8sLCw/LCwsPyqqqr8mpqa/YODg/5kZGT+Pj4+/xAQEP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/CAgI/5SU - lPj////h////y////67///+M////av///0v///8y////Hv///xL///8K////BP///wL///8BAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8E////CP///xD///8c////L////0n///9p////jf// - /7D////O+vr65GlpafwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wsLC/9NTU3/lZWV/dXV1fz9/f35////+f////j////3////9/////f////2////9v// - //f////3////9/////j////5/f39+dbW1vyVlZX+S0tL/wkJCf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/2lpafz6+vrk////zv///7D///+N////av// - /0n///8v////Hf///xD///8I////BP///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// - /wf///8O////Gv///yz///9F////Zv///4v///+v////zvX19edOTk79AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/zw8PP+hoaH98fHx+v////n////4////9/// - //b////2////9f////X////0////9P////T////0////9P////T////1////9f////b////2////9/// - //j////57u7u+p2dnf04ODj/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/05OTv319fXn////zv///7D///+L////Zv///0b///8s////Gv///w7///8H////A/// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8F////DP///xb///8n////QP///2D///+G////rP// - /8339/flRkZG/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AQEB/1FR - Uf/Kysr9////+f////j////3////9v////b////1////9f////b////2////9v////b////2////9v// - //b////2////9v////b////2////9v////b////2////9v////b////3////+P////rIyMj9UVFR/wEB - Af8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/0VFRf739/fm////zf// - /6z///+G////YP///0D///8n////Fv///wz///8F////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////BP// - /wn///8S////If///zj///9Y////fv///6X////I/Pz84k9PT/0AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/zo6Ov/Hx8f9////+f////j////3////9v////b////2////9/// - //f////4////+fX19fnV1dX7u7u7/KqqqvykpKT8pKSk/Kqqqvy7u7v81dXV+/T09Pn////5////+P// - //f////3////9v////b////2////9/////j////6x8fH/Tg4OP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/09PT/38/Pzj////yf///6b///9+////WP///zj///8h////Ev// - /wn///8E////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8H////D////xv///8w////Tv///3P///+c////wv// - /991dXX7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/w0NDf+YmJj+/f39+v// - //j////3////9v////b////3////+P39/fnPz8/7i4uL/VJSUv8gICD/AQEB/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AQEB/x8fH/9RUVH/i4uL/dHR0fz////5////+P////f////2////9v// - //f////4/f39+pSUlP4MDAz/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/3R0 - dPv////f////wv///5z///9z////Tv///zD///8b////D////wf///8D////AQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bf// - /wv///8W////J////0L///9m////kP///7j////Yra2t9QEBAf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8wMDD/2tra/P////n////4////9/////f////3////+dnZ2ft4eHj+Hx8f/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/yEhIf94eHj+2dnZ+/////n////4////9/////f////4////+djY2PwwMDD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/6ysrPX////Y////uP///5D///9m////Qv// - /yf///8W////C////wX///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////EP///x////82////V////4H///+r////z+Li - 4usUFBT/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/VlZW//Pz8/v////5////9/// - //f////4////+cfHx/xMTEz/AgIC/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8CAgL/TExM/8fH - x/z////5////+P////f////3////+fT09PtWVlb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/FBQU/+Li4uv////P////q////4H///9X////Nv///x////8Q////CP///wP///8BAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bf// - /wz///8X////Kv///0j///9v////m////8P////hUVFR/QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/25ubv/8/Pz6////+P////f////3////+OLi4vtYWFj+AQEB/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AQEB/1hYWP7i4uL7////+P////f////3////+Pz8 - /PpsbGz/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/UFBQ/f///+H////D////m/// - /2////9I////Kv///xf///8M////Bf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////Ef///yD///85////Xf///4j///+z////1rCw - sPQAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9ra2v//f39+v////j////3////9/// - //menp79Dw8P/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/w8PD/+enp79////+f////f////3////+P39/fpra2v/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/r6+v9P///9b///+z////iP///13///85////IP///xH///8I////A/// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bf// - /wv///8X////K////0r///9z////oP///8j6+vrlKSkp/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/Tk5O//z8/Pv////4////9/////j4+Pj5YmJi/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9iYmL++Pj4+f// - //j////3////+Pz8/PtOTk7/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8oKCj/+vr65f// - /8j///+g////c////0r///8r////F////wv///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8H////EP///x////84////Xf///4n///+1////2ZeX - l/gAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIiIv/w8PD7////+f////f////49PT0+kRE - RP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9ERET/9PT0+v////j////3////+fDw8PsiIiL/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+Wlpb4////2f///7X///+J////Xf///zj///8f////EP// - /wf///8C////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////BP// - /wr///8W////KP///0f///9x////n////8j6+vrmIiIi/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8CAgL/w8PD/f////n////4////+Pj4+PpISEj/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP9JSUn/+fn5+v////j////4////+cPDw/0CAgL/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIi - Iv/6+vrm////yP///5////9x////R////yj///8V////Cv///wT///8BAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////Dv///xz///80////WP///4X///+z////16Ki - ovcAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/2dnZ//////6////+P////f////5bGxs/gAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9tbW3+////+f////f////4////+mdn - Z/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/6Ghoff////X////sv///4X///9Y////NP// - /xz///8O////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// - /wj///8T////JP///0H///9q////mP///8P////jNjY2/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8ODg7/6enp+/////n////3////+bGxsf0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP+xsbH9////+f////f////56enp+w4ODv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/NjY2/v///+P////D////mP///2n///9B////JP///xP///8I////A////wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8E////C////xj///8t////T////3z///+r////0snJ - yfEAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/39/f/7////6////+P////jw8PD6GRkZ/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xkZGf/w8PD6////+P// - //j////6f39//gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/ycnJ8f///9L///+q////e/// - /0////8t////F////wv///8E////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av// - /wb///8P////Hv///zf///9e////jf///7r////ea2tr/AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8ODg7/7e3t+/////j////4////+XZ2dv4AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/3Z2dv7////5////+P////jt7e37Dg4O/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP9ra2v8////3f///7r///+M////Xf///zf///8d////D////wb///8CAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////CP///xP///8k////Q////23///+d////yPr6 - +ucUFBT/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/25ubv/////6////+P////jn5+f7CgoK/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/CQkJ/+fn - 5/v////4////+P////pubm7/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xQUFP/6+vrn////yP// - /53///9t////Qv///yT///8S////CP///wP///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wT///8L////F////yz///9P////fP///6z////Uubm59QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/1tbW/P////n////4////+Xd3d/4AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/d3d3/v////n////4////+dbW1vwAAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/7m5ufX////T////rP///3z///9O////LP///xf///8K////BP// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bf///w3///8c////NP///1v///+L////uf// - /91paWn8AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/zg4OP/////6////+P////j19fX6FBQU/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8UFBT/9fX1+v////j////4////+jg4OP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/aWlp/P// - /93///+5////iv///1v///80////G////w3///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wL///8H////Ef///yH///89////aP///5n////F////5SIiIv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/kZGR/v////r////4////+aOjo/0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+jo6P9////+f////j////6kZGR/gAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8iIiL/////5f///8X///+Y////Z////z3///8h////EP// - /wf///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A////wn///8U////J////0f///90////pf// - /8/Y2NjwAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wEBAf/i4uL7////+f////j////6SUlJ/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/0lJSf/////6////+P////nh4eH8AQEB/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP/Y2Njv////z////6X///90////Rv///yb///8U////CP///wP///8BAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wH///8E////Cv///xf///8t////Uf///4D///+w////15eXl/gAAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/MTEx//////r////5////+e/v7/sFBQX/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/BQUF/+7u7vv////5////+f// - //oxMTH/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/5iYmPj////X////sP///4D///9Q////LP// - /xf///8K////BP///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wX///8N////G////zT///9b////i/// - /7r////eXV1d/QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP93d3f/////+v////j////5p6en/QAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/pqam/f////n////4////+nZ2dv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/Xl5e/f///97///+6////i////1r///8z////G////wz///8F////AQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8C////Bv///w////8f////Ov///2T///+W////w////+QmJib/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/7a2tv3////5////+P////pjY2P/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9iYmL/////+v// - //j////5tbW1/QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8nJyf/////5P///8P///+W////ZP// - /zr///8f////D////wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8H////Ef///yP///9B////bv// - /5/////L8PDw6gICAv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8CAgL/7+/v+/////n////4////+iIi - Iv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIiIv/////6////+f////nu7u77AgIC/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wICAv/w8PDq////y////5////9t////Qf///yL///8R////B////wIAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////A////wj///8U////J////0j///93////qP///9HBwcHzAAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/ygoKP/////6////+f////no6Oj7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/+fn - 5/v////5////+f////snJyf/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/8HBwfP////R////qP// - /3b///9I////Jv///xT///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////Cv///xb///8r////T/// - /3////+w////15eXl/kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/Wlpa//////r////4////+bKy - sv0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/srKy/f////n////4////+llZWf8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/mJiY+P///9f///+v////fv///07///8q////Fv///wr///8D////AQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////Af///wT///8L////GP///y////9V////hv///7b////ccHBw+wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP+Dg4P+////+v////j////5hYWF/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP+FhYX+////+f////j////6goKC/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9xcXH7////3P// - /7b///+G////Vf///y////8Y////C////wT///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Bf///wz///8a////Mv// - /1v///+N////vP///+BPT0/+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/6ysrP3////5////+P// - //pbW1v/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/1tbW//////6////+P////mqqqr9AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/1BQUP7////g////vP///43///9b////Mv///xr///8M////Bf// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wL///8F////Df///x3///82////YP///5L////B////4zIyMv4AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/zc3N/f////n////4////+jc3N/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/ODg4//////r////4////+czMzP0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/MjIy/v// - /+P////B////kv///2D///82////Hf///w3///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8P////Hv// - /zr///9l////mP///8X////mFhYW/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/t7e37////+f// - //n////6FhYW/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8WFhb/////+v////j////56+vr+wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8XFxf/////5v///8X///+Y////Zf///zr///8e////D/// - /wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8C////Bv///w////8g////Pf///2r///+c////yfz8/OgDAwP/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/CAgI//////v////5////+fj4+PsBAQH/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wEBAf/5+fn6////+P////j9/f37BwcH/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wMD - A//8/Pzo////yf///5z///9q////Pf///yD///8P////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////Ef// - /yL///9A////bv///6D////M6enp7AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8eHh7/////+v// - //j////54eHh+wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/+Pj4/v////4////+P// - //odHR3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/+rq6uz////M////oP///27///9A////Iv// - /xH///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Av///wf///8S////I////0P///9x////o////87Z2dnvAAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/y8vL//////6////+P////jQ0ND9AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/0dHR/P////j////4////+i4uLv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/2dnZ7////87///+j////cf///0P///8j////Ev///wf///8CAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////B/// - /xL///8k////RP///3T///+m////0M3NzfIAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/PT09//// - //r////3////+MHBwfwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/CwsL8////+P// - //f////6PDw8/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/Ozs7y////0P///6b///90////RP// - /yT///8S////B////wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8I////Ev///yX///9G////df///6f////Rw8PD8wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9GRkb/////+f////b////2t7e3+wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/7i4uPv////2////9v////lERET/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/8TExPP////R////p////3X///9G////Jf///xL///8I////Av///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// - /wj///8T////Jv///0f///92////qP///9LAwMD0AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/0xM - TP/////4////9P////SxsbH6AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/srKy+v// - //T////0////+EpKSv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/wcHB8////9L///+o////dv// - /0f///8l////E////wj///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////CP///xP///8m////R////3b///+o////0r29 - vfQAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/TU1N//////b////x////76+vr/cAAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+xsbH3////7/////H////2S0tL/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP++vr70////0v///6j///92////R////yb///8T////CP///wP///8BAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wP///8I////Ev///yX///9H////dv///6j////SwMDA9AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP9KSkr+////9P///+3////nsbGx8gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/7Ky - svL////n////7f////RISEj+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/8HBwfP////S////qP// - /3b///9H////Jf///xL///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av///wj///8S////Jf///0b///91////p/// - /9HHx8fzAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/0JCQv7////y////5////9z////X////2P// - /93////i////5v///+f////o////6f///+n////p////6f///+n////p////6f///+n////p////6f// - /+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f// - /+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f// - /+n////p////6P///+f////m////4v///93////Y////1////9z////n////8kBAQP4AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/yMjI8v///9H///+n////df///0X///8k////Ev///wj///8C////AQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wH///8C////B////xL///8k////RP///3P///+l////0M/Pz/IAAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/OTk5//////D////g////z////8L///+9////v////8L////G////yP///8n////K////yv// - /8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv// - /8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv// - /8r////K////yv///8r////K////yv///8r////K////yv///8r////J////yP///8b////C////v/// - /73////C////z////+D////wNzc3/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/Q0NDy////z/// - /6X///9z////RP///yT///8S////B////wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8H////Ef///yP///9C////cP// - /6L////N3t7e7gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8pKSn/////7v///9r////C////rP// - /53///+Y////mP///5r///+c////nf///57///+e////nv///57///+e////nv///57///+e////nv// - /57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv// - /57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv// - /57///+e////nv///53///+c////mv///5n///+Y////nf///6z////C////2v///+4nJyf/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/9/f3+3////N////ov///3D///9C////I////xH///8H////AgAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAA////Av///wb///8Q////Iv///z////9s////n////8vv7+/qAAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/xYWFv/////t////1v///7f///+X////fv///3H///9r////av///2r///9q////a/// - /2v///9r////a////2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP// - /2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP// - /2z///9s////bP///2z///9s////bP///2z///9r////a////2v///9r////av///2r///9q////bP// - /3H///9+////mP///7f////W////7RQUFP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/8PDw6v// - /8v///+f////bP///z////8h////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv///w////8g////PP// - /2n///+b////yP///+cICAj/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AgIC//r6+u3////U////sf// - /4n///9m////UP///0b///9A////Pv///z7///8+////P////z////8/////P////z////8/////P/// - /z////8/////P////z////8/////P////z////8/////P////z////8/////P////z////8/////P/// - /z////8/////P////z////8/////P////z////8/////P////z////8/////P////z////8/////P/// - /z////8/////P////z7///8+////Pv///0D///9G////Uf///2f///+J////sf///9T4+PjtAQEB/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wkJCf/////n////x////5v///9o////PP///x////8P////Bv// - /wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wL///8F////Dv///x7///85////ZP///5b////E////5SAgIP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/4ODg8f///9X///+v////g////1r///8+////L////yb///8j////Iv// - /yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv// - /yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv// - /yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8j////Jv// - /y////8/////W////4T///+w////1d3d3fEAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/IiIi//// - /+X////D////lv///2P///85////Hv///w7///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wX///8N////HP// - /zX///9e////kP///7/////iOzs7/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+9vb31////1/// - /7H///+D////V////zb///8k////Gf///xP///8R////EP///xD///8Q////EP///xD///8Q////EP// - /xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP// - /xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP// - /xD///8Q////EP///xD///8Q////Ef///xT///8Z////JP///zf///9Y////hP///7L////Yu7u79QAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP88PDz+////4v///7////+Q////Xv///zX///8c////Df// - /wX///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8B////BP///wz///8a////Mf///1n///+K////uv///95cXFz9AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/5mZmfn////b////tv///4j///9Y////NP///x////8S////C/// - /wj///8H////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv// - /wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv// - /wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wf///8I////C/// - /xL///8f////Nf///1n///+J////t////9yXl5f6AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/15e - Xv3////e////uv///4r///9Z////Mf///xr///8M////BP///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8E////Cv// - /xf///8t////U////4P///+0////2n5+fvoAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/b29v/P// - /+D///+9////j////17///83////Hv///xD///8I////BP///wP///8C////Av///wL///8C////Av// - /wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av// - /wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av// - /wL///8C////Av///wL///8C////A////wT///8I////Ef///x////84////YP///5D///++////4W1t - bfwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/gICA+v///9r///+0////g////1L///8t////F/// - /wr///8E////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8J////Ff///yn///9M////e////6z////VqKio9gAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9CQkL+////5f///8X///+Y////Z////z3///8h////Ef// - /wf///8D////Af///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8B////A/// - /wj///8R////Iv///z7///9o////mf///8b////lPz8//gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP+qqqr2////1f///6z///97////TP///yn///8V////Cf///wP///8BAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// - /wj///8T////Jf///0X///9z////pP///8/V1dXxAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/w8P - D//8/Pzq////zf///6L///9y////Rf///yb///8U////Cf///wP///8BAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////Cf///xT///8m////Rv///3P///+k////zfv7 - ++oNDQ3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/9fX1/D////O////pP///3L///9F////JP// - /xP///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv///xD///8h////Pv///2n///+b////yPz8 - /OcNDQ3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/9DQ0PP////V////rv///37///9Q////Lf// - /xj///8L////BP///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wT///8L////GP///y3///9Q////f////67////Wzs7O9AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8PDw///Pz85////8f///+b////af///z7///8h////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wL///8F////Dv///x3///83////YP///5H///+/////4kFBQf4AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/kJCQ+v///93///+5////i////1z///81////Hf///w7///8G////Av///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////Bv///w////8d////Nv///1z///+M////uv// - /96NjY36AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/0JCQv7////h////v////5H///9f////N/// - /x3///8O////Bf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wT///8L////Gf///zD///9W////hv// - /7b////beXl5+wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9MTEz+////5f///8X///+a////av// - /0D///8k////E////wn///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wP///8J////E////yT///9B////av///5r////G////5UlJSf4AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/enp6+////9v///+1////hf///1b///8w////Gf///wv///8E////AQAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////A////wr///8V////Kf///0z///96////q////9O6urr0AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/woKCv/09PTt////0f///6n///96////Tv///y3///8Y////DP///wb///8C////Af// - /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Af///wH///8C////Bv///wz///8Z////Lf///07///97////qv// - /9Hz8/PuCQkJ/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+8vLz0////0////6r///95////S/// - /yn///8V////Cf///wP///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////CP///xL///8j////Qv// - /23///+e////yfb29ukKCgr/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/6ioqPj////c////uP// - /4v///9e////Of///yH///8S////Cf///wX///8D////Av///wL///8C////Av///wL///8C////Av// - /wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av// - /wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////A/// - /wX///8J////Ev///yH///85////Xv///4z///+5////3KampvgAAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/CgoK//b29uj////J////nv///23///9B////I////xL///8I////Av///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wL///8G////D////x7///84////YP///5D///++////4EtLS/0AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/UFBQ/v///+b////I////n////3H///9J////Lf///xv///8R////C/// - /wj///8H////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv// - /wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv// - /wb///8G////Bv///wb///8G////Bv///wf///8I////C////xH///8b////Lf///0n///9x////n/// - /8j////mTk5O/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9NTU39////4P///77///+Q////YP// - /zj///8e////Dv///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wT///8M////Gf// - /y////9T////gv///7H////XmZmZ+AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8FBQX/5OTk8f// - /9b///+y////h////17///8+////Kf///x3///8W////E////xH///8R////EP///xD///8Q////EP// - /xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP// - /xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8R////Ef// - /xP///8W////Hf///yn///8+////Xv///4f///+z////1+Pj4/EEBAT/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/5qamvj////X////sf///4L///9T////L////xn///8M////BP///wEAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAD///8B////A////wn///8U////J////0b///9y////ov///8zr6+vrBgYG/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP99fX37////4////8b///+g////eP///1b///8/////Mf// - /yj///8k////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv// - /yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv// - /yL///8i////Iv///yL///8i////Iv///yL///8i////JP///yj///8x////P////1b///94////oP// - /8b////jfHx8/AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8HBwf/7Ozs6////8z///+i////cv// - /0b///8n////FP///wn///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////B/// - /xD///8g////Ov///2L///+S////vv///+BOTk79AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xUV - Ff/09PTv////2P///7n///+W////dv///17///9P////Rv///0L///9A////P////z////8/////P/// - /z////8/////P////z////8/////P////z////8/////P////z////8/////P////z////8/////P/// - /z////8/////P////z////8/////P////z////8/////P////z////8/////P////z////8/////P/// - /0D///9C////Rv///0////9e////dv///5b///+5////2PPz8+8TExP/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/1BQUP3////g////vv///5L///9i////Ov///yD///8Q////B////wIAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8F////DP///xn///8v////U////4D///+v////1bCw - sPUAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/4aGhvz////o////0v///7f///+d////iP// - /3v///9z////b////23///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP// - /2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP// - /2z///9s////bP///2z///9s////bP///2z///9s////bf///2////9z////e////4j///+d////t/// - /9L////og4OD/AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/tLS09f///9X///+v////gP// - /1P///8v////Gf///wz///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// - /wP///8J////FP///yb///9E////bv///5z////H/Pz85SIiIv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/EBAQ/+fn5/X////m////1f///8P///+0////qv///6T///+h////n////5////+e////nv// - /57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv// - /57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv// - /5////+f////of///6T///+q////tP///8P////V////5ubm5vUPDw//AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/yMjI//8/Pzl////x////5z///9u////RP///yb///8U////Cf///wP///8BAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8P////Hv///zb///9b////if// - /7b////Zj4+P+QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/Wlpa/v////T////r////4P// - /9j////R////zv///8z////L////y////8r////K////yv///8r////K////yv///8r////K////yv// - /8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv// - /8r////K////yv///8r////K////yv///8r////K////y////8v////M////zv///9H////Y////4P// - /+v////0WFhY/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/kZGR+f///9n///+1////iP// - /1v///82////Hf///w////8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAD///8B////BP///wv///8X////K////0r///90////ov///8r09PTpFhYW/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/qqqq/f////f////z////7////+z////q////6f///+n////p////6f// - /+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f// - /+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f// - /+n////p////6f///+n////q////7P///+/////z////96enp/0AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/xgYGP/19fXo////yv///6H///90////Sv///yr///8X////C////wT///8BAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////CP///xH///8g////Ov// - /1////+M////t////9qOjo75AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/kZGR+f///9r///+3////i/// - /1////86////IP///xH///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAP///wL///8F////DP///xj///8s////TP///3X///+i////yfj4+OclJSX/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/ycnJ//5+fnm////yf///6H///91////TP///yz///8Y////DP///wX///8CAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////Ev// - /yH///87////X////4r///+1////2Le3t/UAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8BAQH/ubm59f///9f///+0////iv// - /17///87////If///xH///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAA////Av///wX///8M////Gf///y3///9L////c////57////G////415e - Xv0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/2BgYP3////j////xv///57///9z////S////yz///8Z////DP///wX///8CAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// - /wn///8S////Iv///zz///9f////if///7T////W8PDw7yMjI/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8kJCT/8fHx7////9b///+0////if// - /1////88////Iv///xL///8J////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////Bv///w7///8b////MP///0////93////ov// - /8n////lzMzM+AsLC/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/CgoK/8zMzPj////l////yf///6L///93////T////zD///8b////Dv///wb///8C////AQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// - /wL///8E////C////xX///8n////Q////2f///+S////u////9z////wra2t/QICAv8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wMDA/+urq79////8P///9z///+7////kv// - /2f///9D////J////xX///8L////BP///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////Ev///yH///85////W/// - /4T///+v////0////+z////4lJSU/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA - AP8AAAD/lpaW/v////j////s////0////6////+E////W////zn///8h////Ev///wj///8D////AQAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////4AAAAAAAAAAAB////////+AAAAAAAAAAAAf/ - ///////gAAAAAAAAAAAH////////4AAAAAAAAAAAB////////+AAAAAAAAAAAAf////////gAAAAAAAA - AAAH////////8AAAAAAAAAAAD/////////AAAAAAAAAAAA/////////wAAAAAAAAAAAP////////8AAA - AAAAAAAAD/////////gAAAAAAAAAAB/////////8AAAAAAAAAAA//////////AAAAAAAAAAAP/////// - //4AAAAAAAAAAH//////////AAAAAAAAAAD//////////8AAAAAAAAAD///////////4AAAAAAAAH/// - //////////+AAAAB////////////////wAAAA////////////////+AAAAf////////////////gAAAH - ////////////////4AAAB////////////////+AAAAf////////////////gAAAH//////////////// - 4AAAB////////////////+AAAAf////////////////gAAAH////////////////4AAAB/////////// - /////+AAAAf////////////////gAAAH////////////////4AAAB////////////////+AAAAf///// - ///////////gAAAH////////////////4AAAB////////////////+AAAAf////////////////gAAAH - ////////////////4AAAB////////////////+AAAAf////////////////gAAAH//////////////// - wAAAA////////////////8AAAAP///////////////8AAAAA///////////////4AAAAAB////////// - ////4AAAAAAH/////////////4AAAAAAAf////////////4AAAAAAAB////////////4AAAAAAAAH/// - ////////8AAAAAAAAA///////////+AAAAAAAAAH//////////+AAAAAAAAAAf//////////AAAAAAAA - AAD//////////gAAAAAAAAAAf/////////wAAAAAAAAAAD/////////4AAAAAAAAAAAf////////8AAA - AAAAAAAAD////////+AAAAAAAAAAAAf////////AAAAAAAAAAAAD////////wAAAAAAAAAAAA/////// - /4AAAAAAAAAAAAH///////8AAAAAAAAAAAAA////////AAAAAAAAAAAAAP///////gAAAAAAAAAAAAB/ - //////4AAAAAAAAAAAAAf//////8AAAAAAAAAAAAAD///////AAAAAAAAAAAAAA///////gAAAAAAAAA - AAAAH//////4AAAAAAAAAAAAAB//////8AAAAAAAAAAAAAAP//////AAAAAAAAAAAAAAD//////wAAAA - AAAAAAAAAA//////4AAAAAAAAAAAAAAH/////+AAAAAAAAAAAAAAB//////gAAAAAAAAAAAAAAf///// - wAAAAAAAAAAAAAAD/////8AAAAAAAAAAAAAAA//////AAAAAAAAAAAAAAAP/////wAAAAAAAAAAAAAAD - /////4AAAAAAAAAAAAAAAf////+AAAAAAAAAAAAAAAH/////gAAAAAAAAAAAAAAB/////4AAAAAAAAAA - AAAAAf////+AAAAAAAAAAAAAAAH/////AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAA - AAAAAAAAAAD/////AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAD///// - AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAD////+AAAAAAAAAAAAAAAA - f////gAAAAAAAAAAAAAAAH////4AAAAAAAAAAAAAAAB////+AAAAAAAAAAAAAAAAf////gAAAAAAAAAA - AAAAAH////4AAAAAAAAAAAAAAAB////+AAAAAAAAAAAAAAAAf////wAAAAAAAAAAAAAAAP////8AAAAA - AAAAAAAAAAD/////AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAD///// - AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAH/////+AAAAD/////AAAAD//////wAAAA - /////4AAAA//////8AAAAf////+AAAAH/////+AAAAH/////gAAAB//////gAAAB/////4AAAAH///// - gAAAAf////+AAAAAAAAAAAAAAAH/////wAAAAAAAAAAAAAAD/////8AAAAAAAAAAAAAAA//////AAAAA - AAAAAAAAAAP/////4AAAAAAAAAAAAAAH/////+AAAAAAAAAAAAAAB//////gAAAAAAAAAAAAAAf///// - 8AAAAAAAAAAAAAAP//////AAAAAAAAAAAAAAD//////wAAAAAAAAAAAAAA//////+AAAAAAAAAAAAAAf - //////gAAAAAAAAAAAAAH//////8AAAAAAAAAAAAAD///////AAAAAAAAAAAAAA///////wAAAAAAAAA - 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 - AAAASUVORK5CYII= - - - \ No newline at end of file diff --git a/WinFormsDesigner/GridEntry.cs b/WinFormsDesigner/GridEntry.cs deleted file mode 100644 index 4a4383f5..00000000 --- a/WinFormsDesigner/GridEntry.cs +++ /dev/null @@ -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 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; } - } -} diff --git a/WinFormsDesigner/ProductsGrid.Designer.cs b/WinFormsDesigner/ProductsGrid.Designer.cs deleted file mode 100644 index 744efa71..00000000 --- a/WinFormsDesigner/ProductsGrid.Designer.cs +++ /dev/null @@ -1,191 +0,0 @@ -namespace WinFormsDesigner -{ - partial class ProductsGrid - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - 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; - } -} diff --git a/WinFormsDesigner/ProductsGrid.cs b/WinFormsDesigner/ProductsGrid.cs deleted file mode 100644 index 6548709e..00000000 --- a/WinFormsDesigner/ProductsGrid.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/WinFormsDesigner/ProductsGrid.resx b/WinFormsDesigner/ProductsGrid.resx deleted file mode 100644 index d1166daf..00000000 --- a/WinFormsDesigner/ProductsGrid.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/WinFormsDesigner/Program.cs b/WinFormsDesigner/Program.cs deleted file mode 100644 index 712e683d..00000000 --- a/WinFormsDesigner/Program.cs +++ /dev/null @@ -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 - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Form1()); - } - } -} diff --git a/WinFormsDesigner/Properties/DataSources/WinFormsDesigner.GridEntry.datasource b/WinFormsDesigner/Properties/DataSources/WinFormsDesigner.GridEntry.datasource deleted file mode 100644 index 0105788b..00000000 --- a/WinFormsDesigner/Properties/DataSources/WinFormsDesigner.GridEntry.datasource +++ /dev/null @@ -1,10 +0,0 @@ - - - - WinFormsDesigner.GridEntry, WinFormsDesigner, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null - \ No newline at end of file diff --git a/WinFormsDesigner/WinFormsDesigner.csproj b/WinFormsDesigner/WinFormsDesigner.csproj deleted file mode 100644 index cc091b42..00000000 --- a/WinFormsDesigner/WinFormsDesigner.csproj +++ /dev/null @@ -1,72 +0,0 @@ - - - - - Debug - AnyCPU - {0807616A-A77A-4B08-A65A-1582B09E114B} - WinExe - WinFormsDesigner - WinFormsDesigner - v4.8 - 512 - true - - - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - Form - - - Form1.cs - - - UserControl - - - ProductsGrid.cs - - - - - Form1.cs - - - ProductsGrid.cs - - - - - - \ No newline at end of file From 560523b99df0423eb00ab718575385d28b75e1d0 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 18:26:10 -0600 Subject: [PATCH 26/64] Revert "Removed WinFormsDesigner project." This reverts commit d5e9e4951704b876a9d8f9fef9cae7de684dc7bb. --- WinFormsDesigner/Form1.Designer.cs | 345 +++ WinFormsDesigner/Form1.cs | 16 + WinFormsDesigner/Form1.resx | 1841 +++++++++++++++++ WinFormsDesigner/GridEntry.cs | 31 + WinFormsDesigner/ProductsGrid.Designer.cs | 191 ++ WinFormsDesigner/ProductsGrid.cs | 27 + WinFormsDesigner/ProductsGrid.resx | 123 ++ WinFormsDesigner/Program.cs | 22 + .../WinFormsDesigner.GridEntry.datasource | 10 + WinFormsDesigner/WinFormsDesigner.csproj | 72 + 10 files changed, 2678 insertions(+) create mode 100644 WinFormsDesigner/Form1.Designer.cs create mode 100644 WinFormsDesigner/Form1.cs create mode 100644 WinFormsDesigner/Form1.resx create mode 100644 WinFormsDesigner/GridEntry.cs create mode 100644 WinFormsDesigner/ProductsGrid.Designer.cs create mode 100644 WinFormsDesigner/ProductsGrid.cs create mode 100644 WinFormsDesigner/ProductsGrid.resx create mode 100644 WinFormsDesigner/Program.cs create mode 100644 WinFormsDesigner/Properties/DataSources/WinFormsDesigner.GridEntry.datasource create mode 100644 WinFormsDesigner/WinFormsDesigner.csproj diff --git a/WinFormsDesigner/Form1.Designer.cs b/WinFormsDesigner/Form1.Designer.cs new file mode 100644 index 00000000..a6bd43d9 --- /dev/null +++ b/WinFormsDesigner/Form1.Designer.cs @@ -0,0 +1,345 @@ +namespace WinFormsDesigner +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} diff --git a/WinFormsDesigner/Form1.cs b/WinFormsDesigner/Form1.cs new file mode 100644 index 00000000..1585a517 --- /dev/null +++ b/WinFormsDesigner/Form1.cs @@ -0,0 +1,16 @@ +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(); + } + } +} diff --git a/WinFormsDesigner/Form1.resx b/WinFormsDesigner/Form1.resx new file mode 100644 index 00000000..2c4b8cf9 --- /dev/null +++ b/WinFormsDesigner/Form1.resx @@ -0,0 +1,1841 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 132, 17 + + + + + AAABAAUAEBAAAAEAIABoBAAAVgAAACAgAAABACAAqBAAAL4EAABAQAAAAQAgAChCAABmFQAAgIAAAAEA + IAAoCAEAjlcAAAAAAAABACAA4TAAALZfAQAoAAAAEAAAACAAAAABACAAAAAAAAAEAADDDgAAww4AAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////CYmJidkAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+JiYnZ////CQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8y////ZP///3B4eHjzeXl58////3D///9k////Mv// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8PdHR063V1dev///8PAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////D3R0dOt1dXXr////DwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///w90dHTrdXV16/// + /w8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wr///88d3d38Xh4 + ePH///88////CgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///zCfn5/JQEBA/AoK + Cv8KCgr/QEBA/J+fn8n///8w////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///yl0dHTsJSUl/4uL + i/2Kior9ioqK/YqKiv0lJSX/dHR07P///ykAAAAAAAAAAAAAAAAAAAAAAAAAAP///wW0tLSvHBwc/4mJ + if0FBQX/AAAA/wAAAP8FBQX/iYmJ/RwcHP+0tLSv////BQAAAAAAAAAAAAAAAAAAAAD///8aYmJi83t7 + e/4MDAz/AAAA/wAAAP8AAAD/AAAA/wwMDP97e3v+YmJi8////xoAAAAAAAAAAAAAAAAAAAAA////OzEx + Mf6AgID9AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/gICA/TExMf7///86AAAAAAAAAAAAAAAAAAAAAP// + /00bGxv/fn5+/QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/35+fv0bGxv/////TQAAAAAAAAAAAAAAAAAA + AAD///9IISEh/9PT07v///9n////Zv///2b///9m////Zv///2fT09O7ISEh/////0gAAAAAAAAAAAAA + AAAAAAAA////LUNDQ/yfn5/J////CP///wH///8B////Af///wH///8In5+fyUNDQ/z///8tAAAAAAAA + AAAAAAAAAAAAAP///w6Dg4PhUVFR+v///3j///9m////Zv///2b///9m////eFFRUfqDg4Ph////DgAA + AAAAAAAAAAAAAAAAAAD///8B5+fnbiYmJv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8mJib/5+fnbv// + /wEAAAAAAAAAAOAHAADgBwAA/D8AAPw/AAD8PwAA+B8AAOAHAADgBwAAwAMAAMADAADAAwAAwAMAAMAD + AADAAwAAwAMAAMADAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAADDDgAAww4AAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///xT///+WNjY2/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/zY2Nv7///+W////FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////D////341NTX9AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/NTU1/f///37///8PAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8E////MP///4P///+q////r/// + /7D///+5////5gAAAP8AAAD/////5v///7n///+w////r////6r///+D////MP///wQAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8F////Ef// + /xv///8d////Hv///zn///+5AAAA/wAAAP////+5////Of///x7///8d////G////xH///8FAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wH///8B////Hv///7AAAAD/AAAA/////7D///8e////Af///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8e////rwAAAP8AAAD/////r////x7///8BAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///x7///+vAAAA/wAAAP////+v////Hv// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Hv///68AAAD/AAAA//// + /6////8e////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8e////rwAA + AP8AAAD/////r////x7///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /x7///+vAAAA/wAAAP////+v////Hv///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wH///8F////J////7QAAAD/AAAA/////7T///8n////Bf///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wH///8I////Hv///0X///9+////2QAAAP8AAAD/////2f///37///9F////Hv///wj///8BAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8D////GP///1L4+PiipKSk51tbW/sqKir/AAAA/wAAAP8qKir/W1tb+6SkpOf4+Pii////Uv// + /xj///8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////BP///yX///9/qqqq5SIiIv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIi + Iv+qqqrl////f////yX///8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wL///8h////i3Nzc/cBAQH/AAAA/yYmJv+Hh4f9xsbG+ubm5vjm5ub4xsbG+oaG + hv0lJSX/AAAA/wEBAf9zc3P3////i////yL///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Ef///3CBgYHzAAAA/wUFBf+NjY397Ozs+JOTk/1OTk7+Li4u/y4u + Lv9OTk7+k5OT/ezs7PmMjIz9BQUF/wAAAP+BgYHz////cP///xEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///85zMzM0QUFBf8CAgL/sLCw/LGxsfsVFRX/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/FRUV/7GxsfuwsLD8AgIC/wUFBf/MzMzR////Of///wQAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////D////3lZWVn7AAAA/2lpaf7BwcH7BAQE/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/BQUF/8HBwftpaWn+AAAA/1lZWfv///95////DwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8k6+vruAgICP8EBAT/4uLi+y8v + L/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/Ly8v/+Li4vsEBAT/CAgI/+vr + 67j///8k////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A////0GmpqbmAAAA/0JC + Qv/Hx8f7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/x8fH/EJC + Qv8AAAD/pqam5v///0H///8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8H////YHJy + cvcAAAD/gYGB/YGBgf0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP+BgYH9gYGB/QAAAP9ycnL3////X////wcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wz///94Tk5O/AAAAP+tra38UlJS/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/1JSUv6tra38AAAA/05OTvz///94////DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////D////4c4ODj+AAAA/8fHx/s3Nzf/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/Nzc3/8fHx/sAAAD/OTk5/v///4f///8PAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8R////jjAwMP4AAAD/0NDQ+CwsLP4AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8tLS3+z8/P+AAAAP8wMDD+////jv///xAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///w////+JNjY2/gAAAP/Gxsbq////rv///67///+v////r/// + /6////+v////r////6////+v////r////6////+v////rv///67FxcXqAAAA/zY2Nv7///+J////DwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////DP///3tKSkr9AAAA/6urq+j///9T////IP// + /x7///8e////Hv///x7///8e////Hv///x7///8e////Hv///x7///8g////VKurq+kAAAD/SkpK/f// + /3v///8MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8I////Y2xsbPgAAAD/g4OD9P// + /1z///8I////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wj///9dgoKC9AAA + AP9sbGz4////Y////wgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///9Fnp6e6QAA + AP9GRkb9////iv///xT///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////FP// + /4pGRkb9AAAA/5+fn+n///9F////BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /ybm5ua9BQUF/wUFBf/f39/N////SP///yD///8e////Hv///x7///8e////Hv///x7///8e////Hv// + /yD///9I39/fzQUFBf8GBgb/5ubmvf///yb///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////EP///3xVVVX8AAAA/2ZmZvz////I////sf///6////+v////r////6////+v////r/// + /6////+v////sf///8hmZmb8AAAA/1RUVPv///98////EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8E////O83NzdIGBgb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8GBgb/zc3N0v///zv///8EAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8W////hpCQkPkAAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/5CQkPn///+G////Fv///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAP4AAH/+AAB//gAAf/8AAP//4Af///AP///wD///8A////AP///wD///4Af//4AB//8A + AP/+AAB//AAAP/wAAD/4AAAf+AAAH/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AA + AA/wAAAP+AAAH/gAAB/4AAAfKAAAAEAAAACAAAAAAQAgAAAAAAAAQAAAww4AAMMOAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////B/// + /yH///9o////wq+vr/kWFhb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/FhYW/6+v + r/n////D////aP///yH///8HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wb///8g////af///8UWFhb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8XFxf/////xf///2n///8g////BgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8F////HP///1////+6FhYW/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/FxcX/////7r///9f////Hf///wUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////BP///xX///9H////l6Wl + pegWFhb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/FhYW/6ioqOj///+X////R/// + /xX///8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wL///8L////KP///17///+Z////wf///9L////Y////2f///9r////a////2v///9v////h////7f// + //oAAAD/AAAA/wAAAP8AAAD/////+v///+3////h////2////9r////a////2v///9n////Y////0v// + /8H///+Z////Xv///yj///8L////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////BP///xD///8p////Sv///2j///96////gv///4T///+F////hf// + /4b///+L////nf///8X////tAAAA/wAAAP8AAAD/AAAA/////+3////F////nf///4v///+G////hf// + /4X///+E////gv///3r///9o////Sv///yn///8Q////BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8E////DP///xb///8h////Kv// + /y7///8v////MP///zD///8x////Ov///1j///+d////4QAAAP8AAAD/AAAA/wAAAP/////h////nf// + /1j///86////Mf///zD///8w////L////y7///8q////If///xb///8M////BP///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wL///8E////B////wn///8K////C////wv///8L////Df///xf///86////i////9sAAAD/AAAA/wAA + AP8AAAD/////2////4v///86////F////w3///8L////C////wv///8K////Cf///wf///8E////AgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8B////Af///wH///8B////Af///wP///8N////Mf// + /4b////aAAAA/wAAAP8AAAD/AAAA/////9r///+G////Mf///w3///8D////Af///wH///8B////Af// + /wH///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////C////zD///+F////2gAAAP8AAAD/AAAA/wAAAP/////a////hf///zD///8L////AQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wv///8w////hf///9oAAAD/AAAA/wAAAP8AAAD/////2v// + /4X///8w////C////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8L////MP///4X////aAAAA/wAA + AP8AAAD/AAAA/////9r///+F////MP///wv///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////C/// + /zD///+F////2gAAAP8AAAD/AAAA/wAAAP/////a////hf///zD///8L////AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////Af///wv///8w////hf///9oAAAD/AAAA/wAAAP8AAAD/////2v///4X///8w////C/// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8L////MP///4X////aAAAA/wAAAP8AAAD/AAAA//// + /9r///+F////MP///wv///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////C////zD///+F////2gAA + AP8AAAD/AAAA/wAAAP/////a////hf///zD///8L////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wv///8w////hf///9oAAAD/AAAA/wAAAP8AAAD/////2v///4X///8w////C////wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wH///8L////MP///4X////aAAAA/wAAAP8AAAD/AAAA/////9r///+F////MP// + /wv///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////C////zD///+F////2gAAAP8AAAD/AAAA/wAA + AP/////a////hf///zD///8L////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wv///8w////hf// + /9oAAAD/AAAA/wAAAP8AAAD/////2v///4X///8w////C////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wP///8O////M////4f////aAAAA/wAAAP8AAAD/AAAA/////9r///+H////M////w7///8D////AQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////A////wb///8M////G////0H///+R////3gAAAP8AAAD/AAAA/wAAAP/////e////kf// + /0H///8b////DP///wb///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wH///8E////Cf///xH///8c////K////0P///9r////rv///+cAAAD/AAAA/wAA + AP8AAAD/////5////67///9r////Q////yv///8c////Ef///wn///8E////AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8J////E////yT///87////WP///3b///+V////tv// + /9v////1AAAA/wAAAP8AAAD/AAAA//////X////b////tv///5X///92////WP///zv///8k////E/// + /wn///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv///z7///9k////jf// + /7H////N2dnZ5piYmPZlZWX9QEBA/wAAAP8AAAD/AAAA/wAAAP9AQED/ZWVl/ZiYmPbZ2dnm////zf// + /7H///+N////ZP///z7///8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wn///8Y////M/// + /1z///+M////uerq6t6IiIj3Kysr/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/ysrK/6IiIj36urq3v///7n///+M////XP///zP///8Y////Cf///wIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A/// + /wv///8e////Qf///3X///+s8PDw2Xl5efkNDQ3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/w0NDf95eXn58vLy2f///6z///91////Qf// + /x7///8L////AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////A////wv///8h////Sv///4b////Atra28B4eHv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/x4e + Hv+2trbw////wP///4b///9K////If///wv///8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Av///wr///8f////S////439/f3KfHx8+QICAv8AAAD/AAAA/wAA + AP8AAAD/AAAA/wMDA/85OTn/eHh4/qampvzGxsb71tbW+tbW1vrGxsb7pqam/Hh4eP44ODj/AgIC/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AgIC/3x8fPn9/f3L////jf///0z///8f////Cv///wIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wf///8Z////RP///4n8/PzMYWFh/AAA + AP8AAAD/AAAA/wAAAP8AAAD/FBQU/4GBgf7k5OT6////9/////b////2////9f////X////1////9f// + //b////2////9+Li4vt/f3/+FBQU/wAAAP8AAAD/AAAA/wAAAP8AAAD/YWFh/Pz8/Mz///+J////RP// + /xn///8H////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///8R////Nv// + /3n////Eb29v+gAAAP8AAAD/AAAA/wAAAP8DAwP/c3Nz/vDw8Pn////3////9/Ly8vi1tbX7hISE/WNj + Y/5TU1P+U1NT/mNjY/6EhIT9tbW1+/Pz8/j////3////9/Dw8PlycnL+AwMD/wAAAP8AAAD/AAAA/wAA + AP9ubm76////xP///3n///82////Ef///wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + 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 + e/kAAAD/AAAA/wAAAP9eXl7/////+bq6uvwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP+6urr8////+V1dXf8AAAD/AAAA/wAAAP97e3v5////tP///1j///8b////BQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wj///8l////bv///8g9PT3+AAAA/wAAAP8AAAD/qamp/f////lmZmb+AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/ZmZm/v////moqKj9AAAA/wAAAP8AAAD/Pj4+/v///8j///9t////JP// + /wj///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wH///8L////L////4L7+/vXCgoK/wAAAP8AAAD/AQEB/+jo6Pr////5ISEh/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yEhIf/////56Ojo+wAAAP8AAAD/AAAA/woK + Cv/7+/vW////gv///y////8L////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////D////zr///+T0tLS5wAAAP8AAAD/AAAA/yAg + IP/////55eXl+gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/5eXl+v// + //kgICD/AAAA/wAAAP8AAAD/0tLS5////5P///86////D////wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A////xL///9E////oaur + q/EAAAD/AAAA/wAAAP9MTEz/////+be3t/wAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/7e3t/z////5TExM/wAAAP8AAAD/AAAA/6ysrPH///+h////RP///xL///8DAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wP///8W////Tf///6yOjo73AAAA/wAAAP8AAAD/bm5u/v////mSkpL9AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+SkpL9////+W1tbf4AAAD/AAAA/wAAAP+Ojo73////rP// + /03///8W////AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8E////Gf///1X///+0eHh4+gAAAP8AAAD/AAAA/4iIiP7////4dnZ2/gAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/d3d3/v////iHh4f+AAAA/wAA + AP8AAAD/eHh4+v///7T///9V////Gf///wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Bf///xv///9b////umlpafwAAAD/AAAA/wAA + AP+ampr9////+GRkZP4AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/2Rk + ZP7////4mZmZ/QAAAP8AAAD/AAAA/2lpafz///+6////W////xv///8FAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wX///8c////Xv// + /71gYGD8AAAA/wAAAP8AAAD/oqKi/P////VaWlr+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP9bW1v+////9aGhofwAAAD/AAAA/wAAAP9hYWH8////vf///17///8c////BQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8F////HP///1////+9X19f/AAAAP8AAAD/AAAA/6Ojo/v////tWFhY/QAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/WVlZ/f///+2ioqL7AAAA/wAAAP8AAAD/X19f/P// + /73///9f////HP///wUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Bf///xv///9c////u2VlZfwAAAD/AAAA/wAAAP+dnZ36////3P// + /8z////Q////1////9n////Z////2v///9r////a////2v///9r////a////2v///9r////a////2v// + /9r////a////2v///9r////a////2v///9r////a////2f///9n////X////0P///8z////cnJyc+gAA + AP8AAAD/AAAA/2VlZfz///+7////XP///xv///8FAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///8a////V////7ZxcXH6AAAA/wAA + AP8AAAD/jIyM+v///8r///+X////g////4P///+E////hf///4X///+F////hf///4X///+F////hf// + /4X///+F////hf///4X///+F////hf///4X///+F////hf///4X///+F////hf///4X///+E////g/// + /4P///+Y////youLi/oAAAD/AAAA/wAAAP9ycnL6////tv///1f///8Z////BAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8E////F/// + /1D///+vhoaG+AAAAP8AAAD/AAAA/3V1dfv////C////c////0H///8y////MP///zD///8w////MP// + /zD///8w////MP///zD///8w////MP///zD///8w////MP///zD///8w////MP///zD///8w////MP// + /zD///8w////MP///zL///9B////dP///8Nzc3P7AAAA/wAAAP8AAAD/hoaG+P///6////9Q////F/// + /wQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////A////xP///9H////paGhofMAAAD/AAAA/wAAAP9VVVX9////x////2////8r////Ev// + /wz///8L////C////wv///8L////C////wv///8L////C////wv///8L////C////wv///8L////C/// + /wv///8L////C////wv///8L////C////wz///8T////LP///3D////HVFRU/QAAAP8AAAD/AAAA/6Ki + ovP///+l////R////xP///8DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8Q////Pf///5jGxsbrAAAA/wAAAP8AAAD/LCws/v// + /9H///97////Lf///wz///8D////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8D////DP///y7///98////0iws + LP8AAAD/AAAA/wAAAP/Hx8fr////l////z3///8Q////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////DP///zL///+H8/Pz3AMD + A/8AAAD/AAAA/wQEBP/x8fHh////kP///zr///8Q////AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A/// + /xD///87////kfHx8eEDAwP/AAAA/wAAAP8EBAT/8/Pz2////4f///8y////DP///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wn///8n////c////8wuLi7+AAAA/wAAAP8AAAD/s7Oz8v///6n///9P////Gf///wUAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wX///8Z////T////6qysrLyAAAA/wAAAP8AAAD/Ly8v/v///8z///9z////J/// + /wn///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8G////Hf///13///+5bGxs+wAAAP8AAAD/AAAA/2lpafz////D////bP// + /yj///8L////A////wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af// + /wH///8B////Af///wH///8B////Af///wP///8L////KP///23////EZ2dn/AAAAP8AAAD/AAAA/21t + bfv///+5////Xf///x3///8GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////A////xT///9H////oLW1te8AAAD/AAAA/wAA + AP8WFhb/+Pj43v///5L///9F////HP///w////8M////C////wv///8L////C////wv///8L////C/// + /wv///8L////C////wv///8L////C////wv///8L////C////wz///8P////HP///0X///+S9vb23hUV + Ff8AAAD/AAAA/wAAAP+2trbv////oP///0b///8U////AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8N////Mv// + /4L6+vrWFhYW/wAAAP8AAAD/AAAA/56envf///++////dv///0f///81////Mf///zD///8w////MP// + /zD///8w////MP///zD///8w////MP///zD///8w////MP///zD///8w////MP///zD///8x////Nf// + /0f///92////vp2dnfcAAAD/AAAA/wAAAP8WFhb/+vr61v///4L///8y////Df///wIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////B////yH///9h////unJycvoAAAD/AAAA/wAAAP8mJib/+Pj45v///7v///+Y////iv// + /4b///+F////hf///4X///+F////hf///4X///+F////hf///4X///+F////hf///4X///+F////hf// + /4X///+F////hv///4r///+Y////u/j4+OYlJSX/AAAA/wAAAP8AAAD/c3Nz+v///7n///9h////If// + /wf///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wT///8T////Qv///5Xd3d3kBgYG/wAAAP8AAAD/AAAA/4CA + gP3////t////4f///9v////a////2v///9r////a////2v///9r////a////2v///9r////a////2v// + /9r////a////2v///9r////a////2v///9r////b////4f///+1+fn79AAAA/wAAAP8AAAD/BgYG/97e + 3uP///+V////Qf///xP///8EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Cv///yj///9r////v2lp + afsAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/2pqavv///+/////a////yf///8K////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wX///8W////RP///5Tr6+veGBgY/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/xkZGf/r6+ve////lP///0T///8W////BQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8C////DP///yr///9r////vbW1tfYDAwP/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wMDA/+2trb2////vf///2v///8q////DP// + /wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wf///8c////UP///6D////jkJCQ/QAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+RkZH9////4/// + /6D///9Q////HP///wf///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD//AAAAAA////8AAAAAD////wAAAAAP////AAAAAA////8AAAAAD////4AAAAAf////gAAAAB///// + gAAAAf/////gAAAH//////+AAf///////4AB////////gAH///////+AAf///////4AB////////gAH/ + //////+AAf///////4AB////////gAH///////+AAf///////4AB////////AAD///////wAAD////// + 8AAAD//////AAAAD/////4AAAAH/////AAAAAP////4AAAAAf////AAAAAA////4AAAAAB////AAAAAA + D///8AAAAAAP///gAAAAAAf//+AAAAAAB///wAAAAAAD///AAAAAAAP//4AAAAAAAf//gAAAAAAB//+A + AAAAAAH//4AAAAAAAf//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAA + AP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8AAAAAAAD//wAAAAAAAP//AAAAAAAA//8A + AAAAAAD//wAD///AAP//AAP//8AA//+AAAAAAAH//4AAAAAAAf//gAAAAAAB//+AAAAAAAH//8AAAAAA + A///wAAAAAAD///gAAAAAAf//+AAAAAAB///4AAAAAAH/ygAAACAAAAAAAEAAAEAIAAAAAAAAAABAMMO + AADDDgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////BP///wr///8X////LP///0////99////rf///9T////u2dnZ+05OTv8LCwv/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/CwsL/05OTv/Z2dn8////7v///9X///+t////fv///1D///8s////F////wr///8E////AQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8E////Cv///xb///8s////Uf// + /4H///+w////19bW1vQRERH/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xEREf/X19f0////2P// + /7H///+B////Uf///yz///8W////Cv///wT///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////Af///wP///8K////Fv///yz///9S////gv///7L////ZTExM/QAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/01NTf3////Z////sv///4L///9S////LP///xb///8K////A/// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A////wr///8V////Kv// + /1D///+A////sf///9gLCwv/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/DAwM//// + /9j///+x////gP///1D///8q////Ff///wr///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wH///8D////Cf///xT///8o////TP///3v///+r////0wsLC/8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8MDAz/////0////6v///97////TP///yj///8U////Cf// + /wP///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8I////Ev// + /yT///9F////cf///6D////JSkpK/AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/01N + Tfz////J////oP///3H///9F////JP///xL///8I////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8P////H////zv///9i////jv///7jPz8/jEBAQ/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8RERH/0tLS4v///7j///+O////Y////zv///8f////D/// + /wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Bf// + /wz///8Z////L////1D///93////nv///8HPz8/jSUlJ/AwMDP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8MDAz/SkpK/NLS + 0uP////B////nv///3f///9Q////L////xn///8M////Bf///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////Cf///xL///8j////PP///1z///9/////oP// + /7r////N////2v///+H////l////5////+j////p////6f///+n////p////6f///+n////p////6f// + /+n////q////7P///+7////y////9/////r////9AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP/////9////+v////f////y////7v///+z////q////6f///+n////p////6f///+n////p////6f// + /+n////p////6P///+f////l////4f///9r////N////uv///6D///9/////XP///zz///8j////Ev// + /wn///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wL///8G////Df///xj///8q////Qf///13///95////kv///6b///+1////vv///8T////I////yf// + /8r////K////yv///8r////K////yv///8v////L////zP///87////S////2P///+H////r////9P// + //oAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//////r////0////6////+H////Y////0v// + /87////M////y////8v////K////yv///8r////K////yv///8r////J////yP///8T///++////tf// + /6b///+S////ef///13///9B////Kv///xj///8N////Bv///wL///8BAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////D////xr///8q////Pf// + /1L///9n////eP///4b///+Q////l////5v///+d////nv///57///+e////nv///57///+e////n/// + /6D///+h////pf///6z///+3////yP///9v////r////9wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/////9////+v////b////yP///7f///+s////pf///6H///+g////n////57///+e////nv// + /57///+e////nv///53///+b////l////5D///+G////eP///2f///9S////Pf///yr///8a////D/// + /wj///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////Av///wT///8J////D////xn///8k////Mf///z////9L////Vv///17///9k////aP// + /2r///9r////a////2v///9r////bP///2z///9s////bf///3D///92////gP///5H///+r////yP// + /+H////yAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////y////4f///8j///+r////kf// + /4D///92////cP///23///9s////bP///2z///9r////a////2v///9r////av///2j///9k////Xv// + /1b///9L////P////zH///8k////Gf///w////8J////BP///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av///wT///8I////Df// + /xP///8a////Iv///yn///8v////Nf///zn///87////Pf///z7///8+////P////z////8/////P/// + /z////9B////RP///0z///9Z////b////5H///+3////2P///+4AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/////+7////Y////t////5H///9v////Wf///0z///9E////Qf///z////8/////P/// + /z////8/////Pv///z7///89////O////zn///81////L////yn///8i////Gv///xP///8N////CP// + /wT///8C////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8B////Av///wP///8G////Cf///w3///8R////Ff///xj///8b////Hv// + /x////8g////If///yH///8h////Iv///yL///8i////Iv///yT///8o////MP///0D///9Z////gP// + /6z////S////7AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////7P///9L///+s////gP// + /1n///9A////MP///yj///8k////Iv///yL///8i////Iv///yH///8h////If///yD///8f////Hv// + /xv///8Y////Ff///xH///8N////Cf///wb///8D////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Af// + /wL///8E////Bf///wj///8J////C////w3///8O////D////w////8Q////EP///xD///8Q////EP// + /xD///8R////E////xf///8g////MP///0z///92////pf///87////qAAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP/////q////zv///6X///92////TP///zD///8g////F////xP///8R////EP// + /xD///8Q////EP///xD///8Q////D////w////8O////Df///wv///8J////CP///wX///8E////Av// + /wH///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wH///8C////Av///wP///8E////Bf// + /wX///8G////Bv///wb///8G////Bv///wb///8G////Bv///wf///8J////Dv///xf///8o////RP// + /3D///+h////zP///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////M////of// + /3D///9E////KP///xf///8O////Cf///wf///8G////Bv///wb///8G////Bv///wb///8G////Bv// + /wX///8F////BP///wP///8C////Av///wH///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8B////Af///wH///8B////Av///wL///8C////Av///wL///8C////Av// + /wL///8C////A////wX///8J////E////yT///9B////bf///6D////L////6QAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/////6f///8v///+g////bf///0H///8k////E////wn///8F////A/// + /wL///8C////Av///wL///8C////Av///wL///8C////Av///wH///8B////Af///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8B////A////wf///8R////Iv// + /z////9s////n////8v////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////y/// + /5////9s////P////yL///8R////B////wP///8B////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wH///8C////Bv///xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////K////nv///2z///8/////Iv///xD///8G////Av// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP// + /yL///8/////bP///57////K////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f// + /8r///+e////bP///z////8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////yv///57///9s////P////yL///8Q////Bv// + /wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv// + /xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//// + /+n////K////nv///2z///8/////Iv///xD///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP///yL///8/////bP///57////K////6QAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f///8r///+e////bP///z////8i////EP// + /wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av// + /wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP/////p////yv///57///9s////P////yL///8Q////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv///xD///8i////P////2z///+e////yv// + /+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////K////nv///2z///8/////Iv// + /xD///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wL///8G////EP///yL///8/////bP///57////K////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/////6f///8r///+e////bP///z////8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv///z////9s////nv// + /8r////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////yv///57///9s////P/// + /yL///8Q////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8C////Bv///xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/////+n////K////nv///2z///8/////Iv///xD///8G////AgAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP///yL///8/////bP// + /57////K////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f///8r///+e////bP// + /z////8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////Av///wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP/////p////yv///57///9s////P////yL///8Q////Bv///wIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv///xD///8i////P/// + /2z///+e////yv///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////K////nv// + /2z///8/////Iv///xD///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wL///8G////EP///yL///8/////bP///57////K////6QAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/////6f///8r///+e////bP///z////8i////EP///wb///8CAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv// + /z////9s////nv///8r////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////yv// + /57///9s////P////yL///8Q////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8C////Bv///xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////K////nv///2z///8/////Iv///xD///8G////AgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP// + /yL///8/////bP///57////K////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f// + /8r///+e////bP///z////8i////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////yv///57///9s////P////yL///8Q////Bv// + /wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv// + /xD///8i////P////2z///+e////yv///+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//// + /+n////K////nv///2z///8/////Iv///xD///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////EP///yL///8/////bP///57////K////6QAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////6f///8r///+e////bP///z////8i////EP// + /wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av// + /wb///8Q////Iv///z////9s////nv///8r////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP/////p////yv///57///9s////P////yL///8Q////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////B////xH///8i////P////2z///+f////y/// + /+kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/////+n////L////n////2z///8/////Iv// + /xH///8H////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wP///8I////Ev///yP///9A////bf///6D////L////6QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/////6f///8v///+g////bf///0D///8j////Ev///wj///8D////AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wH///8C////Bf///wr///8V////Jv///0P///9w////of// + /8z////pAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////p////zP///6H///9w////Q/// + /yb///8V////Cv///wX///8C////Af///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Af///wL///8C////BP// + /wb///8J////EP///xv///8s////Sf///3X///+l////zv///+oAAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/////+r////O////pf///3X///9J////LP///xv///8Q////Cf///wb///8E////Av// + /wL///8B////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////Af///wL///8D////Bf///wf///8K////Dv///xL///8a////Jv///zn///9V////f/// + /6v////S////7AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/////7P///9L///+r////f/// + /1X///85////Jv///xr///8S////Dv///wr///8H////Bf///wP///8C////Af///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av///wP///8E////Bv///wn///8N////Ef// + /xb///8b////Iv///yz///86////Tf///2n///+P////t////9n////vAAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP/////v////2f///7f///+P////af///03///86////LP///yL///8b////Fv// + /xH///8N////Cf///wb///8E////A////wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av// + /wP///8E////B////wr///8P////FP///xr///8h////Kf///zP///8+////S////1v///9v////if// + /6j////I////4v////MAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA//////P////i////yP// + /6j///+J////b////1v///9L////Pv///zP///8p////If///xr///8U////D////wr///8H////BP// + /wP///8C////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8B////Af///wL///8E////B////wv///8Q////Fv///x3///8m////MP// + /zz///9J////V////2b///92////h////5n///+u////xf///9v////s////9wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/////9////+z////b////xf///67///+Z////h////3b///9m////V/// + /0n///88////MP///yb///8d////Fv///xD///8L////B////wT///8C////Af///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8D////Bv// + /wr///8P////Fv///x7///8o////Nf///0P///9T////ZP///3X///+G////lv///6X///+0////wv// + /9D////f////7P////X////7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/////7////9f// + /+z////f////0P///8L///+0////pf///5b///+G////df///2T///9T////Q////zX///8o////Hv// + /xb///8P////Cv///wb///8D////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wH///8D////Bf///wj///8N////FP///x3///8o////Nv///0b///9Y////bP// + /4D///+T////pP///7T////C////zv///9n////i////6uLi4vO1tbX6jo6O/m9vb/8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/29vb/+Ojo7+tbW1+uPj4/P////q////4v///9n////O////wv// + /7T///+k////k////4D///9s////WP///0b///82////KP///x3///8U////Df///wj///8F////A/// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////A////wb///8L////Ef// + /xn///8k////M////0T///9Y////bv///4T///+a////rv///7/////O////2f///+Pd3d3ukpKS+VZW + Vv0aGhr/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/xoaGv9XV1f9kpKS+d3d3e7////j////2f///87///+/////rv///5r///+E////bv// + /1j///9E////M////yT///8Z////Ef///wv///8G////A////wL///8BAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////Av///wT///8I////Df///xX///8f////Lf///z7///9T////av///4P///+b////sf// + /8T////U////4N/f3+1/f3/6LCws/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/y0t + Lf6AgID639/f7f///+D////U////xP///7H///+b////g////2r///9T////Pv///y3///8f////Ff// + /w3///8I////BP///wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8F////Cf///w////8Y////JP// + /zX///9K////Yf///3v///+W////rv///8T////W////47W1tfRKSkr+AgIC/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8CAgL/S0tL/rW1tfT////j////1v// + /8T///+u////lv///3v///9h////Sv///zX///8k////GP///w////8J////Bf///wL///8BAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wH///8C////Bf///wr///8R////G////yn///88////U////2////+L////pv///7/////T////4rGx + sfQxMTH+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/zIyMv6ysrL0////4v///9P///+/////pv///4v///9v////VP// + /zz///8p////G////xH///8K////Bf///wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av///wb///8L////Ev///x3///8u////Qv// + /1z///95////l////7T////L////3s7OzvE+Pj7+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8+Pj7+zs7O8f///97////L////tP///5f///95////XP///0P///8u////Hf///xL///8L////Bv// + /wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wL///8F////C////xP///8f////MP///0f///9j////gv///6L///++////1fLy8uhxcXH7AwMD/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8DAwP/cXFx+/Ly8uj////V////vv// + /6L///+C////Y////0j///8w////H////xP///8L////Bv///wL///8BAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////Bf///wr///8T////H////zL///9K////aP// + /4j///+p////xv///9zLy8vxKSkp/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/KSkp/8vLy/H////c////xv///6n///+J////aP///0r///8y////H/// + /xP///8K////Bf///wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av// + /wT///8K////Ev///x7///8x////Sv///2r///+M////rv///8v////hlJSU+AgICP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/EBAQ/z4+ + Pv9kZGT+goKC/pqamv2qqqr8sLCw/LCwsPyqqqr8mpqa/YODg/5kZGT+Pj4+/xAQEP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/CAgI/5SU + lPj////h////y////67///+M////av///0v///8y////Hv///xL///8K////BP///wL///8BAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8E////CP///xD///8c////L////0n///9p////jf// + /7D////O+vr65GlpafwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wsLC/9NTU3/lZWV/dXV1fz9/f35////+f////j////3////9/////f////2////9v// + //f////3////9/////j////5/f39+dbW1vyVlZX+S0tL/wkJCf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/2lpafz6+vrk////zv///7D///+N////av// + /0n///8v////Hf///xD///8I////BP///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// + /wf///8O////Gv///yz///9F////Zv///4v///+v////zvX19edOTk79AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/zw8PP+hoaH98fHx+v////n////4////9/// + //b////2////9f////X////0////9P////T////0////9P////T////1////9f////b////2////9/// + //j////57u7u+p2dnf04ODj/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/05OTv319fXn////zv///7D///+L////Zv///0b///8s////Gv///w7///8H////A/// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8F////DP///xb///8n////QP///2D///+G////rP// + /8339/flRkZG/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AQEB/1FR + Uf/Kysr9////+f////j////3////9v////b////1////9f////b////2////9v////b////2////9v// + //b////2////9v////b////2////9v////b////2////9v////b////3////+P////rIyMj9UVFR/wEB + Af8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/0VFRf739/fm////zf// + /6z///+G////YP///0D///8n////Fv///wz///8F////Av///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////BP// + /wn///8S////If///zj///9Y////fv///6X////I/Pz84k9PT/0AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/zo6Ov/Hx8f9////+f////j////3////9v////b////2////9/// + //f////4////+fX19fnV1dX7u7u7/KqqqvykpKT8pKSk/Kqqqvy7u7v81dXV+/T09Pn////5////+P// + //f////3////9v////b////2////9/////j////6x8fH/Tg4OP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/09PT/38/Pzj////yf///6b///9+////WP///zj///8h////Ev// + /wn///8E////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8H////D////xv///8w////Tv///3P///+c////wv// + /991dXX7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/w0NDf+YmJj+/f39+v// + //j////3////9v////b////3////+P39/fnPz8/7i4uL/VJSUv8gICD/AQEB/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AQEB/x8fH/9RUVH/i4uL/dHR0fz////5////+P////f////2////9v// + //f////4/f39+pSUlP4MDAz/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/3R0 + dPv////f////wv///5z///9z////Tv///zD///8b////D////wf///8D////AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bf// + /wv///8W////J////0L///9m////kP///7j////Yra2t9QEBAf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8wMDD/2tra/P////n////4////9/////f////3////+dnZ2ft4eHj+Hx8f/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/yEhIf94eHj+2dnZ+/////n////4////9/////f////4////+djY2PwwMDD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/6ysrPX////Y////uP///5D///9m////Qv// + /yf///8W////C////wX///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////EP///x////82////V////4H///+r////z+Li + 4usUFBT/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/VlZW//Pz8/v////5////9/// + //f////4////+cfHx/xMTEz/AgIC/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8CAgL/TExM/8fH + x/z////5////+P////f////3////+fT09PtWVlb/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/FBQU/+Li4uv////P////q////4H///9X////Nv///x////8Q////CP///wP///8BAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bf// + /wz///8X////Kv///0j///9v////m////8P////hUVFR/QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/25ubv/8/Pz6////+P////f////3////+OLi4vtYWFj+AQEB/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AQEB/1hYWP7i4uL7////+P////f////3////+Pz8 + /PpsbGz/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/UFBQ/f///+H////D////m/// + /2////9I////Kv///xf///8M////Bf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////Ef///yD///85////Xf///4j///+z////1rCw + sPQAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9ra2v//f39+v////j////3////9/// + //menp79Dw8P/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/w8PD/+enp79////+f////f////3////+P39/fpra2v/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/r6+v9P///9b///+z////iP///13///85////IP///xH///8I////A/// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bf// + /wv///8X////K////0r///9z////oP///8j6+vrlKSkp/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/Tk5O//z8/Pv////4////9/////j4+Pj5YmJi/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9iYmL++Pj4+f// + //j////3////+Pz8/PtOTk7/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8oKCj/+vr65f// + /8j///+g////c////0r///8r////F////wv///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8H////EP///x////84////Xf///4n///+1////2ZeX + l/gAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIiIv/w8PD7////+f////f////49PT0+kRE + RP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9ERET/9PT0+v////j////3////+fDw8PsiIiL/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+Wlpb4////2f///7X///+J////Xf///zj///8f////EP// + /wf///8C////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////BP// + /wr///8W////KP///0f///9x////n////8j6+vrmIiIi/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8CAgL/w8PD/f////n////4////+Pj4+PpISEj/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP9JSUn/+fn5+v////j////4////+cPDw/0CAgL/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIi + Iv/6+vrm////yP///5////9x////R////yj///8V////Cv///wT///8BAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////Dv///xz///80////WP///4X///+z////16Ki + ovcAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/2dnZ//////6////+P////f////5bGxs/gAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9tbW3+////+f////f////4////+mdn + Z/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/6Ghoff////X////sv///4X///9Y////NP// + /xz///8O////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// + /wj///8T////JP///0H///9q////mP///8P////jNjY2/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8ODg7/6enp+/////n////3////+bGxsf0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP+xsbH9////+f////f////56enp+w4ODv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/NjY2/v///+P////D////mP///2n///9B////JP///xP///8I////A////wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8E////C////xj///8t////T////3z///+r////0snJ + yfEAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/39/f/7////6////+P////jw8PD6GRkZ/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xkZGf/w8PD6////+P// + //j////6f39//gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/ycnJ8f///9L///+q////e/// + /0////8t////F////wv///8E////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av// + /wb///8P////Hv///zf///9e////jf///7r////ea2tr/AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8ODg7/7e3t+/////j////4////+XZ2dv4AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/3Z2dv7////5////+P////jt7e37Dg4O/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP9ra2v8////3f///7r///+M////Xf///zf///8d////D////wb///8CAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////CP///xP///8k////Q////23///+d////yPr6 + +ucUFBT/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/25ubv/////6////+P////jn5+f7CgoK/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/CQkJ/+fn + 5/v////4////+P////pubm7/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xQUFP/6+vrn////yP// + /53///9t////Qv///yT///8S////CP///wP///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wT///8L////F////yz///9P////fP///6z////Uubm59QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/1tbW/P////n////4////+Xd3d/4AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/d3d3/v////n////4////+dbW1vwAAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/7m5ufX////T////rP///3z///9O////LP///xf///8K////BP// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bf///w3///8c////NP///1v///+L////uf// + /91paWn8AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/zg4OP/////6////+P////j19fX6FBQU/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8UFBT/9fX1+v////j////4////+jg4OP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/aWlp/P// + /93///+5////iv///1v///80////G////w3///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wL///8H////Ef///yH///89////aP///5n////F////5SIiIv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/kZGR/v////r////4////+aOjo/0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+jo6P9////+f////j////6kZGR/gAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8iIiL/////5f///8X///+Y////Z////z3///8h////EP// + /wf///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A////wn///8U////J////0f///90////pf// + /8/Y2NjwAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wEBAf/i4uL7////+f////j////6SUlJ/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/0lJSf/////6////+P////nh4eH8AQEB/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP/Y2Njv////z////6X///90////Rv///yb///8U////CP///wP///8BAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wH///8E////Cv///xf///8t////Uf///4D///+w////15eXl/gAAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/MTEx//////r////5////+e/v7/sFBQX/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/BQUF/+7u7vv////5////+f// + //oxMTH/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/5iYmPj////X////sP///4D///9Q////LP// + /xf///8K////BP///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wX///8N////G////zT///9b////i/// + /7r////eXV1d/QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP93d3f/////+v////j////5p6en/QAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/pqam/f////n////4////+nZ2dv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/Xl5e/f///97///+6////i////1r///8z////G////wz///8F////AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8C////Bv///w////8f////Ov///2T///+W////w////+QmJib/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/7a2tv3////5////+P////pjY2P/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9iYmL/////+v// + //j////5tbW1/QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8nJyf/////5P///8P///+W////ZP// + /zr///8f////D////wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8H////Ef///yP///9B////bv// + /5/////L8PDw6gICAv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8CAgL/7+/v+/////n////4////+iIi + Iv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIiIv/////6////+f////nu7u77AgIC/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wICAv/w8PDq////y////5////9t////Qf///yL///8R////B////wIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////A////wj///8U////J////0j///93////qP///9HBwcHzAAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/ygoKP/////6////+f////no6Oj7AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/+fn + 5/v////5////+f////snJyf/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/8HBwfP////R////qP// + /3b///9I////Jv///xT///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////Cv///xb///8r////T/// + /3////+w////15eXl/kAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/Wlpa//////r////4////+bKy + sv0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/srKy/f////n////4////+llZWf8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/mJiY+P///9f///+v////fv///07///8q////Fv///wr///8D////AQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////Af///wT///8L////GP///y////9V////hv///7b////ccHBw+wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP+Dg4P+////+v////j////5hYWF/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP+FhYX+////+f////j////6goKC/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9xcXH7////3P// + /7b///+G////Vf///y////8Y////C////wT///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Bf///wz///8a////Mv// + /1v///+N////vP///+BPT0/+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/6ysrP3////5////+P// + //pbW1v/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/1tbW//////6////+P////mqqqr9AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/1BQUP7////g////vP///43///9b////Mv///xr///8M////Bf// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wL///8F////Df///x3///82////YP///5L////B////4zIyMv4AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/zc3N/f////n////4////+jc3N/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/ODg4//////r////4////+czMzP0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/MjIy/v// + /+P////B////kv///2D///82////Hf///w3///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8P////Hv// + /zr///9l////mP///8X////mFhYW/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/t7e37////+f// + //n////6FhYW/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8WFhb/////+v////j////56+vr+wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8XFxf/////5v///8X///+Y////Zf///zr///8e////D/// + /wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8C////Bv///w////8g////Pf///2r///+c////yfz8/OgDAwP/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/CAgI//////v////5////+fj4+PsBAQH/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wEBAf/5+fn6////+P////j9/f37BwcH/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wMD + A//8/Pzo////yf///5z///9q////Pf///yD///8P////Bv///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8G////Ef// + /yL///9A////bv///6D////M6enp7AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8eHh7/////+v// + //j////54eHh+wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/+Pj4/v////4////+P// + //odHR3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/+rq6uz////M////oP///27///9A////Iv// + /xH///8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Av///wf///8S////I////0P///9x////o////87Z2dnvAAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/y8vL//////6////+P////jQ0ND9AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/0dHR/P////j////4////+i4uLv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/2dnZ7////87///+j////cf///0P///8j////Ev///wf///8CAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////B/// + /xL///8k////RP///3T///+m////0M3NzfIAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/PT09//// + //r////3////+MHBwfwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/CwsL8////+P// + //f////6PDw8/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/Ozs7y////0P///6b///90////RP// + /yT///8S////B////wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wL///8I////Ev///yX///9G////df///6f////Rw8PD8wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9GRkb/////+f////b////2t7e3+wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/7i4uPv////2////9v////lERET/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/8TExPP////R////p////3X///9G////Jf///xL///8I////Av///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// + /wj///8T////Jv///0f///92////qP///9LAwMD0AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/0xM + TP/////4////9P////SxsbH6AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/srKy+v// + //T////0////+EpKSv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/wcHB8////9L///+o////dv// + /0f///8l////E////wj///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////CP///xP///8m////R////3b///+o////0r29 + vfQAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/TU1N//////b////x////76+vr/cAAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+xsbH3////7/////H////2S0tL/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP++vr70////0v///6j///92////R////yb///8T////CP///wP///8BAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wP///8I////Ev///yX///9H////dv///6j////SwMDA9AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP9KSkr+////9P///+3////nsbGx8gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/7Ky + svL////n////7f////RISEj+AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/8HBwfP////S////qP// + /3b///9H////Jf///xL///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////Av///wj///8S////Jf///0b///91////p/// + /9HHx8fzAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/0JCQv7////y////5////9z////X////2P// + /93////i////5v///+f////o////6f///+n////p////6f///+n////p////6f///+n////p////6f// + /+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f// + /+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f// + /+n////p////6P///+f////m////4v///93////Y////1////9z////n////8kBAQP4AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/yMjI8v///9H///+n////df///0X///8k////Ev///wj///8C////AQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wH///8C////B////xL///8k////RP///3P///+l////0M/Pz/IAAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/OTk5//////D////g////z////8L///+9////v////8L////G////yP///8n////K////yv// + /8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv// + /8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv// + /8r////K////yv///8r////K////yv///8r////K////yv///8r////J////yP///8b////C////v/// + /73////C////z////+D////wNzc3/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP/Q0NDy////z/// + /6X///9z////RP///yT///8S////B////wL///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8H////Ef///yP///9C////cP// + /6L////N3t7e7gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8pKSn/////7v///9r////C////rP// + /53///+Y////mP///5r///+c////nf///57///+e////nv///57///+e////nv///57///+e////nv// + /57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv// + /57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv// + /57///+e////nv///53///+c////mv///5n///+Y////nf///6z////C////2v///+4nJyf/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/9/f3+3////N////ov///3D///9C////I////xH///8H////AgAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA////Av///wb///8Q////Iv///z////9s////n////8vv7+/qAAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/xYWFv/////t////1v///7f///+X////fv///3H///9r////av///2r///9q////a/// + /2v///9r////a////2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP// + /2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP// + /2z///9s////bP///2z///9s////bP///2z///9r////a////2v///9r////av///2r///9q////bP// + /3H///9+////mP///7f////W////7RQUFP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/8PDw6v// + /8v///+f////bP///z////8h////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv///w////8g////PP// + /2n///+b////yP///+cICAj/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AgIC//r6+u3////U////sf// + /4n///9m////UP///0b///9A////Pv///z7///8+////P////z////8/////P////z////8/////P/// + /z////8/////P////z////8/////P////z////8/////P////z////8/////P////z////8/////P/// + /z////8/////P////z////8/////P////z////8/////P////z////8/////P////z////8/////P/// + /z////8/////P////z7///8+////Pv///0D///9G////Uf///2f///+J////sf///9T4+PjtAQEB/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wkJCf/////n////x////5v///9o////PP///x////8P////Bv// + /wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wL///8F////Dv///x7///85////ZP///5b////E////5SAgIP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/4ODg8f///9X///+v////g////1r///8+////L////yb///8j////Iv// + /yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv// + /yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv// + /yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8j////Jv// + /y////8/////W////4T///+w////1d3d3fEAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/IiIi//// + /+X////D////lv///2P///85////Hv///w7///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wX///8N////HP// + /zX///9e////kP///7/////iOzs7/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+9vb31////1/// + /7H///+D////V////zb///8k////Gf///xP///8R////EP///xD///8Q////EP///xD///8Q////EP// + /xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP// + /xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP// + /xD///8Q////EP///xD///8Q////Ef///xT///8Z////JP///zf///9Y////hP///7L////Yu7u79QAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP88PDz+////4v///7////+Q////Xv///zX///8c////Df// + /wX///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8B////BP///wz///8a////Mf///1n///+K////uv///95cXFz9AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/5mZmfn////b////tv///4j///9Y////NP///x////8S////C/// + /wj///8H////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv// + /wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv// + /wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wf///8I////C/// + /xL///8f////Nf///1n///+J////t////9yXl5f6AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/15e + Xv3////e////uv///4r///9Z////Mf///xr///8M////BP///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8E////Cv// + /xf///8t////U////4P///+0////2n5+fvoAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/b29v/P// + /+D///+9////j////17///83////Hv///xD///8I////BP///wP///8C////Av///wL///8C////Av// + /wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av// + /wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av// + /wL///8C////Av///wL///8C////A////wT///8I////Ef///x////84////YP///5D///++////4W1t + bfwAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/gICA+v///9r///+0////g////1L///8t////F/// + /wr///8E////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8J////Ff///yn///9M////e////6z////VqKio9gAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9CQkL+////5f///8X///+Y////Z////z3///8h////Ef// + /wf///8D////Af///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8B////A/// + /wj///8R////Iv///z7///9o////mf///8b////lPz8//gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP+qqqr2////1f///6z///97////TP///yn///8V////Cf///wP///8BAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// + /wj///8T////Jf///0X///9z////pP///8/V1dXxAAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/w8P + D//8/Pzq////zf///6L///9y////Rf///yb///8U////Cf///wP///8BAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////Cf///xT///8m////Rv///3P///+k////zfv7 + ++oNDQ3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/9fX1/D////O////pP///3L///9F////JP// + /xP///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////Bv///xD///8h////Pv///2n///+b////yPz8 + /OcNDQ3/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/9DQ0PP////V////rv///37///9Q////Lf// + /xj///8L////BP///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wT///8L////GP///y3///9Q////f////67////Wzs7O9AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8PDw///Pz85////8f///+b////af///z7///8h////EP///wb///8CAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wL///8F////Dv///x3///83////YP///5H///+/////4kFBQf4AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/kJCQ+v///93///+5////i////1z///81////Hf///w7///8G////Av///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////Bv///w////8d////Nv///1z///+M////uv// + /96NjY36AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/0JCQv7////h////v////5H///9f////N/// + /x3///8O////Bf///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wT///8L////Gf///zD///9W////hv// + /7b////beXl5+wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9MTEz+////5f///8X///+a////av// + /0D///8k////E////wn///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wP///8J////E////yT///9B////av///5r////G////5UlJSf4AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/enp6+////9v///+1////hf///1b///8w////Gf///wv///8E////AQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////A////wr///8V////Kf///0z///96////q////9O6urr0AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/woKCv/09PTt////0f///6n///96////Tv///y3///8Y////DP///wb///8C////Af// + /wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Af///wH///8C////Bv///wz///8Z////Lf///07///97////qv// + /9Hz8/PuCQkJ/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP+8vLz0////0////6r///95////S/// + /yn///8V////Cf///wP///8BAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////CP///xL///8j////Qv// + /23///+e////yfb29ukKCgr/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/6ioqPj////c////uP// + /4v///9e////Of///yH///8S////Cf///wX///8D////Av///wL///8C////Av///wL///8C////Av// + /wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av// + /wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////Av///wL///8C////A/// + /wX///8J////Ev///yH///85////Xv///4z///+5////3KampvgAAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/CgoK//b29uj////J////nv///23///9B////I////xL///8I////Av///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wL///8G////D////x7///84////YP///5D///++////4EtLS/0AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/UFBQ/v///+b////I////n////3H///9J////Lf///xv///8R////C/// + /wj///8H////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv// + /wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv///wb///8G////Bv// + /wb///8G////Bv///wb///8G////Bv///wf///8I////C////xH///8b////Lf///0n///9x////n/// + /8j////mTk5O/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9NTU39////4P///77///+Q////YP// + /zj///8e////Dv///wb///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wT///8M////Gf// + /y////9T////gv///7H////XmZmZ+AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8FBQX/5OTk8f// + /9b///+y////h////17///8+////Kf///x3///8W////E////xH///8R////EP///xD///8Q////EP// + /xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP// + /xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8Q////EP///xD///8R////Ef// + /xP///8W////Hf///yn///8+////Xv///4f///+z////1+Pj4/EEBAT/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/5qamvj////X////sf///4L///9T////L////xn///8M////BP///wEAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8B////A////wn///8U////J////0b///9y////ov///8zr6+vrBgYG/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP99fX37////4////8b///+g////eP///1b///8/////Mf// + /yj///8k////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv// + /yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv///yL///8i////Iv// + /yL///8i////Iv///yL///8i////Iv///yL///8i////JP///yj///8x////P////1b///94////oP// + /8b////jfHx8/AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8HBwf/7Ozs6////8z///+i////cv// + /0b///8n////FP///wn///8D////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8C////B/// + /xD///8g////Ov///2L///+S////vv///+BOTk79AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xUV + Ff/09PTv////2P///7n///+W////dv///17///9P////Rv///0L///9A////P////z////8/////P/// + /z////8/////P////z////8/////P////z////8/////P////z////8/////P////z////8/////P/// + /z////8/////P////z////8/////P////z////8/////P////z////8/////P////z////8/////P/// + /0D///9C////Rv///0////9e////dv///5b///+5////2PPz8+8TExP/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/1BQUP3////g////vv///5L///9i////Ov///yD///8Q////B////wIAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL///8F////DP///xn///8v////U////4D///+v////1bCw + sPUAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/4aGhvz////o////0v///7f///+d////iP// + /3v///9z////b////23///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP// + /2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP///2z///9s////bP// + /2z///9s////bP///2z///9s////bP///2z///9s////bf///2////9z////e////4j///+d////t/// + /9L////og4OD/AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/tLS09f///9X///+v////gP// + /1P///8v////Gf///wz///8F////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af// + /wP///8J////FP///yb///9E////bv///5z////H/Pz85SIiIv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/EBAQ/+fn5/X////m////1f///8P///+0////qv///6T///+h////n////5////+e////nv// + /57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv// + /57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv///57///+e////nv// + /5////+f////of///6T///+q////tP///8P////V////5ubm5vUPDw//AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/yMjI//8/Pzl////x////5z///9u////RP///yb///8U////Cf///wP///8BAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Av///wb///8P////Hv///zb///9b////if// + /7b////Zj4+P+QAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/Wlpa/v////T////r////4P// + /9j////R////zv///8z////L////y////8r////K////yv///8r////K////yv///8r////K////yv// + /8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv///8r////K////yv// + /8r////K////yv///8r////K////yv///8r////K////y////8v////M////zv///9H////Y////4P// + /+v////0WFhY/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/kZGR+f///9n///+1////iP// + /1v///82////Hf///w////8G////AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD///8B////BP///wv///8X////K////0r///90////ov///8r09PTpFhYW/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/qqqq/f////f////z////7////+z////q////6f///+n////p////6f// + /+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f// + /+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f///+n////p////6f// + /+n////p////6f///+n////q////7P///+/////z////96enp/0AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/xgYGP/19fXo////yv///6H///90////Sv///yr///8X////C////wT///8BAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8D////CP///xH///8g////Ov// + /1////+M////t////9qOjo75AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/kZGR+f///9r///+3////i/// + /1////86////IP///xH///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP///wL///8F////DP///xj///8s////TP///3X///+i////yfj4+OclJSX/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/ycnJ//5+fnm////yf///6H///91////TP///yz///8Y////DP///wX///8CAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////Ev// + /yH///87////X////4r///+1////2Le3t/UAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8BAQH/ubm59f///9f///+0////iv// + /17///87////If///xH///8I////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA////Av///wX///8M////Gf///y3///9L////c////57////G////415e + Xv0AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/2BgYP3////j////xv///57///9z////S////yz///8Z////DP///wX///8CAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B////A/// + /wn///8S////Iv///zz///9f////if///7T////W8PDw7yMjI/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8kJCT/8fHx7////9b///+0////if// + /1////88////Iv///xL///8J////A////wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wH///8C////Bv///w7///8b////MP///0////93////ov// + /8n////lzMzM+AsLC/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/CgoK/8zMzPj////l////yf///6L///93////T////zD///8b////Dv///wb///8C////AQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP// + /wL///8E////C////xX///8n////Q////2f///+S////u////9z////wra2t/QICAv8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wMDA/+urq79////8P///9z///+7////kv// + /2f///9D////J////xX///8L////BP///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Af///wP///8I////Ev///yH///85////W/// + /4T///+v////0////+z////4lJSU/gAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAA + AP8AAAD/lpaW/v////j////s////0////6////+E////W////zn///8h////Ev///wj///8D////AQAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////4AAAAAAAAAAAB////////+AAAAAAAAAAAAf/ + ///////gAAAAAAAAAAAH////////4AAAAAAAAAAAB////////+AAAAAAAAAAAAf////////gAAAAAAAA + AAAH////////8AAAAAAAAAAAD/////////AAAAAAAAAAAA/////////wAAAAAAAAAAAP////////8AAA + AAAAAAAAD/////////gAAAAAAAAAAB/////////8AAAAAAAAAAA//////////AAAAAAAAAAAP/////// + //4AAAAAAAAAAH//////////AAAAAAAAAAD//////////8AAAAAAAAAD///////////4AAAAAAAAH/// + //////////+AAAAB////////////////wAAAA////////////////+AAAAf////////////////gAAAH + ////////////////4AAAB////////////////+AAAAf////////////////gAAAH//////////////// + 4AAAB////////////////+AAAAf////////////////gAAAH////////////////4AAAB/////////// + /////+AAAAf////////////////gAAAH////////////////4AAAB////////////////+AAAAf///// + ///////////gAAAH////////////////4AAAB////////////////+AAAAf////////////////gAAAH + ////////////////4AAAB////////////////+AAAAf////////////////gAAAH//////////////// + wAAAA////////////////8AAAAP///////////////8AAAAA///////////////4AAAAAB////////// + ////4AAAAAAH/////////////4AAAAAAAf////////////4AAAAAAAB////////////4AAAAAAAAH/// + ////////8AAAAAAAAA///////////+AAAAAAAAAH//////////+AAAAAAAAAAf//////////AAAAAAAA + AAD//////////gAAAAAAAAAAf/////////wAAAAAAAAAAD/////////4AAAAAAAAAAAf////////8AAA + AAAAAAAAD////////+AAAAAAAAAAAAf////////AAAAAAAAAAAAD////////wAAAAAAAAAAAA/////// + /4AAAAAAAAAAAAH///////8AAAAAAAAAAAAA////////AAAAAAAAAAAAAP///////gAAAAAAAAAAAAB/ + //////4AAAAAAAAAAAAAf//////8AAAAAAAAAAAAAD///////AAAAAAAAAAAAAA///////gAAAAAAAAA + AAAAH//////4AAAAAAAAAAAAAB//////8AAAAAAAAAAAAAAP//////AAAAAAAAAAAAAAD//////wAAAA + AAAAAAAAAA//////4AAAAAAAAAAAAAAH/////+AAAAAAAAAAAAAAB//////gAAAAAAAAAAAAAAf///// + wAAAAAAAAAAAAAAD/////8AAAAAAAAAAAAAAA//////AAAAAAAAAAAAAAAP/////wAAAAAAAAAAAAAAD + /////4AAAAAAAAAAAAAAAf////+AAAAAAAAAAAAAAAH/////gAAAAAAAAAAAAAAB/////4AAAAAAAAAA + AAAAAf////+AAAAAAAAAAAAAAAH/////AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAA + AAAAAAAAAAD/////AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAD///// + AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAD////+AAAAAAAAAAAAAAAA + f////gAAAAAAAAAAAAAAAH////4AAAAAAAAAAAAAAAB////+AAAAAAAAAAAAAAAAf////gAAAAAAAAAA + AAAAAH////4AAAAAAAAAAAAAAAB////+AAAAAAAAAAAAAAAAf////wAAAAAAAAAAAAAAAP////8AAAAA + AAAAAAAAAAD/////AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAAAAAAAAAAAAD///// + AAAAAAAAAAAAAAAA/////wAAAAAAAAAAAAAAAP////8AAAAH/////+AAAAD/////AAAAD//////wAAAA + /////4AAAA//////8AAAAf////+AAAAH/////+AAAAH/////gAAAB//////gAAAB/////4AAAAH///// + gAAAAf////+AAAAAAAAAAAAAAAH/////wAAAAAAAAAAAAAAD/////8AAAAAAAAAAAAAAA//////AAAAA + AAAAAAAAAAP/////4AAAAAAAAAAAAAAH/////+AAAAAAAAAAAAAAB//////gAAAAAAAAAAAAAAf///// + 8AAAAAAAAAAAAAAP//////AAAAAAAAAAAAAAD//////wAAAAAAAAAAAAAA//////+AAAAAAAAAAAAAAf + //////gAAAAAAAAAAAAAH//////8AAAAAAAAAAAAAD///////AAAAAAAAAAAAAA///////wAAAAAAAAA + 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 + AAAASUVORK5CYII= + + + \ No newline at end of file diff --git a/WinFormsDesigner/GridEntry.cs b/WinFormsDesigner/GridEntry.cs new file mode 100644 index 00000000..4a4383f5 --- /dev/null +++ b/WinFormsDesigner/GridEntry.cs @@ -0,0 +1,31 @@ +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 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; } + } +} diff --git a/WinFormsDesigner/ProductsGrid.Designer.cs b/WinFormsDesigner/ProductsGrid.Designer.cs new file mode 100644 index 00000000..744efa71 --- /dev/null +++ b/WinFormsDesigner/ProductsGrid.Designer.cs @@ -0,0 +1,191 @@ +namespace WinFormsDesigner +{ + partial class ProductsGrid + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + 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; + } +} diff --git a/WinFormsDesigner/ProductsGrid.cs b/WinFormsDesigner/ProductsGrid.cs new file mode 100644 index 00000000..6548709e --- /dev/null +++ b/WinFormsDesigner/ProductsGrid.cs @@ -0,0 +1,27 @@ +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(); + } + } +} diff --git a/WinFormsDesigner/ProductsGrid.resx b/WinFormsDesigner/ProductsGrid.resx new file mode 100644 index 00000000..d1166daf --- /dev/null +++ b/WinFormsDesigner/ProductsGrid.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/WinFormsDesigner/Program.cs b/WinFormsDesigner/Program.cs new file mode 100644 index 00000000..712e683d --- /dev/null +++ b/WinFormsDesigner/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace WinFormsDesigner +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/WinFormsDesigner/Properties/DataSources/WinFormsDesigner.GridEntry.datasource b/WinFormsDesigner/Properties/DataSources/WinFormsDesigner.GridEntry.datasource new file mode 100644 index 00000000..0105788b --- /dev/null +++ b/WinFormsDesigner/Properties/DataSources/WinFormsDesigner.GridEntry.datasource @@ -0,0 +1,10 @@ + + + + WinFormsDesigner.GridEntry, WinFormsDesigner, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null + \ No newline at end of file diff --git a/WinFormsDesigner/WinFormsDesigner.csproj b/WinFormsDesigner/WinFormsDesigner.csproj new file mode 100644 index 00000000..cc091b42 --- /dev/null +++ b/WinFormsDesigner/WinFormsDesigner.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {0807616A-A77A-4B08-A65A-1582B09E114B} + WinExe + WinFormsDesigner + WinFormsDesigner + v4.8 + 512 + true + + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + Form + + + Form1.cs + + + UserControl + + + ProductsGrid.cs + + + + + Form1.cs + + + ProductsGrid.cs + + + + + + \ No newline at end of file From 2f3c0e8a95f2ef2ba12740051074558de9201eb6 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 18:26:21 -0600 Subject: [PATCH 27/64] Revert "Removed WinFormsDesigner because it's not being used." This reverts commit 54d24a7b09cf624face58d68d288526d410224aa. --- Libation.sln | 7 +++++++ LibationLauncher/LibationLauncher.csproj | 2 +- .../ProcessorAutomationController.cs | 7 +++---- LibationWinForms/GridEntry.cs | 14 ++++++-------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Libation.sln b/Libation.sln index 191ce50a..9de9110b 100644 --- a/Libation.sln +++ b/Libation.sln @@ -52,6 +52,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AudibleApi.Tests", "..\audi 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}") = "Dinah.Core", "..\Dinah.Core\Dinah.Core\Dinah.Core.csproj", "{9E951521-2587-4FC6-AD26-FAA9179FB6C4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.EntityFrameworkCore", "..\Dinah.Core\Dinah.EntityFrameworkCore\Dinah.EntityFrameworkCore.csproj", "{1255D9BA-CE6E-42E4-A253-6376540B9661}" @@ -144,6 +146,10 @@ 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 {9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E951521-2587-4FC6-AD26-FAA9179FB6C4}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -226,6 +232,7 @@ Global {7EA01F9C-E579-4B01-A3B9-733B49DD0B60} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05} {111420E2-D4F0-4068-B46A-C4B6DCC823DC} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD} {635F00E1-AAD1-45F7-BEB7-D909AD33B9F6} = {8679CAC8-9164-4007-BDD2-F004810EDA14} + {0807616A-A77A-4B08-A65A-1582B09E114B} = {8679CAC8-9164-4007-BDD2-F004810EDA14} {9E951521-2587-4FC6-AD26-FAA9179FB6C4} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1} {1255D9BA-CE6E-42E4-A253-6376540B9661} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1} {35803735-B669-4090-9681-CC7F7FABDC71} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05} diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 973ba77e..7cda4a7d 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.141 + 5.4.9.140 diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 107f33ed..7496d81a 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -281,15 +281,14 @@ namespace LibationWinForms.BookLiberation #endregion #region Set initially displayed book properties from library info. - decryptDialog.SetTitle(actionName, libraryBook.Book.Title); decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors)); decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames)); decryptDialog.SetCoverImage( - WindowsDesktopUtilities.WinAudibleImageServer - .GetImage( + WindowsDesktopUtilities.WinAudibleImageServer.GetImage( libraryBook.Book.PictureId, - FileManager.PictureSize._80x80)); + FileManager.PictureSize._80x80 + )); #endregion #region define how model actions will affect form behavior diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index b64a9530..ae04e870 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -1,7 +1,6 @@ using ApplicationServices; using DataLayer; using Dinah.Core.Drawing; -using FileManager; using System; using System.Collections; using System.Collections.Generic; @@ -25,19 +24,19 @@ namespace LibationWinForms private Book Book => LibraryBook.Book; private Image _cover; - private PictureDefinition PictureDefinition { get; } public GridEntry(LibraryBook libraryBook) { LibraryBook = libraryBook; - PictureDefinition = new PictureDefinition(Book.PictureId, PictureSize._80x80); + _memberValues = CreateMemberValueDictionary(); //Get cover art. If it's default, subscribe to PictureCached - (bool isDefault, byte[] picture) = PictureStorage.GetPicture(PictureDefinition); + var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80); + (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); if (isDefault) - PictureStorage.PictureCached += PictureStorage_PictureCached; + FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; //Mutable property. Set the field so PropertyChanged isn't fired. _cover = ImageReader.ToImage(picture); @@ -64,9 +63,8 @@ namespace LibationWinForms { if (pictureId == Book.PictureId) { - (_, byte[] picture) = PictureStorage.GetPicture(PictureDefinition); - Cover = ImageReader.ToImage(picture); - PictureStorage.PictureCached -= PictureStorage_PictureCached; + Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); + FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; } } From 4989cda93c9f179b95a477eda5e4573e582349b1 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 20:16:34 -0600 Subject: [PATCH 28/64] Added synchronous Picture downloader. --- AaxDecrypter/AaxcDownloadConverter.cs | 34 +++++-------------- FileManager/PictureStorage.cs | 24 +++++++++++-- .../ProcessorAutomationController.cs | 27 +-------------- 3 files changed, 31 insertions(+), 54 deletions(-) diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs index 89d787f4..efbbda4a 100644 --- a/AaxDecrypter/AaxcDownloadConverter.cs +++ b/AaxDecrypter/AaxcDownloadConverter.cs @@ -1,19 +1,13 @@ using AAXClean; using Dinah.Core; -using Dinah.Core.Diagnostics; using Dinah.Core.IO; -using Dinah.Core.Logging; using Dinah.Core.StepRunner; using System; using System.IO; namespace AaxDecrypter { - public enum OutputFormat - { - Mp4a, - Mp3 - } + public enum OutputFormat { Mp4a, Mp3 } public class AaxcDownloadConverter { public event EventHandler RetrievedTags; @@ -26,7 +20,6 @@ namespace AaxDecrypter private string cacheDir { get; } private DownloadLicense downloadLicense { get; } private AaxFile aaxFile; - private byte[] coverArt; private OutputFormat OutputFormat; private StepSequence steps { get; } @@ -65,11 +58,15 @@ namespace AaxDecrypter }; } + /// + /// Setting cover art by this method will insert the art into the audiobook metadata + /// public void SetCoverArt(byte[] coverArt) { if (coverArt is null) return; - this.coverArt = coverArt; + aaxFile?.AppleTags.SetCoverArt(coverArt); + RetrievedCoverArt?.Invoke(this, coverArt); } @@ -98,7 +95,7 @@ namespace AaxDecrypter try { nfsPersister = new NetworkFileStreamPersister(jsonDownloadState); - //If More thaan ~1 hour has elapsed since getting the download url, it will expire. + //If More than ~1 hour has elapsed since getting the download url, it will expire. //The new url will be to the same file. nfsPersister.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl)); } @@ -116,10 +113,9 @@ namespace AaxDecrypter nfsPersister.NetworkFileStream.BeginDownloading(); aaxFile = new AaxFile(nfsPersister.NetworkFileStream); - coverArt = aaxFile.AppleTags.Cover; RetrievedTags?.Invoke(this, aaxFile.AppleTags); - RetrievedCoverArt?.Invoke(this, coverArt); + RetrievedCoverArt?.Invoke(this, aaxFile.AppleTags.Cover); return !isCanceled; } @@ -136,7 +132,6 @@ namespace AaxDecrypter public bool Step2_DownloadAndCombine() { - DecryptProgressUpdate?.Invoke(this, 0); if (File.Exists(outputFileName)) @@ -147,7 +142,6 @@ namespace AaxDecrypter aaxFile.SetDecryptionKey(downloadLicense.AudibleKey, downloadLicense.AudibleIV); aaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate; - var decryptionResult = OutputFormat == OutputFormat.Mp4a ? aaxFile.ConvertToMp4a(outFile, downloadLicense.ChapterInfo) : aaxFile.ConvertToMp3(outFile); aaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate; @@ -155,18 +149,6 @@ namespace AaxDecrypter downloadLicense.ChapterInfo = aaxFile.Chapters; - if (decryptionResult == ConversionResult.NoErrorsDetected - && coverArt is not null - && OutputFormat == OutputFormat.Mp4a) - { - //This handles a special case where the aaxc file doesn't contain cover art and - //Libation downloaded it instead (Animal Farm). Currently only works for Mp4a files. - using var decryptedBook = new Mp4File(outputFileName, FileAccess.ReadWrite); - decryptedBook.AppleTags?.SetCoverArt(coverArt); - decryptedBook.Save(); - decryptedBook.Close(); - } - nfsPersister.Dispose(); DecryptProgressUpdate?.Invoke(this, 0); diff --git a/FileManager/PictureStorage.cs b/FileManager/PictureStorage.cs index ef0ecdf2..4ea1bcf2 100644 --- a/FileManager/PictureStorage.cs +++ b/FileManager/PictureStorage.cs @@ -6,7 +6,7 @@ using System.Net.Http; namespace FileManager { - public enum PictureSize { _80x80, _300x300, _500x500 } + public enum PictureSize { _80x80 = 80, _300x300 = 300, _500x500 = 500 } public struct PictureDefinition { public string PictureId { get; } @@ -54,6 +54,26 @@ namespace FileManager return (cache[def] == null, cache[def] ?? getDefaultImage(def.Size)); } + public static byte[] GetPictureSynchronously(PictureDefinition def) + { + if (!cache.ContainsKey(def) || cache[def] == null) + { + var path = getPath(def); + byte[] bytes; + + if (File.Exists(path)) + bytes = File.ReadAllBytes(path); + else + { + bytes = downloadBytes(def); + saveFile(def, bytes); + } + + cache[def] = bytes; + } + return cache[def]; + } + private static Dictionary defaultImages { get; } = new Dictionary(); public static void SetDefaultImage(PictureSize pictureSize, byte[] bytes) => defaultImages[pictureSize] = bytes; @@ -100,7 +120,7 @@ namespace FileManager private static HttpClient imageDownloadClient { get; } = new HttpClient(); private static byte[] downloadBytes(PictureDefinition def) { - var sz = def.Size.ToString().Split('x')[1]; + var sz = ((int)def.Size).ToString(); return imageDownloadClient.GetByteArrayAsync("ht" + $"tps://images-na.ssl-images-amazon.com/images/I/{def.PictureId}._SL{sz}_.jpg").Result; } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 7496d81a..c8c5ced0 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -293,7 +293,6 @@ namespace LibationWinForms.BookLiberation #region define how model actions will affect form behavior void decryptBegin(object _, string __) => decryptDialog.Show(); - void titleDiscovered(object _, string title) => decryptDialog.SetTitle(actionName, title); void authorsDiscovered(object _, string authors) => decryptDialog.SetAuthorNames(authors); void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators); @@ -301,31 +300,7 @@ namespace LibationWinForms.BookLiberation void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage); void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining); void decryptCompleted(object _, string __) => decryptDialog.Close(); - - void requestCoverArt(object _, Action setCoverArtDelegate) - { - var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); - (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); - - if (isDefault) - { - void pictureCached(object _, string pictureId) - { - if (pictureId == libraryBook.Book.PictureId) - { - FileManager.PictureStorage.PictureCached -= pictureCached; - - var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500); - (_, picture) = FileManager.PictureStorage.GetPicture(picDef); - - setCoverArtDelegate(picture); - } - }; - FileManager.PictureStorage.PictureCached += pictureCached; - } - else - setCoverArtDelegate(picture); - } + void requestCoverArt(object _, Action setCoverArtDelegate) => setCoverArtDelegate(FileManager.PictureStorage.GetPictureSynchronously(new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500))); #endregion #region subscribe new form to model's events From d795244247ec8fb92a2c88f33556f8bade7e05e4 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 20:33:15 -0600 Subject: [PATCH 29/64] Updated PictureCached event and removed dependence on WinAudibleImageServer --- FileManager/PictureStorage.cs | 9 +++++++-- .../ProcessorAutomationController.cs | 16 ++++++++++----- LibationWinForms/GridEntry.cs | 20 +++++++++---------- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/FileManager/PictureStorage.cs b/FileManager/PictureStorage.cs index 4ea1bcf2..64e3ccfa 100644 --- a/FileManager/PictureStorage.cs +++ b/FileManager/PictureStorage.cs @@ -7,6 +7,11 @@ using System.Net.Http; namespace FileManager { public enum PictureSize { _80x80 = 80, _300x300 = 300, _500x500 = 500 } + public class PictureCachedEventArgs : EventArgs + { + public PictureDefinition Definition { get; internal set; } + public byte[] Picture { get; internal set; } + } public struct PictureDefinition { public string PictureId { get; } @@ -38,7 +43,7 @@ namespace FileManager timer.Elapsed += (_, __) => timerDownload(); } - public static event EventHandler PictureCached; + public static event EventHandler PictureCached; private static Dictionary cache { get; } = new Dictionary(); public static (bool isDefault, byte[] bytes) GetPicture(PictureDefinition def) @@ -109,7 +114,7 @@ namespace FileManager saveFile(def, bytes); cache[def] = bytes; - PictureCached?.Invoke(nameof(PictureStorage), def.PictureId); + PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs { Definition = def, Picture = bytes }); } finally { diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index c8c5ced0..16462100 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -285,10 +285,11 @@ namespace LibationWinForms.BookLiberation decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors)); decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames)); decryptDialog.SetCoverImage( - WindowsDesktopUtilities.WinAudibleImageServer.GetImage( - libraryBook.Book.PictureId, - FileManager.PictureSize._80x80 - )); + Dinah.Core.Drawing.ImageReader.ToImage( + FileManager.PictureStorage.GetPictureSynchronously( + new FileManager.PictureDefinition( + libraryBook.Book.PictureId, + FileManager.PictureSize._80x80)))); #endregion #region define how model actions will affect form behavior @@ -300,7 +301,12 @@ namespace LibationWinForms.BookLiberation void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage); void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining); void decryptCompleted(object _, string __) => decryptDialog.Close(); - void requestCoverArt(object _, Action setCoverArtDelegate) => setCoverArtDelegate(FileManager.PictureStorage.GetPictureSynchronously(new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500))); + void requestCoverArt(object _, Action setCoverArtDelegate) + => setCoverArtDelegate( + FileManager.PictureStorage.GetPictureSynchronously( + new FileManager.PictureDefinition( + libraryBook.Book.PictureId, + FileManager.PictureSize._500x500))); #endregion #region subscribe new form to model's events diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index ae04e870..b173051a 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -28,18 +28,18 @@ namespace LibationWinForms public GridEntry(LibraryBook libraryBook) { LibraryBook = libraryBook; - _memberValues = CreateMemberValueDictionary(); //Get cover art. If it's default, subscribe to PictureCached - var picDef = new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80); - (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef); + { + (bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(new FileManager.PictureDefinition(Book.PictureId, FileManager.PictureSize._80x80)); - if (isDefault) - FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; + if (isDefault) + FileManager.PictureStorage.PictureCached += PictureStorage_PictureCached; - //Mutable property. Set the field so PropertyChanged isn't fired. - _cover = ImageReader.ToImage(picture); + //Mutable property. Set the field so PropertyChanged isn't fired. + _cover = ImageReader.ToImage(picture); + } //Immutable properties { @@ -59,11 +59,11 @@ namespace LibationWinForms //DisplayTags and Liberate properties are live. } - private void PictureStorage_PictureCached(object sender, string pictureId) + private void PictureStorage_PictureCached(object sender, FileManager.PictureCachedEventArgs e) { - if (pictureId == Book.PictureId) + if (e.Definition.PictureId == Book.PictureId) { - Cover = WindowsDesktopUtilities.WinAudibleImageServer.GetImage(pictureId, FileManager.PictureSize._80x80); + Cover = ImageReader.ToImage(e.Picture); FileManager.PictureStorage.PictureCached -= PictureStorage_PictureCached; } } From 689ffc71a26cdaf6be5aec1c764e3ec8705770e5 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 20:38:17 -0600 Subject: [PATCH 30/64] Removed WindowsDesktopUtilities dependency. --- Libation.sln | 7 ------- LibationLauncher/LibationLauncher.csproj | 4 ++-- LibationWinForms/LibationWinForms.csproj | 5 +++-- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/Libation.sln b/Libation.sln index 9de9110b..777fbb84 100644 --- a/Libation.sln +++ b/Libation.sln @@ -74,8 +74,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationServices", "Appl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.Core.WindowsDesktop", "..\Dinah.Core\Dinah.Core.WindowsDesktop\Dinah.Core.WindowsDesktop.csproj", "{059CE32C-9AD6-45E9-A166-790DFFB0B730}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsDesktopUtilities", "WindowsDesktopUtilities\WindowsDesktopUtilities.csproj", "{E7EFD64D-6630-4426-B09C-B6862A92E3FD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationLauncher", "LibationLauncher\LibationLauncher.csproj", "{F3B04A3A-20C8-4582-A54A-715AF6A5D859}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "0 Libation Tests", "0 Libation Tests", "{67E66E82-5532-4440-AFB3-9FB1DF9DEF53}" @@ -190,10 +188,6 @@ Global {059CE32C-9AD6-45E9-A166-790DFFB0B730}.Debug|Any CPU.Build.0 = Debug|Any CPU {059CE32C-9AD6-45E9-A166-790DFFB0B730}.Release|Any CPU.ActiveCfg = Release|Any CPU {059CE32C-9AD6-45E9-A166-790DFFB0B730}.Release|Any CPU.Build.0 = Release|Any CPU - {E7EFD64D-6630-4426-B09C-B6862A92E3FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E7EFD64D-6630-4426-B09C-B6862A92E3FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E7EFD64D-6630-4426-B09C-B6862A92E3FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E7EFD64D-6630-4426-B09C-B6862A92E3FD}.Release|Any CPU.Build.0 = Release|Any CPU {F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Debug|Any CPU.Build.0 = Debug|Any CPU {F3B04A3A-20C8-4582-A54A-715AF6A5D859}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -243,7 +237,6 @@ Global {282EEE16-F569-47E1-992F-C6DB8AEC7AA6} = {F61184E7-2426-4A13-ACEF-5689928E2CE2} {B95650EA-25F0-449E-BA5D-99126BC5D730} = {41CDCC73-9B81-49DD-9570-C54406E852AF} {059CE32C-9AD6-45E9-A166-790DFFB0B730} = {43E3ACB3-E0BC-4370-8DBB-E3720C8C8FD1} - {E7EFD64D-6630-4426-B09C-B6862A92E3FD} = {F0CBB7A7-D3FB-41FF-8F47-CF3F6A592249} {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} diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 7cda4a7d..66f1127c 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -1,5 +1,5 @@  - + WinExe @@ -13,7 +13,7 @@ win-x64 - 5.4.9.140 + 5.4.9.158 diff --git a/LibationWinForms/LibationWinForms.csproj b/LibationWinForms/LibationWinForms.csproj index 6ee0b7db..343425c6 100644 --- a/LibationWinForms/LibationWinForms.csproj +++ b/LibationWinForms/LibationWinForms.csproj @@ -1,4 +1,5 @@ - + + Library @@ -12,8 +13,8 @@ + - From de75543b3373750c62c6a3432e5611f0c275738c Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 20:39:11 -0600 Subject: [PATCH 31/64] Removed WindowsDesktopUtilities --- .../WinAudibleImageServer.cs | 28 ------------------- .../WindowsDesktopUtilities.csproj | 16 ----------- 2 files changed, 44 deletions(-) delete mode 100644 WindowsDesktopUtilities/WinAudibleImageServer.cs delete mode 100644 WindowsDesktopUtilities/WindowsDesktopUtilities.csproj diff --git a/WindowsDesktopUtilities/WinAudibleImageServer.cs b/WindowsDesktopUtilities/WinAudibleImageServer.cs deleted file mode 100644 index 1b3eeab2..00000000 --- a/WindowsDesktopUtilities/WinAudibleImageServer.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using Dinah.Core.Drawing; -using FileManager; - -namespace WindowsDesktopUtilities -{ - public static class WinAudibleImageServer - { - private static Dictionary cache { get; } = new Dictionary(); - - public static Image GetImage(string pictureId, PictureSize size) - { - var def = new PictureDefinition(pictureId, size); - if (!cache.ContainsKey(def)) - { - (var isDefault, var bytes) = PictureStorage.GetPicture(def); - - var image = ImageReader.ToImage(bytes); - if (isDefault) - return image; - cache[def] = image; - } - return cache[def]; - } - } -} diff --git a/WindowsDesktopUtilities/WindowsDesktopUtilities.csproj b/WindowsDesktopUtilities/WindowsDesktopUtilities.csproj deleted file mode 100644 index 88b1cbf5..00000000 --- a/WindowsDesktopUtilities/WindowsDesktopUtilities.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Library - net5.0-windows - true - - - - - - - - - - \ No newline at end of file From 9c1f620223b35bae9356e939fe5998fc53f9c4ee Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 20:54:43 -0600 Subject: [PATCH 32/64] Revert indenting --- LibationLauncher/Program.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs index ab4c6e1c..fde65698 100644 --- a/LibationLauncher/Program.cs +++ b/LibationLauncher/Program.cs @@ -148,7 +148,7 @@ namespace LibationLauncher CancelInstallation(); } -#region migrate to v5.0.0 re-register device if device info not in settings + #region migrate to v5.0.0 re-register device if device info not in settings private static void migrate_to_v5_0_0(Configuration config) { if (!config.Exists(nameof(config.AllowLibationFixup))) @@ -190,9 +190,9 @@ namespace LibationLauncher } } } -#endregion + #endregion -#region migrate to v5.2.0 + #region migrate to v5.2.0 // get rid of meta-directories, combine DownloadsInProgressEnum and DecryptInProgressEnum => InProgress private static void migrate_to_v5_2_0__pre_config() { @@ -234,9 +234,9 @@ namespace LibationLauncher if (!config.Exists(nameof(config.DecryptToLossy))) config.DecryptToLossy = false; } -#endregion + #endregion -#region migrate to v5.4.1 see comment + #region migrate to v5.4.1 see comment // this 'migration' is a bit different. it intentionally runs each time Libation is started. its job will be fulfilled when I eventually // implement the portion which removes FilePaths.json, at which time this method will be a proper migration // @@ -300,7 +300,7 @@ namespace LibationLauncher debugStopwatch.Stop(); var debugTotal = debugStopwatch.Elapsed; } -#endregion + #endregion private static void ensureSerilogConfig(Configuration config) { From 963d632208b05553664cec0419fd77b2339a8a08 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Tue, 10 Aug 2021 21:14:33 -0600 Subject: [PATCH 33/64] Removed space. --- LibationLauncher/Program.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs index fde65698..0786e414 100644 --- a/LibationLauncher/Program.cs +++ b/LibationLauncher/Program.cs @@ -421,7 +421,6 @@ namespace LibationLauncher if (latest is null) return; - var latestVersionString = latest.TagName.Trim('v'); if (!Version.TryParse(latestVersionString, out var latestRelease)) return; From 0045cf05efb9bb4cc522f5321a189f5217224d3a Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 11 Aug 2021 18:08:38 -0600 Subject: [PATCH 34/64] Redesign DookLiberation control flow. --- AaxDecrypter/AaxcDownloadConverter.cs | 24 +- FileLiberator/BackupBook.cs | 23 +- FileLiberator/ConvertToMp3.cs | 49 +- FileLiberator/DownloadDecryptBook.cs | 44 +- FileLiberator/DownloadFile.cs | 19 +- FileLiberator/DownloadableBase.cs | 16 +- .../{IDecryptable.cs => IAudioDecodable.cs} | 9 +- FileLiberator/IDownloadable.cs | 12 - FileLiberator/IDownloadableProcessable.cs | 4 - FileLiberator/IStreamProcessable.cs | 5 + FileLiberator/IStreamable.cs | 13 + FileManager/PictureStorage.cs | 2 +- LibationLauncher/LibationLauncher.csproj | 2 +- LibationLauncher/Program.cs | 3 + .../BookLiberation/AudioConvertForm.cs | 28 + ...ner.cs => AudioDecodeBaseForm.Designer.cs} | 2 +- .../BookLiberation/AudioDecodeBaseForm.cs | 129 ++++ ...ryptForm.resx => AudioDecodeBaseForm.resx} | 0 .../BookLiberation/AudioDecryptForm.cs | 24 + .../BookLiberation/DecryptForm.cs | 55 -- .../BookLiberation/DownloadForm.cs | 24 +- .../BookLiberation/PdfDownloadForm.cs | 16 + .../BookLiberation/ProcessBaseForm.cs | 45 ++ .../ProcessorAutomationController.cs | 306 ++------- .../BookLiberation/StreamBaseForm.cs | 52 ++ LibationWinForms/Form1.Designer.cs | 625 +++++++++--------- LibationWinForms/Form1.cs | 9 + LibationWinForms/Form1.resx | 3 +- 28 files changed, 817 insertions(+), 726 deletions(-) rename FileLiberator/{IDecryptable.cs => IAudioDecodable.cs} (56%) delete mode 100644 FileLiberator/IDownloadable.cs delete mode 100644 FileLiberator/IDownloadableProcessable.cs create mode 100644 FileLiberator/IStreamProcessable.cs create mode 100644 FileLiberator/IStreamable.cs create mode 100644 LibationWinForms/BookLiberation/AudioConvertForm.cs rename LibationWinForms/BookLiberation/{DecryptForm.Designer.cs => AudioDecodeBaseForm.Designer.cs} (99%) create mode 100644 LibationWinForms/BookLiberation/AudioDecodeBaseForm.cs rename LibationWinForms/BookLiberation/{DecryptForm.resx => AudioDecodeBaseForm.resx} (100%) create mode 100644 LibationWinForms/BookLiberation/AudioDecryptForm.cs delete mode 100644 LibationWinForms/BookLiberation/DecryptForm.cs create mode 100644 LibationWinForms/BookLiberation/PdfDownloadForm.cs create mode 100644 LibationWinForms/BookLiberation/ProcessBaseForm.cs create mode 100644 LibationWinForms/BookLiberation/StreamBaseForm.cs diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs index efbbda4a..86794123 100644 --- a/AaxDecrypter/AaxcDownloadConverter.cs +++ b/AaxDecrypter/AaxcDownloadConverter.cs @@ -1,6 +1,7 @@ using AAXClean; using Dinah.Core; using Dinah.Core.IO; +using Dinah.Core.Net.Http; using Dinah.Core.StepRunner; using System; using System.IO; @@ -12,8 +13,9 @@ namespace AaxDecrypter { public event EventHandler RetrievedTags; public event EventHandler RetrievedCoverArt; - public event EventHandler DecryptProgressUpdate; + public event EventHandler DecryptProgressUpdate; public event EventHandler DecryptTimeRemaining; + public string AppName { get; set; } = nameof(AaxcDownloadConverter); private string outputFileName { get; } @@ -132,7 +134,14 @@ namespace AaxDecrypter public bool Step2_DownloadAndCombine() { - DecryptProgressUpdate?.Invoke(this, 0); + var zeroProgress = new DownloadProgress + { + BytesReceived = 0, + ProgressPercentage = 0, + TotalBytesToReceive = nfsPersister.NetworkFileStream.Length + }; + + DecryptProgressUpdate?.Invoke(this, zeroProgress); if (File.Exists(outputFileName)) FileExt.SafeDelete(outputFileName); @@ -151,7 +160,7 @@ namespace AaxDecrypter nfsPersister.Dispose(); - DecryptProgressUpdate?.Invoke(this, 0); + DecryptProgressUpdate?.Invoke(this, zeroProgress); return decryptionResult == ConversionResult.NoErrorsDetected && !isCanceled; } @@ -167,7 +176,13 @@ namespace AaxDecrypter double progressPercent = 100 * e.ProcessPosition.TotalSeconds / duration.TotalSeconds; - DecryptProgressUpdate?.Invoke(this, (int)progressPercent); + DecryptProgressUpdate?.Invoke(this, + new DownloadProgress + { + ProgressPercentage = progressPercent, + BytesReceived = (long)(nfsPersister.NetworkFileStream.Length * progressPercent), + TotalBytesToReceive = nfsPersister.NetworkFileStream.Length + }); } public bool Step3_CreateCue() @@ -209,6 +224,7 @@ namespace AaxDecrypter { isCanceled = true; aaxFile?.Cancel(); + aaxFile?.Dispose(); } } } diff --git a/FileLiberator/BackupBook.cs b/FileLiberator/BackupBook.cs index 718e0e7f..c7050b4c 100644 --- a/FileLiberator/BackupBook.cs +++ b/FileLiberator/BackupBook.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using DataLayer; using Dinah.Core.ErrorHandling; -using FileManager; namespace FileLiberator { @@ -20,10 +19,16 @@ namespace FileLiberator public event EventHandler StatusUpdate; public event EventHandler Completed; - public DownloadDecryptBook DownloadDecryptBook { get; } = new DownloadDecryptBook(); - public DownloadPdf DownloadPdf { get; } = new DownloadPdf(); + public DownloadDecryptBook DownloadDecryptBook { get; } + public DownloadPdf DownloadPdf { get; } - public bool Validate(LibraryBook libraryBook) + public BackupBook(DownloadDecryptBook downloadDecryptBook, DownloadPdf downloadPdf) + { + DownloadDecryptBook = downloadDecryptBook; + DownloadPdf = downloadPdf; + } + + public bool Validate(LibraryBook libraryBook) => !ApplicationServices.TransitionalFileLocator.Audio_Exists(libraryBook.Book); // do NOT use ConfigureAwait(false) on ProcessAsync() @@ -35,16 +40,16 @@ namespace FileLiberator try { { - var statusHandler = await DownloadDecryptBook.TryProcessAsync(libraryBook); - if (statusHandler.HasErrors) + var statusHandler = await DownloadDecryptBook.TryProcessAsync(libraryBook); + if (statusHandler.HasErrors) return statusHandler; } - { - var statusHandler = await DownloadPdf.TryProcessAsync(libraryBook); + { + var statusHandler = await DownloadPdf.TryProcessAsync(libraryBook); if (statusHandler.HasErrors) return statusHandler; - } + } return new StatusHandler(); } diff --git a/FileLiberator/ConvertToMp3.cs b/FileLiberator/ConvertToMp3.cs index 92eddcfa..6b514837 100644 --- a/FileLiberator/ConvertToMp3.cs +++ b/FileLiberator/ConvertToMp3.cs @@ -3,6 +3,7 @@ using DataLayer; using Dinah.Core; using Dinah.Core.ErrorHandling; using Dinah.Core.IO; +using Dinah.Core.Net.Http; using FileManager; using System; using System.IO; @@ -11,25 +12,26 @@ using System.Threading.Tasks; namespace FileLiberator { - public class ConvertToMp3 : IDecryptable + public class ConvertToMp3 : IAudioDecodable { - public event EventHandler DecryptBegin; - public event EventHandler TitleDiscovered; - public event EventHandler AuthorsDiscovered; - public event EventHandler NarratorsDiscovered; - public event EventHandler CoverImageFilepathDiscovered; - public event EventHandler UpdateProgress; - public event EventHandler UpdateRemainingTime; - public event EventHandler DecryptCompleted; - public event EventHandler Begin; - public event EventHandler Completed; - - public event EventHandler StatusUpdate; - public event EventHandler> RequestCoverArt; private Mp4File m4bBook; - private string Mp3FileName(string m4bPath) => m4bPath is null ? string.Empty : PathLib.ReplaceExtension(m4bPath, ".mp3"); + public event EventHandler StreamingTimeRemaining; + public event EventHandler> RequestCoverArt; + public event EventHandler TitleDiscovered; + public event EventHandler AuthorsDiscovered; + public event EventHandler NarratorsDiscovered; + public event EventHandler CoverImageFilepathDiscovered; + public event EventHandler StreamingBegin; + public event EventHandler StreamingProgressChanged; + public event EventHandler StreamingCompleted; + public event EventHandler Begin; + public event EventHandler StatusUpdate; + public event EventHandler Completed; + + private long fileSize; + private string Mp3FileName(string m4bPath) => m4bPath is null ? string.Empty : PathLib.ReplaceExtension(m4bPath, ".mp3"); public void Cancel() => m4bBook?.Cancel(); @@ -43,15 +45,16 @@ namespace FileLiberator { Begin?.Invoke(this, libraryBook); - DecryptBegin?.Invoke(this, $"Begin converting {libraryBook} to mp3"); + StreamingBegin?.Invoke(this, $"Begin converting {libraryBook} to mp3"); try { var m4bPath = ApplicationServices.TransitionalFileLocator.Audio_GetPath(libraryBook.Book); - m4bBook = new Mp4File(m4bPath, FileAccess.Read); m4bBook.ConversionProgressUpdate += M4bBook_ConversionProgressUpdate; + fileSize = m4bBook.InputStream.Length; + TitleDiscovered?.Invoke(this, m4bBook.AppleTags.Title); AuthorsDiscovered?.Invoke(this, m4bBook.AppleTags.FirstAuthor); NarratorsDiscovered?.Invoke(this, m4bBook.AppleTags.Narrator); @@ -76,7 +79,7 @@ namespace FileLiberator } finally { - DecryptCompleted?.Invoke(this, $"Completed converting to mp3: {libraryBook.Book.Title}"); + StreamingCompleted?.Invoke(this, $"Completed converting to mp3: {libraryBook.Book.Title}"); Completed?.Invoke(this, libraryBook); } } @@ -88,11 +91,17 @@ namespace FileLiberator double estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed; if (double.IsNormal(estTimeRemaining)) - UpdateRemainingTime?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining)); + StreamingTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining)); double progressPercent = 100 * e.ProcessPosition.TotalSeconds / duration.TotalSeconds; - UpdateProgress?.Invoke(this, (int)progressPercent); + StreamingProgressChanged?.Invoke(this, + new DownloadProgress + { + ProgressPercentage = progressPercent, + BytesReceived = (long)(fileSize * progressPercent), + TotalBytesToReceive = fileSize + }); } } } diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index a2b961c6..8565a1da 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -8,27 +8,30 @@ using AudibleApi; using DataLayer; using Dinah.Core; using Dinah.Core.ErrorHandling; +using Dinah.Core.Net.Http; using FileManager; namespace FileLiberator { - public class DownloadDecryptBook : IDecryptable + public class DownloadDecryptBook : IAudioDecodable { - public event EventHandler> RequestCoverArt; - public event EventHandler Begin; - public event EventHandler DecryptBegin; - public event EventHandler TitleDiscovered; - public event EventHandler AuthorsDiscovered; - public event EventHandler NarratorsDiscovered; - public event EventHandler CoverImageFilepathDiscovered; - public event EventHandler UpdateProgress; - public event EventHandler UpdateRemainingTime; - public event EventHandler DecryptCompleted; - public event EventHandler Completed; - public event EventHandler StatusUpdate; - private AaxcDownloadConverter aaxcDownloader; - public async Task ProcessAsync(LibraryBook libraryBook) + private AaxcDownloadConverter aaxcDownloader; + + public event EventHandler StreamingTimeRemaining; + public event EventHandler> RequestCoverArt; + public event EventHandler TitleDiscovered; + public event EventHandler AuthorsDiscovered; + public event EventHandler NarratorsDiscovered; + public event EventHandler CoverImageFilepathDiscovered; + public event EventHandler StreamingBegin; + public event EventHandler StreamingProgressChanged; + public event EventHandler StreamingCompleted; + public event EventHandler Begin; + public event EventHandler StatusUpdate; + public event EventHandler Completed; + + public async Task ProcessAsync(LibraryBook libraryBook) { Begin?.Invoke(this, libraryBook); @@ -63,7 +66,7 @@ namespace FileLiberator private async Task aaxToM4bConverterDecryptAsync(string cacheDir, string destinationDir, LibraryBook libraryBook) { - DecryptBegin?.Invoke(this, $"Begin decrypting {libraryBook}"); + StreamingBegin?.Invoke(this, $"Begin decrypting {libraryBook}"); try { @@ -103,8 +106,8 @@ namespace FileLiberator aaxcDownloader = new AaxcDownloadConverter(outFileName, cacheDir, aaxcDecryptDlLic, format) { AppName = "Libation" }; - aaxcDownloader.DecryptProgressUpdate += (s, progress) => UpdateProgress?.Invoke(this, progress); - aaxcDownloader.DecryptTimeRemaining += (s, remaining) => UpdateRemainingTime?.Invoke(this, remaining); + aaxcDownloader.DecryptProgressUpdate += (s, progress) => StreamingProgressChanged?.Invoke(this, progress); + aaxcDownloader.DecryptTimeRemaining += (s, remaining) => StreamingTimeRemaining?.Invoke(this, remaining); aaxcDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt; aaxcDownloader.RetrievedTags += aaxcDownloader_RetrievedTags; @@ -119,11 +122,12 @@ namespace FileLiberator } finally { - DecryptCompleted?.Invoke(this, $"Completed downloading and decrypting {libraryBook.Book.Title}"); + StreamingCompleted?.Invoke(this, $"Completed downloading and decrypting {libraryBook.Book.Title}"); } } - private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e) + + private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e) { if (e is null && Configuration.Instance.AllowLibationFixup) { diff --git a/FileLiberator/DownloadFile.cs b/FileLiberator/DownloadFile.cs index 13f38479..d4735420 100644 --- a/FileLiberator/DownloadFile.cs +++ b/FileLiberator/DownloadFile.cs @@ -6,20 +6,21 @@ using Dinah.Core.Net.Http; namespace FileLiberator { // currently only used to download the .zip flies for upgrade - public class DownloadFile : IDownloadable + public class DownloadFile : IStreamable { - public event EventHandler DownloadBegin; - public event EventHandler DownloadProgressChanged; - public event EventHandler DownloadCompleted; + public event EventHandler StreamingBegin; + public event EventHandler StreamingProgressChanged; + public event EventHandler StreamingCompleted; + public event EventHandler StreamingTimeRemaining; public async Task PerformDownloadFileAsync(string downloadUrl, string proposedDownloadFilePath) { var client = new HttpClient(); var progress = new Progress(); - progress.ProgressChanged += (_, e) => DownloadProgressChanged?.Invoke(this, e); + progress.ProgressChanged += OnProgressChanged; - DownloadBegin?.Invoke(this, proposedDownloadFilePath); + StreamingBegin?.Invoke(this, proposedDownloadFilePath); try { @@ -28,8 +29,12 @@ namespace FileLiberator } finally { - DownloadCompleted?.Invoke(this, proposedDownloadFilePath); + StreamingCompleted?.Invoke(this, proposedDownloadFilePath); } } + private void OnProgressChanged(object sender, DownloadProgress e) + { + StreamingProgressChanged?.Invoke(this, e); + } } } diff --git a/FileLiberator/DownloadableBase.cs b/FileLiberator/DownloadableBase.cs index da78a8a6..9156e626 100644 --- a/FileLiberator/DownloadableBase.cs +++ b/FileLiberator/DownloadableBase.cs @@ -6,16 +6,18 @@ using Dinah.Core.Net.Http; namespace FileLiberator { - public abstract class DownloadableBase : IDownloadableProcessable + public abstract class DownloadableBase : IStreamProcessable { public event EventHandler Begin; public event EventHandler Completed; - public event EventHandler DownloadBegin; - public event EventHandler DownloadProgressChanged; - public event EventHandler DownloadCompleted; + public event EventHandler StreamingBegin; + public event EventHandler StreamingProgressChanged; + public event EventHandler StreamingCompleted; public event EventHandler StatusUpdate; + public event EventHandler StreamingTimeRemaining; + protected void Invoke_StatusUpdate(string message) => StatusUpdate?.Invoke(this, message); public abstract bool Validate(LibraryBook libraryBook); @@ -44,9 +46,9 @@ namespace FileLiberator protected async Task PerformDownloadAsync(string proposedDownloadFilePath, Func, Task> func) { var progress = new Progress(); - progress.ProgressChanged += (_, e) => DownloadProgressChanged?.Invoke(this, e); + progress.ProgressChanged += (_, e) => StreamingProgressChanged?.Invoke(this, e); - DownloadBegin?.Invoke(this, proposedDownloadFilePath); + StreamingBegin?.Invoke(this, proposedDownloadFilePath); try { @@ -57,7 +59,7 @@ namespace FileLiberator } finally { - DownloadCompleted?.Invoke(this, proposedDownloadFilePath); + StreamingCompleted?.Invoke(this, proposedDownloadFilePath); } } } diff --git a/FileLiberator/IDecryptable.cs b/FileLiberator/IAudioDecodable.cs similarity index 56% rename from FileLiberator/IDecryptable.cs rename to FileLiberator/IAudioDecodable.cs index 568c3624..8eaab624 100644 --- a/FileLiberator/IDecryptable.cs +++ b/FileLiberator/IAudioDecodable.cs @@ -1,20 +1,17 @@ -using System; +using Dinah.Core.Net.Http; +using System; namespace FileLiberator { - public interface IDecryptable : IProcessable + public interface IAudioDecodable : IStreamProcessable { - event EventHandler DecryptBegin; event EventHandler> RequestCoverArt; event EventHandler TitleDiscovered; event EventHandler AuthorsDiscovered; event EventHandler NarratorsDiscovered; event EventHandler CoverImageFilepathDiscovered; - event EventHandler UpdateProgress; - event EventHandler UpdateRemainingTime; - event EventHandler DecryptCompleted; void Cancel(); } } diff --git a/FileLiberator/IDownloadable.cs b/FileLiberator/IDownloadable.cs deleted file mode 100644 index 782d96ee..00000000 --- a/FileLiberator/IDownloadable.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using Dinah.Core.Net.Http; - -namespace FileLiberator -{ - public interface IDownloadable - { - event EventHandler DownloadBegin; - event EventHandler DownloadProgressChanged; - event EventHandler DownloadCompleted; - } -} diff --git a/FileLiberator/IDownloadableProcessable.cs b/FileLiberator/IDownloadableProcessable.cs deleted file mode 100644 index 6c8b41de..00000000 --- a/FileLiberator/IDownloadableProcessable.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace FileLiberator -{ - public interface IDownloadableProcessable : IDownloadable, IProcessable { } -} diff --git a/FileLiberator/IStreamProcessable.cs b/FileLiberator/IStreamProcessable.cs new file mode 100644 index 00000000..360663ab --- /dev/null +++ b/FileLiberator/IStreamProcessable.cs @@ -0,0 +1,5 @@ + +namespace FileLiberator +{ + public interface IStreamProcessable : IStreamable, IProcessable { } +} diff --git a/FileLiberator/IStreamable.cs b/FileLiberator/IStreamable.cs new file mode 100644 index 00000000..94049a7a --- /dev/null +++ b/FileLiberator/IStreamable.cs @@ -0,0 +1,13 @@ +using System; +using Dinah.Core.Net.Http; + +namespace FileLiberator +{ + public interface IStreamable + { + event EventHandler StreamingBegin; + event EventHandler StreamingProgressChanged; + event EventHandler StreamingTimeRemaining; + event EventHandler StreamingCompleted; + } +} diff --git a/FileManager/PictureStorage.cs b/FileManager/PictureStorage.cs index 64e3ccfa..4fdd894b 100644 --- a/FileManager/PictureStorage.cs +++ b/FileManager/PictureStorage.cs @@ -125,7 +125,7 @@ namespace FileManager private static HttpClient imageDownloadClient { get; } = new HttpClient(); private static byte[] downloadBytes(PictureDefinition def) { - var sz = ((int)def.Size).ToString(); + var sz = (int)def.Size; return imageDownloadClient.GetByteArrayAsync("ht" + $"tps://images-na.ssl-images-amazon.com/images/I/{def.PictureId}._SL{sz}_.jpg").Result; } diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 66f1127c..ca7b53f8 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.158 + 5.4.9.200 diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs index 0786e414..0184b1a7 100644 --- a/LibationLauncher/Program.cs +++ b/LibationLauncher/Program.cs @@ -63,6 +63,9 @@ namespace LibationLauncher #if !DEBUG checkForUpdate(config); #endif + LibationWinForms.BookLiberation.ProcessorAutomationController.DownloadFile( + "https://github.com/rmcrackan/Libation/releases/download/v5.4.9/Libation.5.4.9.zip", + @"C:\Users\mbuca\Downloads\libation test dl.zip"); Application.Run(new Form1()); } diff --git a/LibationWinForms/BookLiberation/AudioConvertForm.cs b/LibationWinForms/BookLiberation/AudioConvertForm.cs new file mode 100644 index 00000000..0dde6860 --- /dev/null +++ b/LibationWinForms/BookLiberation/AudioConvertForm.cs @@ -0,0 +1,28 @@ +using DataLayer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibationWinForms.BookLiberation +{ + class AudioConvertForm : AudioDecodeBaseForm + { + #region AudioDecodeBaseForm overrides + public override string DecodeActionName => "Converting"; + #endregion + + #region IProcessable event handler overrides + public override void OnBegin(object sender, LibraryBook libraryBook) + { + InfoLogAction($"Convert Step, Begin: {libraryBook.Book}"); + + base.OnBegin(sender, libraryBook); + } + public override void OnCompleted(object sender, LibraryBook libraryBook) + => InfoLogAction($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + + #endregion + } +} diff --git a/LibationWinForms/BookLiberation/DecryptForm.Designer.cs b/LibationWinForms/BookLiberation/AudioDecodeBaseForm.Designer.cs similarity index 99% rename from LibationWinForms/BookLiberation/DecryptForm.Designer.cs rename to LibationWinForms/BookLiberation/AudioDecodeBaseForm.Designer.cs index e279b1ec..d38e4bbb 100644 --- a/LibationWinForms/BookLiberation/DecryptForm.Designer.cs +++ b/LibationWinForms/BookLiberation/AudioDecodeBaseForm.Designer.cs @@ -1,6 +1,6 @@ namespace LibationWinForms.BookLiberation { - partial class DecryptForm + partial class AudioDecodeBaseForm { /// /// Required designer variable. diff --git a/LibationWinForms/BookLiberation/AudioDecodeBaseForm.cs b/LibationWinForms/BookLiberation/AudioDecodeBaseForm.cs new file mode 100644 index 00000000..3354460a --- /dev/null +++ b/LibationWinForms/BookLiberation/AudioDecodeBaseForm.cs @@ -0,0 +1,129 @@ +using DataLayer; +using Dinah.Core.Net.Http; +using Dinah.Core.Windows.Forms; +using FileLiberator; +using System; + +namespace LibationWinForms.BookLiberation +{ + public partial class AudioDecodeBaseForm : ProcessBaseForm + { + public virtual string DecodeActionName { get; } = "Decoding"; + public AudioDecodeBaseForm() => InitializeComponent(); + + private Func GetCoverArtDelegate; + + // book info + private string title; + private string authorNames; + private string narratorNames; + + public override void SetProcessable(IStreamable streamProcessable, Action infoLog) + { + base.SetProcessable(streamProcessable, infoLog); + + if (Streamable is not null && Streamable is IAudioDecodable audioDecodable) + { + OnUnsubscribeAll(this, EventArgs.Empty); + + audioDecodable.RequestCoverArt += OnRequestCoverArt; + audioDecodable.TitleDiscovered += OnTitleDiscovered; + audioDecodable.AuthorsDiscovered += OnAuthorsDiscovered; + audioDecodable.NarratorsDiscovered += OnNarratorsDiscovered; + audioDecodable.CoverImageFilepathDiscovered += OnCoverImageFilepathDiscovered; + + Disposed += OnUnsubscribeAll; + } + } + private void OnUnsubscribeAll(object sender, EventArgs e) + { + Disposed -= OnUnsubscribeAll; + if (Streamable is not null && Streamable is IAudioDecodable audioDecodable) + { + audioDecodable.RequestCoverArt -= OnRequestCoverArt; + audioDecodable.TitleDiscovered -= OnTitleDiscovered; + audioDecodable.AuthorsDiscovered -= OnAuthorsDiscovered; + audioDecodable.NarratorsDiscovered -= OnNarratorsDiscovered; + audioDecodable.CoverImageFilepathDiscovered -= OnCoverImageFilepathDiscovered; + + audioDecodable.Cancel(); + } + } + + #region IProcessable event handler overrides + public override void OnBegin(object sender, LibraryBook libraryBook) + { + GetCoverArtDelegate = () => FileManager.PictureStorage.GetPictureSynchronously( + new FileManager.PictureDefinition( + libraryBook.Book.PictureId, + FileManager.PictureSize._500x500)); + + //Set default values from library + OnTitleDiscovered(null, libraryBook.Book.Title); + OnAuthorsDiscovered(null, string.Join(", ", libraryBook.Book.Authors)); + OnNarratorsDiscovered(null, string.Join(", ", libraryBook.Book.NarratorNames)); + OnCoverImageFilepathDiscovered(null, + FileManager.PictureStorage.GetPictureSynchronously( + new FileManager.PictureDefinition( + libraryBook.Book.PictureId, + FileManager.PictureSize._80x80))); + } + #endregion + + #region IStreamable event handler overrides + + public override void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) + { + if (!downloadProgress.ProgressPercentage.HasValue) + return; + + if (downloadProgress.ProgressPercentage == 0) + updateRemainingTime(0); + else + progressBar1.UIThread(() => progressBar1.Value = (int)downloadProgress.ProgressPercentage); + } + + public override void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) + => updateRemainingTime((int)timeRemaining.TotalSeconds); + + #endregion + + #region IAudioDecodable event handlers + + public virtual void OnRequestCoverArt(object sender, Action setCoverArtDelegate) + => setCoverArtDelegate(GetCoverArtDelegate?.Invoke()); + + public virtual void OnTitleDiscovered(object sender, string title) + { + this.UIThread(() => this.Text = DecodeActionName + " " + title); + this.title = title; + updateBookInfo(); + } + + public virtual void OnAuthorsDiscovered(object sender, string authors) + { + authorNames = authors; + updateBookInfo(); + } + + public virtual void OnNarratorsDiscovered(object sender, string narrators) + { + narratorNames = narrators; + updateBookInfo(); + } + + public virtual void OnCoverImageFilepathDiscovered(object sender, byte[] coverArt) + => pictureBox1.UIThread(() => 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}"); + + private void updateRemainingTime(int remaining) + => remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec"); + + } +} diff --git a/LibationWinForms/BookLiberation/DecryptForm.resx b/LibationWinForms/BookLiberation/AudioDecodeBaseForm.resx similarity index 100% rename from LibationWinForms/BookLiberation/DecryptForm.resx rename to LibationWinForms/BookLiberation/AudioDecodeBaseForm.resx diff --git a/LibationWinForms/BookLiberation/AudioDecryptForm.cs b/LibationWinForms/BookLiberation/AudioDecryptForm.cs new file mode 100644 index 00000000..45b6b5c8 --- /dev/null +++ b/LibationWinForms/BookLiberation/AudioDecryptForm.cs @@ -0,0 +1,24 @@ +using DataLayer; +using System; + +namespace LibationWinForms.BookLiberation +{ + class AudioDecryptForm : AudioDecodeBaseForm + { + #region AudioDecodeBaseForm overrides + public override string DecodeActionName => "Decrypting"; + #endregion + + #region IProcessable event handler overrides + public override void OnBegin(object sender, LibraryBook libraryBook) + { + InfoLogAction($"Download & Decrypt Step, Begin: {libraryBook.Book}"); + + base.OnBegin(sender, libraryBook); + } + public override void OnCompleted(object sender, LibraryBook libraryBook) + => InfoLogAction($"Download & Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + + #endregion + } +} diff --git a/LibationWinForms/BookLiberation/DecryptForm.cs b/LibationWinForms/BookLiberation/DecryptForm.cs deleted file mode 100644 index d39089e7..00000000 --- a/LibationWinForms/BookLiberation/DecryptForm.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Dinah.Core.Windows.Forms; -using System; -using System.Linq; -using System.Windows.Forms; - -namespace LibationWinForms.BookLiberation -{ - public partial class DecryptForm : Form - { - public DecryptForm() => InitializeComponent(); - - // book info - private string title; - private string authorNames; - private string narratorNames; - - public void SetTitle(string actionName, string title) - { - this.UIThread(() => this.Text = actionName + " " + title); - this.title = title; - updateBookInfo(); - } - public void SetAuthorNames(string authorNames) - { - this.authorNames = authorNames; - updateBookInfo(); - } - public void SetNarratorNames(string narratorNames) - { - this.narratorNames = narratorNames; - updateBookInfo(); - } - - // thread-safe UI updates - private void updateBookInfo() - => bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"); - - public void SetCoverImage(System.Drawing.Image coverImage) - => pictureBox1.UIThread(() => pictureBox1.Image = coverImage); - - public void UpdateProgress(int percentage) - { - if (percentage == 0) - updateRemainingTime(0); - else - progressBar1.UIThread(() => progressBar1.Value = percentage); - } - - public void UpdateRemainingTime(TimeSpan remaining) - => updateRemainingTime((int)remaining.TotalSeconds); - - private void updateRemainingTime(int remaining) - => remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec"); - } -} diff --git a/LibationWinForms/BookLiberation/DownloadForm.cs b/LibationWinForms/BookLiberation/DownloadForm.cs index 7a260a05..563a68fd 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.cs +++ b/LibationWinForms/BookLiberation/DownloadForm.cs @@ -1,11 +1,13 @@ -using Dinah.Core.Windows.Forms; +using DataLayer; +using Dinah.Core.Net.Http; +using Dinah.Core.Windows.Forms; using System; using System.Linq; using System.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class DownloadForm : Form + public partial class DownloadForm : ProcessBaseForm { public DownloadForm() { @@ -15,23 +17,27 @@ namespace LibationWinForms.BookLiberation filenameLbl.Text = ""; } - // thread-safe UI updates - public void UpdateFilename(string title) => filenameLbl.UIThread(() => filenameLbl.Text = title); - - public void DownloadProgressChanged(long BytesReceived, long? TotalBytesToReceive) + #region IStreamable event handler overrides + public override void OnStreamingBegin(object sender, string beginString) + { + filenameLbl.UIThread(() => filenameLbl.Text = beginString); + base.OnStreamingBegin(sender, beginString); + } + public override void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { // this won't happen with download file. it will happen with download string - if (!TotalBytesToReceive.HasValue || TotalBytesToReceive.Value <= 0) + if (!downloadProgress.TotalBytesToReceive.HasValue || downloadProgress.TotalBytesToReceive.Value <= 0) return; - progressLbl.UIThread(() => progressLbl.Text = $"{BytesReceived:#,##0} of {TotalBytesToReceive.Value:#,##0}"); + progressLbl.UIThread(() => progressLbl.Text = $"{downloadProgress.BytesReceived:#,##0} of {downloadProgress.TotalBytesToReceive.Value:#,##0}"); - var d = double.Parse(BytesReceived.ToString()) / double.Parse(TotalBytesToReceive.Value.ToString()) * 100.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); lastDownloadProgress = DateTime.Now; } + #endregion #region timer private Timer timer { get; } = new Timer { Interval = 1000 }; diff --git a/LibationWinForms/BookLiberation/PdfDownloadForm.cs b/LibationWinForms/BookLiberation/PdfDownloadForm.cs new file mode 100644 index 00000000..870871e9 --- /dev/null +++ b/LibationWinForms/BookLiberation/PdfDownloadForm.cs @@ -0,0 +1,16 @@ +using DataLayer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LibationWinForms.BookLiberation +{ + internal class PdfDownloadForm : DownloadForm + { + public override void OnBegin(object sender, LibraryBook libraryBook) => InfoLogAction($"PDF Step, Begin: {libraryBook.Book}"); + public override void OnCompleted(object sender, LibraryBook libraryBook) => InfoLogAction($"PDF Step, Completed: {libraryBook.Book}"); + + } +} diff --git a/LibationWinForms/BookLiberation/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/ProcessBaseForm.cs new file mode 100644 index 00000000..acd8b6bd --- /dev/null +++ b/LibationWinForms/BookLiberation/ProcessBaseForm.cs @@ -0,0 +1,45 @@ +using DataLayer; +using Dinah.Core.Net.Http; +using FileLiberator; +using System; +using System.Windows.Forms; + +namespace LibationWinForms.BookLiberation +{ + public class ProcessBaseForm : StreamBaseForm + { + protected Action InfoLogAction { get; private set; } + public virtual void SetProcessable(IStreamable streamable, Action infoLog) + { + InfoLogAction = infoLog; + SetStreamable(streamable); + + if (Streamable is not null && Streamable is IProcessable processable) + { + OnUnsubscribeAll(this, EventArgs.Empty); + + processable.Begin += OnBegin; + processable.Completed += OnCompleted; + processable.StatusUpdate += OnStatusUpdate; + Disposed += OnUnsubscribeAll; + } + } + + private void OnUnsubscribeAll(object sender, EventArgs e) + { + Disposed -= OnUnsubscribeAll; + if (Streamable is not null && Streamable is IProcessable processable) + { + processable.Begin -= OnBegin; + processable.Completed -= OnCompleted; + processable.StatusUpdate -= OnStatusUpdate; + } + } + + #region IProcessable event handlers + public virtual void OnBegin(object sender, LibraryBook libraryBook) => InfoLogAction($"Begin: {libraryBook.Book}"); + public virtual void OnStatusUpdate(object sender, string statusUpdate) => InfoLogAction("- " + statusUpdate); + public virtual void OnCompleted(object sender, LibraryBook libraryBook) => InfoLogAction($"Completed: {libraryBook.Book}{Environment.NewLine}"); + #endregion + } +} diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 16462100..8cbc11ba 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -55,153 +55,65 @@ namespace LibationWinForms.BookLiberation { Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId }); - var backupBook = getWiredUpBackupBook(completedAction); - - (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook); + LogMe logMe = LogMe.RegisterForm(); + var backupBook = CreateBackupBook(completedAction, logMe); // continue even if libraryBook is null. we'll display even that in the processing box await new BackupSingle(logMe, backupBook, libraryBook).RunBackupAsync(); - - unsubscribeEvents(); } public static async Task BackupAllBooksAsync(EventHandler completedAction = null) { Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync)); - var backupBook = getWiredUpBackupBook(completedAction); var automatedBackupsForm = new AutomatedBackupsForm(); + LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); - (Action unsubscribeEvents, LogMe logMe) = attachToBackupsForm(backupBook, automatedBackupsForm); + var backupBook = CreateBackupBook(completedAction, logMe); await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync(); - unsubscribeEvents(); } public static async Task ConvertAllBooksAsync() { Serilog.Log.Logger.Information("Begin " + nameof(ConvertAllBooksAsync)); - var convertBook = new ConvertToMp3(); - convertBook.Begin += (_, l) => wireUpEvents(convertBook, l, "Converting"); - var automatedBackupsForm = new AutomatedBackupsForm(); + LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); - var logMe = LogMe.RegisterForm(automatedBackupsForm); - - void statusUpdate(object _, string str) => logMe.Info("- " + str); - void convertBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Begin: {libraryBook.Book}"); - void convertBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - convertBook.Begin += convertBookBegin; - convertBook.StatusUpdate += statusUpdate; - convertBook.Completed += convertBookCompleted; + var convertBook = CreateStreamableProcessable(null, logMe); await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); - - convertBook.Begin -= convertBookBegin; - convertBook.StatusUpdate -= statusUpdate; - convertBook.Completed -= convertBookCompleted; } - private static BackupBook getWiredUpBackupBook(EventHandler completedAction) + private static BackupBook CreateBackupBook(EventHandler completedAction, LogMe logMe) { - var backupBook = new BackupBook(); - - backupBook.DownloadDecryptBook.Begin += (_, l) => wireUpEvents(backupBook.DownloadDecryptBook, l); - backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf); - - if (completedAction != null) - { - backupBook.DownloadDecryptBook.Completed += completedAction; - backupBook.DownloadPdf.Completed += completedAction; - } - - return backupBook; - } - - private static (Action unsubscribeEvents, LogMe) attachToBackupsForm(BackupBook backupBook, AutomatedBackupsForm automatedBackupsForm = null) - { - #region create logger - var logMe = LogMe.RegisterForm(automatedBackupsForm); - #endregion - - #region define how model actions will affect form behavior - void statusUpdate(object _, string str) => logMe.Info("- " + str); - void decryptBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Begin: {libraryBook.Book}"); - // extra line after book is completely finished - void decryptBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - void downloadPdfBegin(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Begin: {libraryBook.Book}"); - // extra line after book is completely finished - void downloadPdfCompleted(object _, LibraryBook libraryBook) => logMe.Info($"PDF Step, Completed: {libraryBook.Book}{Environment.NewLine}"); - #endregion - - #region subscribe new form to model's events - backupBook.DownloadDecryptBook.Begin += decryptBookBegin; - backupBook.DownloadDecryptBook.StatusUpdate += statusUpdate; - backupBook.DownloadDecryptBook.Completed += decryptBookCompleted; - backupBook.DownloadPdf.Begin += downloadPdfBegin; - backupBook.DownloadPdf.StatusUpdate += statusUpdate; - backupBook.DownloadPdf.Completed += downloadPdfCompleted; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - Action unsubscribe = () => - { - backupBook.DownloadDecryptBook.Begin -= decryptBookBegin; - backupBook.DownloadDecryptBook.StatusUpdate -= statusUpdate; - backupBook.DownloadDecryptBook.Completed -= decryptBookCompleted; - backupBook.DownloadPdf.Begin -= downloadPdfBegin; - backupBook.DownloadPdf.StatusUpdate -= statusUpdate; - backupBook.DownloadPdf.Completed -= downloadPdfCompleted; - }; - #endregion - - return (unsubscribe, logMe); + var downloadPdf = CreateStreamableProcessable(completedAction, logMe); + var downloadDecryptBook = CreateStreamableProcessable(completedAction, logMe); + return new BackupBook(downloadDecryptBook, downloadPdf); } public static async Task BackupAllPdfsAsync(EventHandler completedAction = null) { Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); - var downloadPdf = getWiredUpDownloadPdf(completedAction); + var automatedBackupsForm = new AutomatedBackupsForm(); + LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); + + var downloadPdf = CreateStreamableProcessable(completedAction, logMe); - (AutomatedBackupsForm automatedBackupsForm, LogMe logMe) = attachToBackupsForm(downloadPdf); await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); } - private static DownloadPdf getWiredUpDownloadPdf(EventHandler completedAction) - { - var downloadPdf = new DownloadPdf(); - - downloadPdf.Begin += (_, __) => wireUpEvents(downloadPdf); - - if (completedAction != null) - downloadPdf.Completed += completedAction; - - return downloadPdf; - } - public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) { - var downloadDialog = new DownloadForm(); - downloadDialog.UpdateFilename(destination); - downloadDialog.Show(); - new System.Threading.Thread(() => { - var downloadFile = new DownloadFile(); + (DownloadFile downloadFile, DownloadForm downloadForm) = CreateStreamable(); - downloadFile.DownloadProgressChanged += (_, progress) => downloadDialog.UIThread(() => - downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive) - ); - downloadFile.DownloadCompleted += (_, __) => downloadDialog.UIThread(() => - { - downloadDialog.Close(); - if (showDownloadCompletedDialog) - MessageBox.Show("File downloaded"); - }); + if (showDownloadCompletedDialog) + downloadFile.StreamingCompleted += (_, __) => MessageBox.Show("File downloaded"); downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult(); }) @@ -209,171 +121,41 @@ namespace LibationWinForms.BookLiberation .Start(); } - // subscribed to Begin event because a new form should be created+processed+closed on each iteration - private static void wireUpEvents(IDownloadableProcessable downloadable) + /// + /// Create a new and which creates a new on IProcessable.Begin. + /// + /// The derrived type to create. + /// The derrived form to create on Begin + /// An additional event handler to handle .Completed + /// A new of type + private static TStrProc CreateStreamableProcessable(EventHandler completedAction = null, LogMe logMe = null) + where TForm : ProcessBaseForm, new() + where TStrProc : IStreamProcessable, new() { - #region create form - var downloadDialog = new DownloadForm(); - #endregion + var strProc = new TStrProc(); - // extra complexity for wiring up download form: - // case 1: download is needed - // dialog created. subscribe to events - // downloadable.DownloadBegin fires. shows dialog - // downloadable.DownloadCompleted fires. closes dialog. which fires FormClosing, FormClosed, Disposed - // Disposed unsubscribe from events - // case 2: download is not needed - // dialog created. subscribe to events - // dialog is never shown nor closed - // downloadable.Completed fires. disposes dialog and unsubscribes from events - - #region define how model actions will affect form behavior - void downloadBegin(object _, string str) + strProc.Begin += (sender, libraryBook) => { - downloadDialog.UpdateFilename(str); - downloadDialog.Show(); - } - - // close form on DOWNLOAD completed, not final Completed. Else for BackupBook this form won't close until DECRYPT is also complete - void fileDownloadCompleted(object _, string __) => downloadDialog.Close(); - - void downloadProgressChanged(object _, Dinah.Core.Net.Http.DownloadProgress progress) - => downloadDialog.DownloadProgressChanged(progress.BytesReceived, progress.TotalBytesToReceive); - - void unsubscribe(object _ = null, EventArgs __ = null) - { - downloadable.DownloadBegin -= downloadBegin; - downloadable.DownloadCompleted -= fileDownloadCompleted; - downloadable.DownloadProgressChanged -= downloadProgressChanged; - downloadable.Completed -= dialogDispose; - } - - // unless we dispose, if the form is created but un-used/never-shown then weird UI stuff can happen - // also, since event unsubscribe occurs on FormClosing and an unused form is never closed, then the events will never be unsubscribed - void dialogDispose(object _, object __) - { - if (!downloadDialog.IsDisposed) - downloadDialog.Dispose(); - } - #endregion - - #region subscribe new form to model's events - downloadable.DownloadBegin += downloadBegin; - downloadable.DownloadCompleted += fileDownloadCompleted; - downloadable.DownloadProgressChanged += downloadProgressChanged; - downloadable.Completed += dialogDispose; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - // FormClosing is more UI safe but won't fire unless the form is shown and closed - // if form was shown, Disposed will fire for FormClosing, FormClosed, and Disposed - // if not shown, it will still fire for Disposed - downloadDialog.Disposed += unsubscribe; - #endregion - } - - // subscribed to Begin event because a new form should be created+processed+closed on each iteration - private static void wireUpEvents(IDecryptable decryptBook, LibraryBook libraryBook, string actionName = "Decrypting") - { - #region create form - var decryptDialog = new DecryptForm(); - #endregion - - #region Set initially displayed book properties from library info. - decryptDialog.SetTitle(actionName, libraryBook.Book.Title); - decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors)); - decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames)); - decryptDialog.SetCoverImage( - Dinah.Core.Drawing.ImageReader.ToImage( - FileManager.PictureStorage.GetPictureSynchronously( - new FileManager.PictureDefinition( - libraryBook.Book.PictureId, - FileManager.PictureSize._80x80)))); - #endregion - - #region define how model actions will affect form behavior - void decryptBegin(object _, string __) => decryptDialog.Show(); - void titleDiscovered(object _, string title) => decryptDialog.SetTitle(actionName, title); - void authorsDiscovered(object _, string authors) => decryptDialog.SetAuthorNames(authors); - void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators); - void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(Dinah.Core.Drawing.ImageReader.ToImage(coverBytes)); - void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage); - void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining); - void decryptCompleted(object _, string __) => decryptDialog.Close(); - void requestCoverArt(object _, Action setCoverArtDelegate) - => setCoverArtDelegate( - FileManager.PictureStorage.GetPictureSynchronously( - new FileManager.PictureDefinition( - libraryBook.Book.PictureId, - FileManager.PictureSize._500x500))); - #endregion - - #region subscribe new form to model's events - decryptBook.DecryptBegin += decryptBegin; - - decryptBook.TitleDiscovered += titleDiscovered; - decryptBook.AuthorsDiscovered += authorsDiscovered; - decryptBook.NarratorsDiscovered += narratorsDiscovered; - decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered; - decryptBook.UpdateProgress += updateProgress; - decryptBook.UpdateRemainingTime += updateRemainingTime; - decryptBook.RequestCoverArt += requestCoverArt; - - decryptBook.DecryptCompleted += decryptCompleted; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - decryptDialog.FormClosing += (_, __) => - { - decryptBook.DecryptBegin -= decryptBegin; - - decryptBook.TitleDiscovered -= titleDiscovered; - decryptBook.AuthorsDiscovered -= authorsDiscovered; - decryptBook.NarratorsDiscovered -= narratorsDiscovered; - decryptBook.CoverImageFilepathDiscovered -= coverImageFilepathDiscovered; - decryptBook.UpdateProgress -= updateProgress; - decryptBook.UpdateRemainingTime -= updateRemainingTime; - decryptBook.RequestCoverArt -= requestCoverArt; - - decryptBook.DecryptCompleted -= decryptCompleted; - decryptBook.Cancel(); + var processForm = new TForm(); + processForm.SetProcessable(strProc, logMe.Info); + processForm.OnBegin(sender, libraryBook); }; - #endregion + + if (completedAction != null) + strProc.Completed += completedAction; + + return strProc; } - - private static (AutomatedBackupsForm, LogMe) attachToBackupsForm(IDownloadableProcessable downloadable) + private static (TStrProc, TForm) CreateStreamable(EventHandler completedAction = null) + where TForm : StreamBaseForm, new() + where TStrProc : IStreamable, new() { - #region create form and logger - var automatedBackupsForm = new AutomatedBackupsForm(); - var logMe = LogMe.RegisterForm(automatedBackupsForm); - #endregion + var strProc = new TStrProc(); - #region define how model actions will affect form behavior - void begin(object _, LibraryBook libraryBook) => logMe.Info($"Begin: {libraryBook.Book}"); - void statusUpdate(object _, string str) => logMe.Info("- " + str); - // extra line after book is completely finished - void completed(object _, LibraryBook libraryBook) => logMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); - #endregion + var streamForm = new TForm(); + streamForm.SetStreamable(strProc); - #region subscribe new form to model's events - downloadable.Begin += begin; - downloadable.StatusUpdate += statusUpdate; - downloadable.Completed += completed; - #endregion - - #region when form closes, unsubscribe from model's events - // unsubscribe so disposed forms aren't still trying to receive notifications - automatedBackupsForm.FormClosing += (_, __) => - { - downloadable.Begin -= begin; - downloadable.StatusUpdate -= statusUpdate; - downloadable.Completed -= completed; - }; - #endregion - - return (automatedBackupsForm, logMe); + return (strProc, streamForm); } } diff --git a/LibationWinForms/BookLiberation/StreamBaseForm.cs b/LibationWinForms/BookLiberation/StreamBaseForm.cs new file mode 100644 index 00000000..f6907131 --- /dev/null +++ b/LibationWinForms/BookLiberation/StreamBaseForm.cs @@ -0,0 +1,52 @@ +using Dinah.Core.Net.Http; +using FileLiberator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace LibationWinForms.BookLiberation +{ + public class StreamBaseForm : Form + { + protected IStreamable Streamable { get; private set; } + public void SetStreamable(IStreamable streamable) + { + Streamable = streamable; + + if (Streamable is null) return; + + OnUnsubscribeAll(this, EventArgs.Empty); + + Streamable.StreamingBegin += OnStreamingBegin; + Streamable.StreamingCompleted += OnStreamingCompleted; + Streamable.StreamingProgressChanged += OnStreamingProgressChanged; + Streamable.StreamingTimeRemaining += OnStreamingTimeRemaining; + + Disposed += OnUnsubscribeAll; + } + + private void OnUnsubscribeAll(object sender, EventArgs e) + { + Disposed -= OnUnsubscribeAll; + + Streamable.StreamingBegin -= OnStreamingBegin; + Streamable.StreamingCompleted -= OnStreamingCompleted; + Streamable.StreamingProgressChanged -= OnStreamingProgressChanged; + Streamable.StreamingTimeRemaining -= OnStreamingTimeRemaining; + } + + #region IStreamable event handlers + public virtual void OnStreamingBegin(object sender, string beginString) => Show(); + public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } + public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } + public virtual void OnStreamingCompleted(object sender, string completedString) + { + Close(); + Dispose(); + } + #endregion + } +} diff --git a/LibationWinForms/Form1.Designer.cs b/LibationWinForms/Form1.Designer.cs index 3762f817..ad89b4ef 100644 --- a/LibationWinForms/Form1.Designer.cs +++ b/LibationWinForms/Form1.Designer.cs @@ -28,339 +28,351 @@ /// 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.removeLibraryBooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.removeAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.removeSomeAccountsToolStripMenuItem = 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.convertAllM4bToMp3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.exportLibraryToolStripMenuItem = 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.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.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.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.removeLibraryBooksToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeAllAccountsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.removeSomeAccountsToolStripMenuItem = 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.convertAllM4bToMp3ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.exportLibraryToolStripMenuItem = 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.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.button1 = new System.Windows.Forms.Button(); + 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(14, 65); - this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.gridPanel.Name = "gridPanel"; - this.gridPanel.Size = new System.Drawing.Size(979, 445); - this.gridPanel.TabIndex = 5; - // - // filterHelpBtn - // - this.filterHelpBtn.Location = new System.Drawing.Point(14, 31); - this.filterHelpBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.filterHelpBtn.Name = "filterHelpBtn"; - this.filterHelpBtn.Size = new System.Drawing.Size(26, 27); - this.filterHelpBtn.TabIndex = 3; - this.filterHelpBtn.Text = "?"; - this.filterHelpBtn.UseVisualStyleBackColor = true; - this.filterHelpBtn.Click += new System.EventHandler(this.filterHelpBtn_Click); - // - // 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(905, 31); - this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.filterBtn.Name = "filterBtn"; - this.filterBtn.Size = new System.Drawing.Size(88, 27); - this.filterBtn.TabIndex = 2; - this.filterBtn.Text = "Filter"; - this.filterBtn.UseVisualStyleBackColor = true; - this.filterBtn.Click += new System.EventHandler(this.filterBtn_Click); - // - // filterSearchTb - // - this.filterSearchTb.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + this.gridPanel.Location = new System.Drawing.Point(14, 65); + this.gridPanel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.gridPanel.Name = "gridPanel"; + this.gridPanel.Size = new System.Drawing.Size(979, 445); + this.gridPanel.TabIndex = 5; + // + // filterHelpBtn + // + this.filterHelpBtn.Location = new System.Drawing.Point(14, 31); + this.filterHelpBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.filterHelpBtn.Name = "filterHelpBtn"; + this.filterHelpBtn.Size = new System.Drawing.Size(26, 27); + this.filterHelpBtn.TabIndex = 3; + this.filterHelpBtn.Text = "?"; + this.filterHelpBtn.UseVisualStyleBackColor = true; + this.filterHelpBtn.Click += new System.EventHandler(this.filterHelpBtn_Click); + // + // 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(905, 31); + this.filterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.filterBtn.Name = "filterBtn"; + this.filterBtn.Size = new System.Drawing.Size(88, 27); + this.filterBtn.TabIndex = 2; + this.filterBtn.Text = "Filter"; + this.filterBtn.UseVisualStyleBackColor = true; + this.filterBtn.Click += new System.EventHandler(this.filterBtn_Click); + // + // 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(217, 33); - this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.filterSearchTb.Name = "filterSearchTb"; - this.filterSearchTb.Size = new System.Drawing.Size(681, 23); - this.filterSearchTb.TabIndex = 1; - this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); - // - // menuStrip1 - // - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.filterSearchTb.Location = new System.Drawing.Point(217, 33); + this.filterSearchTb.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.filterSearchTb.Name = "filterSearchTb"; + this.filterSearchTb.Size = new System.Drawing.Size(681, 23); + this.filterSearchTb.TabIndex = 1; + this.filterSearchTb.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.filterSearchTb_KeyPress); + // + // 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.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); - this.menuStrip1.Size = new System.Drawing.Size(1007, 24); - this.menuStrip1.TabIndex = 0; - this.menuStrip1.Text = "menuStrip1"; - // - // importToolStripMenuItem - // - this.importToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.menuStrip1.Location = new System.Drawing.Point(0, 0); + this.menuStrip1.Name = "menuStrip1"; + this.menuStrip1.Padding = new System.Windows.Forms.Padding(7, 2, 0, 2); + this.menuStrip1.Size = new System.Drawing.Size(1007, 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.removeLibraryBooksToolStripMenuItem}); - 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..."; - this.noAccountsYetAddAccountToolStripMenuItem.Click += new System.EventHandler(this.noAccountsYetAddAccountToolStripMenuItem_Click); - // - // scanLibraryToolStripMenuItem - // - this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem"; - this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22); - this.scanLibraryToolStripMenuItem.Text = "Scan &Library"; - this.scanLibraryToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryToolStripMenuItem_Click); - // - // scanLibraryOfAllAccountsToolStripMenuItem - // - this.scanLibraryOfAllAccountsToolStripMenuItem.Name = "scanLibraryOfAllAccountsToolStripMenuItem"; - this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22); - this.scanLibraryOfAllAccountsToolStripMenuItem.Text = "Scan Library of &All Accounts"; - this.scanLibraryOfAllAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfAllAccountsToolStripMenuItem_Click); - // - // scanLibraryOfSomeAccountsToolStripMenuItem - // - this.scanLibraryOfSomeAccountsToolStripMenuItem.Name = "scanLibraryOfSomeAccountsToolStripMenuItem"; - this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22); - this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts..."; - this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click); - // - // removeLibraryBooksToolStripMenuItem - // - this.removeLibraryBooksToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + 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..."; + this.noAccountsYetAddAccountToolStripMenuItem.Click += new System.EventHandler(this.noAccountsYetAddAccountToolStripMenuItem_Click); + // + // scanLibraryToolStripMenuItem + // + this.scanLibraryToolStripMenuItem.Name = "scanLibraryToolStripMenuItem"; + this.scanLibraryToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.scanLibraryToolStripMenuItem.Text = "Scan &Library"; + this.scanLibraryToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryToolStripMenuItem_Click); + // + // scanLibraryOfAllAccountsToolStripMenuItem + // + this.scanLibraryOfAllAccountsToolStripMenuItem.Name = "scanLibraryOfAllAccountsToolStripMenuItem"; + this.scanLibraryOfAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.scanLibraryOfAllAccountsToolStripMenuItem.Text = "Scan Library of &All Accounts"; + this.scanLibraryOfAllAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfAllAccountsToolStripMenuItem_Click); + // + // scanLibraryOfSomeAccountsToolStripMenuItem + // + this.scanLibraryOfSomeAccountsToolStripMenuItem.Name = "scanLibraryOfSomeAccountsToolStripMenuItem"; + this.scanLibraryOfSomeAccountsToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.scanLibraryOfSomeAccountsToolStripMenuItem.Text = "Scan Library of &Some Accounts..."; + this.scanLibraryOfSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.scanLibraryOfSomeAccountsToolStripMenuItem_Click); + // + // removeLibraryBooksToolStripMenuItem + // + this.removeLibraryBooksToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.removeAllAccountsToolStripMenuItem, this.removeSomeAccountsToolStripMenuItem}); - this.removeLibraryBooksToolStripMenuItem.Name = "removeLibraryBooksToolStripMenuItem"; - this.removeLibraryBooksToolStripMenuItem.Size = new System.Drawing.Size(247, 22); - this.removeLibraryBooksToolStripMenuItem.Text = "Remove Library Books"; - // - // removeAllAccountsToolStripMenuItem - // - this.removeAllAccountsToolStripMenuItem.Name = "removeAllAccountsToolStripMenuItem"; - 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(180, 22); - this.removeSomeAccountsToolStripMenuItem.Text = "Some Accounts"; - this.removeSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeSomeAccountsToolStripMenuItem_Click); - // - // liberateToolStripMenuItem - // - this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.removeLibraryBooksToolStripMenuItem.Name = "removeLibraryBooksToolStripMenuItem"; + this.removeLibraryBooksToolStripMenuItem.Size = new System.Drawing.Size(247, 22); + this.removeLibraryBooksToolStripMenuItem.Text = "Remove Library Books"; + // + // removeAllAccountsToolStripMenuItem + // + this.removeAllAccountsToolStripMenuItem.Name = "removeAllAccountsToolStripMenuItem"; + this.removeAllAccountsToolStripMenuItem.Size = new System.Drawing.Size(157, 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.Text = "Some Accounts"; + this.removeSomeAccountsToolStripMenuItem.Click += new System.EventHandler(this.removeSomeAccountsToolStripMenuItem_Click); + // + // liberateToolStripMenuItem + // + this.liberateToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { this.beginBookBackupsToolStripMenuItem, this.beginPdfBackupsToolStripMenuItem, this.convertAllM4bToMp3ToolStripMenuItem}); - 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(284, 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.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 = false; - this.convertAllM4bToMp3ToolStripMenuItem.Click += new System.EventHandler(this.convertAllM4bToMp3ToolStripMenuItem_Click); - // - // exportToolStripMenuItem - // - this.exportToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + 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(284, 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.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 = false; + this.convertAllM4bToMp3ToolStripMenuItem.Click += new System.EventHandler(this.convertAllM4bToMp3ToolStripMenuItem_Click); + // + // 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"; - // - // exportLibraryToolStripMenuItem - // - this.exportLibraryToolStripMenuItem.Name = "exportLibraryToolStripMenuItem"; - this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(156, 22); - this.exportLibraryToolStripMenuItem.Text = "E&xport Library..."; - this.exportLibraryToolStripMenuItem.Click += new System.EventHandler(this.exportLibraryToolStripMenuItem_Click); - // - // quickFiltersToolStripMenuItem - // - this.quickFiltersToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.exportToolStripMenuItem.Name = "exportToolStripMenuItem"; + this.exportToolStripMenuItem.Size = new System.Drawing.Size(53, 20); + this.exportToolStripMenuItem.Text = "E&xport"; + // + // exportLibraryToolStripMenuItem + // + this.exportLibraryToolStripMenuItem.Name = "exportLibraryToolStripMenuItem"; + this.exportLibraryToolStripMenuItem.Size = new System.Drawing.Size(156, 22); + this.exportLibraryToolStripMenuItem.Text = "E&xport Library..."; + this.exportLibraryToolStripMenuItem.Click += new System.EventHandler(this.exportLibraryToolStripMenuItem_Click); + // + // 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"; - this.firstFilterIsDefaultToolStripMenuItem.Click += new System.EventHandler(this.FirstFilterIsDefaultToolStripMenuItem_Click); - // - // editQuickFiltersToolStripMenuItem - // - this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem"; - this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22); - this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters..."; - this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.EditQuickFiltersToolStripMenuItem_Click); - // - // 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.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"; + this.firstFilterIsDefaultToolStripMenuItem.Click += new System.EventHandler(this.FirstFilterIsDefaultToolStripMenuItem_Click); + // + // editQuickFiltersToolStripMenuItem + // + this.editQuickFiltersToolStripMenuItem.Name = "editQuickFiltersToolStripMenuItem"; + this.editQuickFiltersToolStripMenuItem.Size = new System.Drawing.Size(256, 22); + this.editQuickFiltersToolStripMenuItem.Text = "&Edit quick filters..."; + this.editQuickFiltersToolStripMenuItem.Click += new System.EventHandler(this.EditQuickFiltersToolStripMenuItem_Click); + // + // 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.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(133, 22); - this.accountsToolStripMenuItem.Text = "&Accounts..."; - this.accountsToolStripMenuItem.Click += new System.EventHandler(this.accountsToolStripMenuItem_Click); - // - // basicSettingsToolStripMenuItem - // - this.basicSettingsToolStripMenuItem.Name = "basicSettingsToolStripMenuItem"; - this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(133, 22); - this.basicSettingsToolStripMenuItem.Text = "&Settings..."; - this.basicSettingsToolStripMenuItem.Click += new System.EventHandler(this.basicSettingsToolStripMenuItem_Click); - // - // statusStrip1 - // - this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + 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(133, 22); + this.accountsToolStripMenuItem.Text = "&Accounts..."; + this.accountsToolStripMenuItem.Click += new System.EventHandler(this.accountsToolStripMenuItem_Click); + // + // basicSettingsToolStripMenuItem + // + this.basicSettingsToolStripMenuItem.Name = "basicSettingsToolStripMenuItem"; + this.basicSettingsToolStripMenuItem.Size = new System.Drawing.Size(133, 22); + this.basicSettingsToolStripMenuItem.Text = "&Settings..."; + this.basicSettingsToolStripMenuItem.Click += new System.EventHandler(this.basicSettingsToolStripMenuItem_Click); + // + // 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, 517); - this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); - this.statusStrip1.Size = new System.Drawing.Size(1007, 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(375, 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(47, 31); - this.addFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); - this.addFilterBtn.Name = "addFilterBtn"; - this.addFilterBtn.Size = new System.Drawing.Size(163, 27); - this.addFilterBtn.TabIndex = 4; - this.addFilterBtn.Text = "Add To Quick Filters"; - this.addFilterBtn.UseVisualStyleBackColor = true; - this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click); - // - // Form1 - // - this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1007, 539); - 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.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(); - this.statusStrip1.ResumeLayout(false); - this.statusStrip1.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); + this.statusStrip1.Location = new System.Drawing.Point(0, 517); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 16, 0); + this.statusStrip1.Size = new System.Drawing.Size(1007, 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(375, 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(47, 31); + this.addFilterBtn.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3); + this.addFilterBtn.Name = "addFilterBtn"; + this.addFilterBtn.Size = new System.Drawing.Size(163, 27); + this.addFilterBtn.TabIndex = 4; + this.addFilterBtn.Text = "Add To Quick Filters"; + this.addFilterBtn.UseVisualStyleBackColor = true; + this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click); + // + // button1 + // + this.button1.Location = new System.Drawing.Point(648, 0); + this.button1.Name = "button1"; + this.button1.Size = new System.Drawing.Size(123, 70); + this.button1.TabIndex = 0; + this.button1.Text = "button1"; + this.button1.UseVisualStyleBackColor = true; + this.button1.Click += new System.EventHandler(this.button1_Click); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1007, 539); + this.Controls.Add(this.button1); + 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.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(); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); } @@ -398,5 +410,6 @@ private System.Windows.Forms.ToolStripMenuItem removeLibraryBooksToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem removeAllAccountsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem removeSomeAccountsToolStripMenuItem; - } + private System.Windows.Forms.Button button1; + } } diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index 6f9223d3..e9e0c350 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -63,6 +63,7 @@ namespace LibationWinForms // also applies filter. ONLY call AFTER loading grid loadInitialQuickFilterState(); + } private void Form1_FormClosing(object sender, FormClosingEventArgs e) @@ -494,5 +495,13 @@ namespace LibationWinForms private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); #endregion + + private void button1_Click(object sender, EventArgs e) + { + + BookLiberation.ProcessorAutomationController.DownloadFile( + "https://github.com/rmcrackan/Libation/releases/download/v5.4.9/Libation.5.4.9.zip", + @"C:\Users\mbuca\Downloads\libation test dl.zip"); + } } } diff --git a/LibationWinForms/Form1.resx b/LibationWinForms/Form1.resx index 0f6fc673..64da6d15 100644 --- a/LibationWinForms/Form1.resx +++ b/LibationWinForms/Form1.resx @@ -1,5 +1,4 @@ - - + From 687591e08ef45673a62eb08affe0aed8fa13b3c9 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 11 Aug 2021 20:22:36 -0600 Subject: [PATCH 35/64] Refined changes to BookLiberation --- FileLiberator/ConvertToMp3.cs | 4 +- FileLiberator/DownloadDecryptBook.cs | 4 +- FileLiberator/DownloadFile.cs | 2 +- FileLiberator/IAudioDecodable.cs | 2 +- LibationLauncher/LibationLauncher.csproj | 2 +- LibationLauncher/Program.cs | 3 - .../BookLiberation/AudioConvertForm.resx | 120 ++++++++++++++++++ .../AudioDecodeBaseForm.Designer.cs | 0 .../{ => BaseForms}/AudioDecodeBaseForm.cs | 14 +- .../{ => BaseForms}/AudioDecodeBaseForm.resx | 0 .../{ => BaseForms}/ProcessBaseForm.cs | 3 +- .../{ => BaseForms}/StreamBaseForm.cs | 54 ++++++-- .../BookLiberation/PdfDownloadForm.resx | 120 ++++++++++++++++++ .../ProcessorAutomationController.cs | 45 ++++--- LibationWinForms/Form1.Designer.cs | 13 -- LibationWinForms/Form1.cs | 7 - 16 files changed, 331 insertions(+), 62 deletions(-) create mode 100644 LibationWinForms/BookLiberation/AudioConvertForm.resx rename LibationWinForms/BookLiberation/{ => BaseForms}/AudioDecodeBaseForm.Designer.cs (100%) rename LibationWinForms/BookLiberation/{ => BaseForms}/AudioDecodeBaseForm.cs (91%) rename LibationWinForms/BookLiberation/{ => BaseForms}/AudioDecodeBaseForm.resx (100%) rename LibationWinForms/BookLiberation/{ => BaseForms}/ProcessBaseForm.cs (94%) rename LibationWinForms/BookLiberation/{ => BaseForms}/StreamBaseForm.cs (55%) create mode 100644 LibationWinForms/BookLiberation/PdfDownloadForm.resx diff --git a/FileLiberator/ConvertToMp3.cs b/FileLiberator/ConvertToMp3.cs index 6b514837..6c682b20 100644 --- a/FileLiberator/ConvertToMp3.cs +++ b/FileLiberator/ConvertToMp3.cs @@ -22,7 +22,7 @@ namespace FileLiberator public event EventHandler TitleDiscovered; public event EventHandler AuthorsDiscovered; public event EventHandler NarratorsDiscovered; - public event EventHandler CoverImageFilepathDiscovered; + public event EventHandler CoverImageDiscovered; public event EventHandler StreamingBegin; public event EventHandler StreamingProgressChanged; public event EventHandler StreamingCompleted; @@ -58,7 +58,7 @@ namespace FileLiberator TitleDiscovered?.Invoke(this, m4bBook.AppleTags.Title); AuthorsDiscovered?.Invoke(this, m4bBook.AppleTags.FirstAuthor); NarratorsDiscovered?.Invoke(this, m4bBook.AppleTags.Narrator); - CoverImageFilepathDiscovered?.Invoke(this, m4bBook.AppleTags.Cover); + CoverImageDiscovered?.Invoke(this, m4bBook.AppleTags.Cover); using var mp3File = File.OpenWrite(Path.GetTempFileName()); diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs index 8565a1da..d8596ae3 100644 --- a/FileLiberator/DownloadDecryptBook.cs +++ b/FileLiberator/DownloadDecryptBook.cs @@ -23,7 +23,7 @@ namespace FileLiberator public event EventHandler TitleDiscovered; public event EventHandler AuthorsDiscovered; public event EventHandler NarratorsDiscovered; - public event EventHandler CoverImageFilepathDiscovered; + public event EventHandler CoverImageDiscovered; public event EventHandler StreamingBegin; public event EventHandler StreamingProgressChanged; public event EventHandler StreamingCompleted; @@ -136,7 +136,7 @@ namespace FileLiberator if (e is not null) { - CoverImageFilepathDiscovered?.Invoke(this, e); + CoverImageDiscovered?.Invoke(this, e); } } diff --git a/FileLiberator/DownloadFile.cs b/FileLiberator/DownloadFile.cs index d4735420..dd67f8e0 100644 --- a/FileLiberator/DownloadFile.cs +++ b/FileLiberator/DownloadFile.cs @@ -34,7 +34,7 @@ namespace FileLiberator } private void OnProgressChanged(object sender, DownloadProgress e) { - StreamingProgressChanged?.Invoke(this, e); + StreamingProgressChanged.Invoke(this, e); } } } diff --git a/FileLiberator/IAudioDecodable.cs b/FileLiberator/IAudioDecodable.cs index 8eaab624..eb18bb91 100644 --- a/FileLiberator/IAudioDecodable.cs +++ b/FileLiberator/IAudioDecodable.cs @@ -10,7 +10,7 @@ namespace FileLiberator event EventHandler TitleDiscovered; event EventHandler AuthorsDiscovered; event EventHandler NarratorsDiscovered; - event EventHandler CoverImageFilepathDiscovered; + event EventHandler CoverImageDiscovered; void Cancel(); } diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index ca7b53f8..b55ca588 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.200 + 5.4.9.239 diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs index 0184b1a7..0786e414 100644 --- a/LibationLauncher/Program.cs +++ b/LibationLauncher/Program.cs @@ -63,9 +63,6 @@ namespace LibationLauncher #if !DEBUG checkForUpdate(config); #endif - LibationWinForms.BookLiberation.ProcessorAutomationController.DownloadFile( - "https://github.com/rmcrackan/Libation/releases/download/v5.4.9/Libation.5.4.9.zip", - @"C:\Users\mbuca\Downloads\libation test dl.zip"); Application.Run(new Form1()); } diff --git a/LibationWinForms/BookLiberation/AudioConvertForm.resx b/LibationWinForms/BookLiberation/AudioConvertForm.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/LibationWinForms/BookLiberation/AudioConvertForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/LibationWinForms/BookLiberation/AudioDecodeBaseForm.Designer.cs b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.Designer.cs similarity index 100% rename from LibationWinForms/BookLiberation/AudioDecodeBaseForm.Designer.cs rename to LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.Designer.cs diff --git a/LibationWinForms/BookLiberation/AudioDecodeBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs similarity index 91% rename from LibationWinForms/BookLiberation/AudioDecodeBaseForm.cs rename to LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs index 3354460a..a2691eec 100644 --- a/LibationWinForms/BookLiberation/AudioDecodeBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs @@ -18,6 +18,7 @@ namespace LibationWinForms.BookLiberation private string authorNames; private string narratorNames; + #region ProcessBaseForm overrides public override void SetProcessable(IStreamable streamProcessable, Action infoLog) { base.SetProcessable(streamProcessable, infoLog); @@ -30,21 +31,24 @@ namespace LibationWinForms.BookLiberation audioDecodable.TitleDiscovered += OnTitleDiscovered; audioDecodable.AuthorsDiscovered += OnAuthorsDiscovered; audioDecodable.NarratorsDiscovered += OnNarratorsDiscovered; - audioDecodable.CoverImageFilepathDiscovered += OnCoverImageFilepathDiscovered; + audioDecodable.CoverImageDiscovered += OnCoverImageDiscovered; Disposed += OnUnsubscribeAll; } + } + #endregion + private void OnUnsubscribeAll(object sender, EventArgs e) { Disposed -= OnUnsubscribeAll; - if (Streamable is not null && Streamable is IAudioDecodable audioDecodable) + if (Streamable is IAudioDecodable audioDecodable) { audioDecodable.RequestCoverArt -= OnRequestCoverArt; audioDecodable.TitleDiscovered -= OnTitleDiscovered; audioDecodable.AuthorsDiscovered -= OnAuthorsDiscovered; audioDecodable.NarratorsDiscovered -= OnNarratorsDiscovered; - audioDecodable.CoverImageFilepathDiscovered -= OnCoverImageFilepathDiscovered; + audioDecodable.CoverImageDiscovered -= OnCoverImageDiscovered; audioDecodable.Cancel(); } @@ -62,7 +66,7 @@ namespace LibationWinForms.BookLiberation OnTitleDiscovered(null, libraryBook.Book.Title); OnAuthorsDiscovered(null, string.Join(", ", libraryBook.Book.Authors)); OnNarratorsDiscovered(null, string.Join(", ", libraryBook.Book.NarratorNames)); - OnCoverImageFilepathDiscovered(null, + OnCoverImageDiscovered(null, FileManager.PictureStorage.GetPictureSynchronously( new FileManager.PictureDefinition( libraryBook.Book.PictureId, @@ -112,7 +116,7 @@ namespace LibationWinForms.BookLiberation updateBookInfo(); } - public virtual void OnCoverImageFilepathDiscovered(object sender, byte[] coverArt) + public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt) => pictureBox1.UIThread(() => pictureBox1.Image = Dinah.Core.Drawing.ImageReader.ToImage(coverArt)); #endregion diff --git a/LibationWinForms/BookLiberation/AudioDecodeBaseForm.resx b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.resx similarity index 100% rename from LibationWinForms/BookLiberation/AudioDecodeBaseForm.resx rename to LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.resx diff --git a/LibationWinForms/BookLiberation/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs similarity index 94% rename from LibationWinForms/BookLiberation/ProcessBaseForm.cs rename to LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index acd8b6bd..24d80acf 100644 --- a/LibationWinForms/BookLiberation/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -23,12 +23,13 @@ namespace LibationWinForms.BookLiberation processable.StatusUpdate += OnStatusUpdate; Disposed += OnUnsubscribeAll; } + } private void OnUnsubscribeAll(object sender, EventArgs e) { Disposed -= OnUnsubscribeAll; - if (Streamable is not null && Streamable is IProcessable processable) + if (Streamable is IProcessable processable) { processable.Begin -= OnBegin; processable.Completed -= OnCompleted; diff --git a/LibationWinForms/BookLiberation/StreamBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs similarity index 55% rename from LibationWinForms/BookLiberation/StreamBaseForm.cs rename to LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs index f6907131..e6cbde79 100644 --- a/LibationWinForms/BookLiberation/StreamBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs @@ -1,16 +1,25 @@ using Dinah.Core.Net.Http; +using Dinah.Core.Windows.Forms; using FileLiberator; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.ComponentModel; +using System.Threading; using System.Windows.Forms; namespace LibationWinForms.BookLiberation { public class StreamBaseForm : Form { + private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; + public new bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; + private SynchronizationContext SyncContext { get; } + + public StreamBaseForm() + { + //Will not work if set outside constructor. + SyncContext = SynchronizationContext.Current; + } + protected IStreamable Streamable { get; private set; } public void SetStreamable(IStreamable streamable) { @@ -39,14 +48,41 @@ namespace LibationWinForms.BookLiberation } #region IStreamable event handlers - public virtual void OnStreamingBegin(object sender, string beginString) => Show(); + public virtual void OnStreamingBegin(object sender, string beginString) + { + //If StreamingBegin is fired from a worker thread, the form will be created on + //that UI thread. Form.BeginInvoke won't work until the form is created (ie. shown), + //so we need to make certain that we show the form on the same thread that created + //this StreamBaseForm. + + static void sendCallback(object asyncArgs) + { + var e = asyncArgs as AsyncCompletedEventArgs; + ((Action)e.UserState)(); + } + + Action code = Show; + + if (InvokeRequired) + { + var args = new AsyncCompletedEventArgs(null, false, code); + SyncContext.Send( + sendCallback, + args); + } + else + { + code(); + } + } public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } public virtual void OnStreamingCompleted(object sender, string completedString) - { - Close(); - Dispose(); - } + => this.UIThread(() => + { + Close(); + Dispose(); + }); #endregion } } diff --git a/LibationWinForms/BookLiberation/PdfDownloadForm.resx b/LibationWinForms/BookLiberation/PdfDownloadForm.resx new file mode 100644 index 00000000..1af7de15 --- /dev/null +++ b/LibationWinForms/BookLiberation/PdfDownloadForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 8cbc11ba..d43613e1 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -1,7 +1,6 @@ using DataLayer; using Dinah.Core; using Dinah.Core.ErrorHandling; -using Dinah.Core.Windows.Forms; using FileLiberator; using System; using System.Linq; @@ -82,15 +81,15 @@ namespace LibationWinForms.BookLiberation var automatedBackupsForm = new AutomatedBackupsForm(); LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); - var convertBook = CreateStreamableProcessable(null, logMe); + var convertBook = CreateStreamProcessable(null, logMe); await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); } private static BackupBook CreateBackupBook(EventHandler completedAction, LogMe logMe) { - var downloadPdf = CreateStreamableProcessable(completedAction, logMe); - var downloadDecryptBook = CreateStreamableProcessable(completedAction, logMe); + var downloadPdf = CreateStreamProcessable(completedAction, logMe); + var downloadDecryptBook = CreateStreamProcessable(completedAction, logMe); return new BackupBook(downloadDecryptBook, downloadPdf); } @@ -101,34 +100,35 @@ namespace LibationWinForms.BookLiberation var automatedBackupsForm = new AutomatedBackupsForm(); LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); - var downloadPdf = CreateStreamableProcessable(completedAction, logMe); + var downloadPdf = CreateStreamProcessable(completedAction, logMe); await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); } public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) { - new System.Threading.Thread(() => + void OnCompleted(object o, string s) { - (DownloadFile downloadFile, DownloadForm downloadForm) = CreateStreamable(); - if (showDownloadCompletedDialog) - downloadFile.StreamingCompleted += (_, __) => MessageBox.Show("File downloaded"); + MessageBox.Show("File downloaded to:\r\n\r\n" + s); + } - downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult(); - }) + (DownloadFile downloadFile, DownloadForm downloadForm) = CreateStreamable(OnCompleted); + + new System.Threading.Thread(() =>downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult()) { IsBackground = true } .Start(); + } /// - /// Create a new and which creates a new on IProcessable.Begin. + /// Create a new and which creates a new on . /// /// The derrived type to create. - /// The derrived form to create on Begin - /// An additional event handler to handle .Completed + /// The derrived form to create on and and be Shown on + /// An additional event handler to handle /// A new of type - private static TStrProc CreateStreamableProcessable(EventHandler completedAction = null, LogMe logMe = null) + private static TStrProc CreateStreamProcessable(EventHandler completedAction = null, LogMe logMe = null) where TForm : ProcessBaseForm, new() where TStrProc : IStreamProcessable, new() { @@ -137,7 +137,8 @@ namespace LibationWinForms.BookLiberation strProc.Begin += (sender, libraryBook) => { var processForm = new TForm(); - processForm.SetProcessable(strProc, logMe.Info); + Action logAction = logMe is null ? (s) => { } : logMe.Info; + processForm.SetProcessable(strProc, logAction); processForm.OnBegin(sender, libraryBook); }; @@ -146,7 +147,14 @@ namespace LibationWinForms.BookLiberation return strProc; } - private static (TStrProc, TForm) CreateStreamable(EventHandler completedAction = null) + + /// + /// Creates a new and links it to a new + /// + /// The derrived type to create. + /// The derrived form to create, which will be Shown on . + /// An additional event handler to handle + private static (TStrProc, TForm) CreateStreamable(EventHandler completedAction = null) where TForm : StreamBaseForm, new() where TStrProc : IStreamable, new() { @@ -155,6 +163,9 @@ namespace LibationWinForms.BookLiberation var streamForm = new TForm(); streamForm.SetStreamable(strProc); + if (completedAction != null) + strProc.StreamingCompleted += completedAction; + return (strProc, streamForm); } } diff --git a/LibationWinForms/Form1.Designer.cs b/LibationWinForms/Form1.Designer.cs index ad89b4ef..bd26f759 100644 --- a/LibationWinForms/Form1.Designer.cs +++ b/LibationWinForms/Form1.Designer.cs @@ -61,7 +61,6 @@ this.backupsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.pdfsCountsLbl = new System.Windows.Forms.ToolStripStatusLabel(); this.addFilterBtn = new System.Windows.Forms.Button(); - this.button1 = new System.Windows.Forms.Button(); this.menuStrip1.SuspendLayout(); this.statusStrip1.SuspendLayout(); this.SuspendLayout(); @@ -337,22 +336,11 @@ this.addFilterBtn.UseVisualStyleBackColor = true; this.addFilterBtn.Click += new System.EventHandler(this.AddFilterBtn_Click); // - // button1 - // - this.button1.Location = new System.Drawing.Point(648, 0); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(123, 70); - this.button1.TabIndex = 0; - this.button1.Text = "button1"; - this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); - // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1007, 539); - this.Controls.Add(this.button1); this.Controls.Add(this.filterBtn); this.Controls.Add(this.addFilterBtn); this.Controls.Add(this.filterSearchTb); @@ -410,6 +398,5 @@ private System.Windows.Forms.ToolStripMenuItem removeLibraryBooksToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem removeAllAccountsToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem removeSomeAccountsToolStripMenuItem; - private System.Windows.Forms.Button button1; } } diff --git a/LibationWinForms/Form1.cs b/LibationWinForms/Form1.cs index e9e0c350..7caaf420 100644 --- a/LibationWinForms/Form1.cs +++ b/LibationWinForms/Form1.cs @@ -496,12 +496,5 @@ namespace LibationWinForms private void basicSettingsToolStripMenuItem_Click(object sender, EventArgs e) => new SettingsDialog().ShowDialog(); #endregion - private void button1_Click(object sender, EventArgs e) - { - - BookLiberation.ProcessorAutomationController.DownloadFile( - "https://github.com/rmcrackan/Libation/releases/download/v5.4.9/Libation.5.4.9.zip", - @"C:\Users\mbuca\Downloads\libation test dl.zip"); - } } } From 1c239dc54661152fcfae1f3e874c5c1bfca5b4e8 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 11 Aug 2021 21:07:07 -0600 Subject: [PATCH 36/64] More process control refinements --- FileLiberator/BackupBook.cs | 62 ------------------- LibationLauncher/LibationLauncher.csproj | 2 +- .../BookLiberation/AudioConvertForm.cs | 8 +-- .../BookLiberation/AudioDecryptForm.cs | 4 +- .../BaseForms/AudioDecodeBaseForm.cs | 5 +- .../BaseForms/ProcessBaseForm.cs | 12 ++-- .../BookLiberation/PdfDownloadForm.cs | 10 +-- .../ProcessorAutomationController.cs | 21 +++++-- 8 files changed, 30 insertions(+), 94 deletions(-) delete mode 100644 FileLiberator/BackupBook.cs diff --git a/FileLiberator/BackupBook.cs b/FileLiberator/BackupBook.cs deleted file mode 100644 index c7050b4c..00000000 --- a/FileLiberator/BackupBook.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System; -using System.Threading.Tasks; -using DataLayer; -using Dinah.Core.ErrorHandling; - -namespace FileLiberator -{ - /// - /// Download DRM book and decrypt audiobook files - /// - /// Processes: - /// Download: download aax file: the DRM encrypted audiobook - /// Decrypt: remove DRM encryption from audiobook. Store final book - /// Backup: perform all steps (downloaded, decrypt) still needed to get final book - /// - public class BackupBook : IProcessable - { - public event EventHandler Begin; - public event EventHandler StatusUpdate; - public event EventHandler Completed; - - public DownloadDecryptBook DownloadDecryptBook { get; } - public DownloadPdf DownloadPdf { get; } - - public BackupBook(DownloadDecryptBook downloadDecryptBook, DownloadPdf downloadPdf) - { - DownloadDecryptBook = downloadDecryptBook; - DownloadPdf = downloadPdf; - } - - public bool Validate(LibraryBook libraryBook) - => !ApplicationServices.TransitionalFileLocator.Audio_Exists(libraryBook.Book); - - // do NOT use ConfigureAwait(false) on ProcessAsync() - // often calls events which prints to forms in the UI context - public async Task ProcessAsync(LibraryBook libraryBook) - { - Begin?.Invoke(this, libraryBook); - - try - { - { - var statusHandler = await DownloadDecryptBook.TryProcessAsync(libraryBook); - if (statusHandler.HasErrors) - return statusHandler; - } - - { - var statusHandler = await DownloadPdf.TryProcessAsync(libraryBook); - if (statusHandler.HasErrors) - return statusHandler; - } - - return new StatusHandler(); - } - finally - { - Completed?.Invoke(this, libraryBook); - } - } - } -} diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index b55ca588..577fdaa2 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.239 + 5.4.9.252 diff --git a/LibationWinForms/BookLiberation/AudioConvertForm.cs b/LibationWinForms/BookLiberation/AudioConvertForm.cs index 0dde6860..82a586f1 100644 --- a/LibationWinForms/BookLiberation/AudioConvertForm.cs +++ b/LibationWinForms/BookLiberation/AudioConvertForm.cs @@ -1,9 +1,5 @@ using DataLayer; using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace LibationWinForms.BookLiberation { @@ -16,12 +12,12 @@ namespace LibationWinForms.BookLiberation #region IProcessable event handler overrides public override void OnBegin(object sender, LibraryBook libraryBook) { - InfoLogAction($"Convert Step, Begin: {libraryBook.Book}"); + LogMe.Info($"Convert Step, Begin: {libraryBook.Book}"); base.OnBegin(sender, libraryBook); } public override void OnCompleted(object sender, LibraryBook libraryBook) - => InfoLogAction($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + => LogMe.Info($"Convert Step, Completed: {libraryBook.Book}{Environment.NewLine}"); #endregion } diff --git a/LibationWinForms/BookLiberation/AudioDecryptForm.cs b/LibationWinForms/BookLiberation/AudioDecryptForm.cs index 45b6b5c8..a27b936d 100644 --- a/LibationWinForms/BookLiberation/AudioDecryptForm.cs +++ b/LibationWinForms/BookLiberation/AudioDecryptForm.cs @@ -12,12 +12,12 @@ namespace LibationWinForms.BookLiberation #region IProcessable event handler overrides public override void OnBegin(object sender, LibraryBook libraryBook) { - InfoLogAction($"Download & Decrypt Step, Begin: {libraryBook.Book}"); + LogMe.Info($"Download & Decrypt Step, Begin: {libraryBook.Book}"); base.OnBegin(sender, libraryBook); } public override void OnCompleted(object sender, LibraryBook libraryBook) - => InfoLogAction($"Download & Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); + => LogMe.Info($"Download & Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}"); #endregion } diff --git a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs index a2691eec..8225c16f 100644 --- a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs @@ -19,9 +19,9 @@ namespace LibationWinForms.BookLiberation private string narratorNames; #region ProcessBaseForm overrides - public override void SetProcessable(IStreamable streamProcessable, Action infoLog) + public override void SetProcessable(IStreamable streamProcessable, LogMe logMe) { - base.SetProcessable(streamProcessable, infoLog); + base.SetProcessable(streamProcessable, logMe); if (Streamable is not null && Streamable is IAudioDecodable audioDecodable) { @@ -35,7 +35,6 @@ namespace LibationWinForms.BookLiberation Disposed += OnUnsubscribeAll; } - } #endregion diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index 24d80acf..f8f89613 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -8,10 +8,10 @@ namespace LibationWinForms.BookLiberation { public class ProcessBaseForm : StreamBaseForm { - protected Action InfoLogAction { get; private set; } - public virtual void SetProcessable(IStreamable streamable, Action infoLog) + protected LogMe LogMe { get; private set; } + public virtual void SetProcessable(IStreamable streamable, LogMe logMe) { - InfoLogAction = infoLog; + LogMe = logMe; SetStreamable(streamable); if (Streamable is not null && Streamable is IProcessable processable) @@ -38,9 +38,9 @@ namespace LibationWinForms.BookLiberation } #region IProcessable event handlers - public virtual void OnBegin(object sender, LibraryBook libraryBook) => InfoLogAction($"Begin: {libraryBook.Book}"); - public virtual void OnStatusUpdate(object sender, string statusUpdate) => InfoLogAction("- " + statusUpdate); - public virtual void OnCompleted(object sender, LibraryBook libraryBook) => InfoLogAction($"Completed: {libraryBook.Book}{Environment.NewLine}"); + public virtual void OnBegin(object sender, LibraryBook libraryBook) => LogMe.Info($"Begin: {libraryBook.Book}"); + public virtual void OnStatusUpdate(object sender, string statusUpdate) => LogMe.Info("- " + statusUpdate); + public virtual void OnCompleted(object sender, LibraryBook libraryBook) => LogMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); #endregion } } diff --git a/LibationWinForms/BookLiberation/PdfDownloadForm.cs b/LibationWinForms/BookLiberation/PdfDownloadForm.cs index 870871e9..3def88c2 100644 --- a/LibationWinForms/BookLiberation/PdfDownloadForm.cs +++ b/LibationWinForms/BookLiberation/PdfDownloadForm.cs @@ -1,16 +1,10 @@ using DataLayer; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace LibationWinForms.BookLiberation { internal class PdfDownloadForm : DownloadForm { - public override void OnBegin(object sender, LibraryBook libraryBook) => InfoLogAction($"PDF Step, Begin: {libraryBook.Book}"); - public override void OnCompleted(object sender, LibraryBook libraryBook) => InfoLogAction($"PDF Step, Completed: {libraryBook.Book}"); - + public override void OnBegin(object sender, LibraryBook libraryBook) => LogMe.Info($"PDF Step, Begin: {libraryBook.Book}"); + public override void OnCompleted(object sender, LibraryBook libraryBook) => LogMe.Info($"PDF Step, Completed: {libraryBook.Book}"); } } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index d43613e1..e397378f 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -86,11 +86,20 @@ namespace LibationWinForms.BookLiberation await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); } - private static BackupBook CreateBackupBook(EventHandler completedAction, LogMe logMe) + private static IProcessable CreateBackupBook(EventHandler completedAction, LogMe logMe) { - var downloadPdf = CreateStreamProcessable(completedAction, logMe); - var downloadDecryptBook = CreateStreamProcessable(completedAction, logMe); - return new BackupBook(downloadDecryptBook, downloadPdf); + var downloadPdf = CreateStreamProcessable(null, logMe); + + //Chain pdf download on DownloadDecryptBook.Completed + async void onDownloadDecryptBookCompleted(object sender, LibraryBook e) + { + await downloadPdf.TryProcessAsync(e); + + completedAction(sender, e); + } + + var downloadDecryptBook = CreateStreamProcessable(onDownloadDecryptBookCompleted, logMe); + return downloadDecryptBook; } public static async Task BackupAllPdfsAsync(EventHandler completedAction = null) @@ -122,7 +131,7 @@ namespace LibationWinForms.BookLiberation } /// - /// Create a new and which creates a new on . + /// Create a new and links it to a new . /// /// The derrived type to create. /// The derrived form to create on and and be Shown on @@ -138,7 +147,7 @@ namespace LibationWinForms.BookLiberation { var processForm = new TForm(); Action logAction = logMe is null ? (s) => { } : logMe.Info; - processForm.SetProcessable(strProc, logAction); + processForm.SetProcessable(strProc, logMe); processForm.OnBegin(sender, libraryBook); }; From 79ed92f303183628d72d17f538acb3c70c18735a Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 11 Aug 2021 21:19:38 -0600 Subject: [PATCH 37/64] Refinment --- LibationLauncher/LibationLauncher.csproj | 2 +- LibationLauncher/Program.cs | 1 - .../ProcessorAutomationController.cs | 71 +++++++++---------- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 577fdaa2..9d263c16 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.252 + 5.4.9.253 diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs index 0786e414..1cfbda22 100644 --- a/LibationLauncher/Program.cs +++ b/LibationLauncher/Program.cs @@ -63,7 +63,6 @@ namespace LibationLauncher #if !DEBUG checkForUpdate(config); #endif - Application.Run(new Form1()); } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index e397378f..3704289b 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -71,7 +71,6 @@ namespace LibationWinForms.BookLiberation var backupBook = CreateBackupBook(completedAction, logMe); await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync(); - } public static async Task ConvertAllBooksAsync() @@ -86,6 +85,33 @@ namespace LibationWinForms.BookLiberation await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); } + public static async Task BackupAllPdfsAsync(EventHandler completedAction = null) + { + Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); + + var automatedBackupsForm = new AutomatedBackupsForm(); + LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); + + var downloadPdf = CreateStreamProcessable(completedAction, logMe); + + await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); + } + + public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) + { + void onDownloadFileStreamingCompleted(object o, string s) + { + if (showDownloadCompletedDialog) + MessageBox.Show("File downloaded to:\r\n\r\n" + s); + } + + DownloadFile downloadFile = CreateStreamable(onDownloadFileStreamingCompleted); + + new System.Threading.Thread(() => downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult()) + { IsBackground = true } + .Start(); + } + private static IProcessable CreateBackupBook(EventHandler completedAction, LogMe logMe) { var downloadPdf = CreateStreamProcessable(null, logMe); @@ -102,39 +128,11 @@ namespace LibationWinForms.BookLiberation return downloadDecryptBook; } - public static async Task BackupAllPdfsAsync(EventHandler completedAction = null) - { - Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); - - var automatedBackupsForm = new AutomatedBackupsForm(); - LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); - - var downloadPdf = CreateStreamProcessable(completedAction, logMe); - - await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); - } - - public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) - { - void OnCompleted(object o, string s) - { - if (showDownloadCompletedDialog) - MessageBox.Show("File downloaded to:\r\n\r\n" + s); - } - - (DownloadFile downloadFile, DownloadForm downloadForm) = CreateStreamable(OnCompleted); - - new System.Threading.Thread(() =>downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult()) - { IsBackground = true } - .Start(); - - } - /// /// Create a new and links it to a new . /// /// The derrived type to create. - /// The derrived form to create on and and be Shown on + /// The derrived Form to create on and and be Shown on /// An additional event handler to handle /// A new of type private static TStrProc CreateStreamProcessable(EventHandler completedAction = null, LogMe logMe = null) @@ -146,7 +144,6 @@ namespace LibationWinForms.BookLiberation strProc.Begin += (sender, libraryBook) => { var processForm = new TForm(); - Action logAction = logMe is null ? (s) => { } : logMe.Info; processForm.SetProcessable(strProc, logMe); processForm.OnBegin(sender, libraryBook); }; @@ -161,21 +158,21 @@ namespace LibationWinForms.BookLiberation /// Creates a new and links it to a new /// /// The derrived type to create. - /// The derrived form to create, which will be Shown on . + /// The derrived Form to create, which will be Shown on . /// An additional event handler to handle - private static (TStrProc, TForm) CreateStreamable(EventHandler completedAction = null) + private static TStrProc CreateStreamable(EventHandler completedAction = null) where TForm : StreamBaseForm, new() where TStrProc : IStreamable, new() { - var strProc = new TStrProc(); + var streamable = new TStrProc(); var streamForm = new TForm(); - streamForm.SetStreamable(strProc); + streamForm.SetStreamable(streamable); if (completedAction != null) - strProc.StreamingCompleted += completedAction; + streamable.StreamingCompleted += completedAction; - return (strProc, streamForm); + return streamable; } } From b5d941d47994b5791621742b6fb6a4e9781da4af Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 11 Aug 2021 23:22:45 -0600 Subject: [PATCH 38/64] Refactoring and documenting. --- FileManager/PictureStorage.cs | 64 +++++++------------ LibationLauncher/LibationLauncher.csproj | 2 +- .../BaseForms/ProcessBaseForm.cs | 3 +- .../BaseForms/StreamBaseForm.cs | 18 ++---- .../ProcessorAutomationController.cs | 26 ++++---- 5 files changed, 47 insertions(+), 66 deletions(-) diff --git a/FileManager/PictureStorage.cs b/FileManager/PictureStorage.cs index 4fdd894b..f9f5a35d 100644 --- a/FileManager/PictureStorage.cs +++ b/FileManager/PictureStorage.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; +using System.Threading.Tasks; namespace FileManager { @@ -32,31 +34,34 @@ namespace FileManager private static string getPath(PictureDefinition def) => Path.Combine(ImagesDirectory, $"{def.PictureId}{def.Size}.jpg"); - private static System.Timers.Timer timer { get; } + static Task backgroundDownloader; + static PictureStorage() { - timer = new System.Timers.Timer(700) - { - AutoReset = true, - Enabled = true - }; - timer.Elapsed += (_, __) => timerDownload(); + backgroundDownloader = new Task(BackgroundDownloader); + backgroundDownloader.Start(); } public static event EventHandler PictureCached; + private static BlockingCollection DownloadQueue { get; } = new BlockingCollection(); private static Dictionary cache { get; } = new Dictionary(); + private static Dictionary defaultImages { get; } = new Dictionary(); public static (bool isDefault, byte[] bytes) GetPicture(PictureDefinition def) { - if (!cache.ContainsKey(def)) + if (cache.ContainsKey(def)) + return (false, cache[def]); + + var path = getPath(def); + + if (File.Exists(path)) { - var path = getPath(def); - cache[def] - = File.Exists(path) - ? File.ReadAllBytes(path) - : null; + cache[def] = File.ReadAllBytes(path); + return (false, cache[def]); } - return (cache[def] == null, cache[def] ?? getDefaultImage(def.Size)); + + DownloadQueue.Add(def); + return (true, getDefaultImage(def.Size)); } public static byte[] GetPictureSynchronously(PictureDefinition def) @@ -79,7 +84,6 @@ namespace FileManager return cache[def]; } - private static Dictionary defaultImages { get; } = new Dictionary(); public static void SetDefaultImage(PictureSize pictureSize, byte[] bytes) => defaultImages[pictureSize] = bytes; private static byte[] getDefaultImage(PictureSize size) @@ -87,28 +91,12 @@ namespace FileManager ? defaultImages[size] : new byte[0]; - // necessary to avoid IO errors. ReadAllBytes and WriteAllBytes can conflict in some cases, esp when debugging - private static bool isProcessing; - private static void timerDownload() + static void BackgroundDownloader() { - // must live outside try-catch, else 'finally' can reset another thread's lock - if (isProcessing) - return; - - try + while (!DownloadQueue.IsCompleted) { - isProcessing = true; - - var def = cache - .Where(kvp => kvp.Value is null) - .Select(kvp => kvp.Key) - // 80x80 should be 1st since it's enum value == 0 - .OrderBy(d => d.PictureId) - .FirstOrDefault(); - - // no more null entries. all requsted images are cached - if (string.IsNullOrWhiteSpace(def.PictureId)) - return; + if (!DownloadQueue.TryTake(out var def, System.Threading.Timeout.InfiniteTimeSpan)) + continue; var bytes = downloadBytes(def); saveFile(def, bytes); @@ -116,10 +104,6 @@ namespace FileManager PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs { Definition = def, Picture = bytes }); } - finally - { - isProcessing = false; - } } private static HttpClient imageDownloadClient { get; } = new HttpClient(); @@ -135,4 +119,4 @@ namespace FileManager File.WriteAllBytes(path, bytes); } } -} +} \ No newline at end of file diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 9d263c16..390110a8 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.253 + 5.4.9.258 diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index f8f89613..f97825c6 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -1,8 +1,6 @@ using DataLayer; -using Dinah.Core.Net.Http; using FileLiberator; using System; -using System.Windows.Forms; namespace LibationWinForms.BookLiberation { @@ -21,6 +19,7 @@ namespace LibationWinForms.BookLiberation processable.Begin += OnBegin; processable.Completed += OnCompleted; processable.StatusUpdate += OnStatusUpdate; + Disposed += OnUnsubscribeAll; } diff --git a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs index e6cbde79..13337e13 100644 --- a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs @@ -16,7 +16,7 @@ namespace LibationWinForms.BookLiberation public StreamBaseForm() { - //Will not work if set outside constructor. + //Will be null if set outside constructor. SyncContext = SynchronizationContext.Current; } @@ -50,10 +50,11 @@ namespace LibationWinForms.BookLiberation #region IStreamable event handlers public virtual void OnStreamingBegin(object sender, string beginString) { - //If StreamingBegin is fired from a worker thread, the form will be created on - //that UI thread. Form.BeginInvoke won't work until the form is created (ie. shown), - //so we need to make certain that we show the form on the same thread that created - //this StreamBaseForm. + //If StreamingBegin is fired from a worker thread, the window will be created on + //that UI thread. We need to make certain that we show the window on the same + //thread that created form, otherwise the form and the window handle will be on + //different threads. Form.BeginInvoke won't work until the form is created + //(ie. shown) because control doesn't get a window handle until it is Shown. static void sendCallback(object asyncArgs) { @@ -64,16 +65,11 @@ namespace LibationWinForms.BookLiberation Action code = Show; if (InvokeRequired) - { - var args = new AsyncCompletedEventArgs(null, false, code); SyncContext.Send( sendCallback, - args); - } + new AsyncCompletedEventArgs(null, false, code)); else - { code(); - } } public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 3704289b..1575be89 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -54,7 +54,7 @@ namespace LibationWinForms.BookLiberation { Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId }); - LogMe logMe = LogMe.RegisterForm(); + var logMe = LogMe.RegisterForm(); var backupBook = CreateBackupBook(completedAction, logMe); // continue even if libraryBook is null. we'll display even that in the processing box @@ -66,8 +66,7 @@ namespace LibationWinForms.BookLiberation Serilog.Log.Logger.Information("Begin " + nameof(BackupAllBooksAsync)); var automatedBackupsForm = new AutomatedBackupsForm(); - LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); - + var logMe = LogMe.RegisterForm(automatedBackupsForm); var backupBook = CreateBackupBook(completedAction, logMe); await new BackupLoop(logMe, backupBook, automatedBackupsForm).RunBackupAsync(); @@ -78,7 +77,7 @@ namespace LibationWinForms.BookLiberation Serilog.Log.Logger.Information("Begin " + nameof(ConvertAllBooksAsync)); var automatedBackupsForm = new AutomatedBackupsForm(); - LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); + var logMe = LogMe.RegisterForm(automatedBackupsForm); var convertBook = CreateStreamProcessable(null, logMe); @@ -90,7 +89,7 @@ namespace LibationWinForms.BookLiberation Serilog.Log.Logger.Information("Begin " + nameof(BackupAllPdfsAsync)); var automatedBackupsForm = new AutomatedBackupsForm(); - LogMe logMe = LogMe.RegisterForm(automatedBackupsForm); + var logMe = LogMe.RegisterForm(automatedBackupsForm); var downloadPdf = CreateStreamProcessable(completedAction, logMe); @@ -99,13 +98,15 @@ namespace LibationWinForms.BookLiberation public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) { + Serilog.Log.Logger.Information($"Begin {nameof(DownloadFile)} for {url}"); + void onDownloadFileStreamingCompleted(object o, string s) { if (showDownloadCompletedDialog) MessageBox.Show("File downloaded to:\r\n\r\n" + s); } - DownloadFile downloadFile = CreateStreamable(onDownloadFileStreamingCompleted); + var downloadFile = CreateStreamable(onDownloadFileStreamingCompleted); new System.Threading.Thread(() => downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult()) { IsBackground = true } @@ -132,7 +133,7 @@ namespace LibationWinForms.BookLiberation /// Create a new and links it to a new . /// /// The derrived type to create. - /// The derrived Form to create on and and be Shown on + /// The derrived Form to create on , Show on , and Close & Dispose on /// An additional event handler to handle /// A new of type private static TStrProc CreateStreamProcessable(EventHandler completedAction = null, LogMe logMe = null) @@ -157,14 +158,15 @@ namespace LibationWinForms.BookLiberation /// /// Creates a new and links it to a new /// - /// The derrived type to create. - /// The derrived Form to create, which will be Shown on . + /// The derrived type to create. + /// The derrived Form to create, which will Show on and Close & Dispose on . /// An additional event handler to handle - private static TStrProc CreateStreamable(EventHandler completedAction = null) + /// A new of type + private static TStr CreateStreamable(EventHandler completedAction = null) where TForm : StreamBaseForm, new() - where TStrProc : IStreamable, new() + where TStr : IStreamable, new() { - var streamable = new TStrProc(); + var streamable = new TStr(); var streamForm = new TForm(); streamForm.SetStreamable(streamable); From 9f49a88000ccdf805fc8e57e7b9c9de9cf58815f Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 11 Aug 2021 23:28:11 -0600 Subject: [PATCH 39/64] Removed comments --- .../BookLiberation/AudioConvertForm.resx | 59 ------------------ .../BookLiberation/AudioDecryptForm.resx | 61 +++++++++++++++++++ .../BaseForms/ProcessBaseForm.resx | 61 +++++++++++++++++++ .../BaseForms/StreamBaseForm.resx | 61 +++++++++++++++++++ .../BookLiberation/PdfDownloadForm.resx | 61 +------------------ 5 files changed, 184 insertions(+), 119 deletions(-) create mode 100644 LibationWinForms/BookLiberation/AudioDecryptForm.resx create mode 100644 LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.resx create mode 100644 LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.resx diff --git a/LibationWinForms/BookLiberation/AudioConvertForm.resx b/LibationWinForms/BookLiberation/AudioConvertForm.resx index 1af7de15..e8ae276d 100644 --- a/LibationWinForms/BookLiberation/AudioConvertForm.resx +++ b/LibationWinForms/BookLiberation/AudioConvertForm.resx @@ -1,64 +1,5 @@  - diff --git a/LibationWinForms/BookLiberation/AudioDecryptForm.resx b/LibationWinForms/BookLiberation/AudioDecryptForm.resx new file mode 100644 index 00000000..e8ae276d --- /dev/null +++ b/LibationWinForms/BookLiberation/AudioDecryptForm.resx @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.resx b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.resx new file mode 100644 index 00000000..e8ae276d --- /dev/null +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.resx @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.resx b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.resx new file mode 100644 index 00000000..e8ae276d --- /dev/null +++ b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.resx @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/LibationWinForms/BookLiberation/PdfDownloadForm.resx b/LibationWinForms/BookLiberation/PdfDownloadForm.resx index 1af7de15..03e6393f 100644 --- a/LibationWinForms/BookLiberation/PdfDownloadForm.resx +++ b/LibationWinForms/BookLiberation/PdfDownloadForm.resx @@ -1,64 +1,5 @@  - - + From 265ad3a7826fa64549c14892a5887c3126a1eb53 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Wed, 11 Aug 2021 23:44:04 -0600 Subject: [PATCH 40/64] Minor refactoring. --- .../BookLiberation/BaseForms/AudioDecodeBaseForm.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs index 8225c16f..6ba3b4ee 100644 --- a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs @@ -66,10 +66,10 @@ namespace LibationWinForms.BookLiberation OnAuthorsDiscovered(null, string.Join(", ", libraryBook.Book.Authors)); OnNarratorsDiscovered(null, string.Join(", ", libraryBook.Book.NarratorNames)); OnCoverImageDiscovered(null, - FileManager.PictureStorage.GetPictureSynchronously( + FileManager.PictureStorage.GetPicture( new FileManager.PictureDefinition( libraryBook.Book.PictureId, - FileManager.PictureSize._80x80))); + FileManager.PictureSize._80x80)).bytes); } #endregion From cd67e7136b7e161b61e5e168fac273792fdababc Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 00:13:33 -0600 Subject: [PATCH 41/64] Fixed control logic. --- LibationLauncher/LibationLauncher.csproj | 2 +- .../BaseForms/ProcessBaseForm.cs | 23 +++++++++++++++---- .../ProcessorAutomationController.cs | 4 ++-- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 390110a8..7f4ef331 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.258 + 5.4.9.264 diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index f97825c6..e078c03a 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -14,22 +14,25 @@ namespace LibationWinForms.BookLiberation if (Streamable is not null && Streamable is IProcessable processable) { - OnUnsubscribeAll(this, EventArgs.Empty); + OnUnsubscribeAll(this, null); processable.Begin += OnBegin; processable.Completed += OnCompleted; processable.StatusUpdate += OnStatusUpdate; - Disposed += OnUnsubscribeAll; + //Don't unsubscribe from Dispose because it fires on + //IStreamable.StreamingCompleted, and the IProcessable + //events need to live past that. + processable.Completed += OnUnsubscribeAll; } } - private void OnUnsubscribeAll(object sender, EventArgs e) + private void OnUnsubscribeAll(object sender, LibraryBook e) { - Disposed -= OnUnsubscribeAll; if (Streamable is IProcessable processable) { + processable.Completed -= OnUnsubscribeAll; processable.Begin -= OnBegin; processable.Completed -= OnCompleted; processable.StatusUpdate -= OnStatusUpdate; @@ -39,7 +42,17 @@ namespace LibationWinForms.BookLiberation #region IProcessable event handlers public virtual void OnBegin(object sender, LibraryBook libraryBook) => LogMe.Info($"Begin: {libraryBook.Book}"); public virtual void OnStatusUpdate(object sender, string statusUpdate) => LogMe.Info("- " + statusUpdate); - public virtual void OnCompleted(object sender, LibraryBook libraryBook) => LogMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); + public virtual void OnCompleted(object sender, LibraryBook libraryBook) + { + LogMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); + + if (Streamable is IProcessable processable) + { + processable.Begin -= OnBegin; + processable.Completed -= OnCompleted; + processable.StatusUpdate -= OnStatusUpdate; + } + } #endregion } } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 1575be89..c095a92a 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -149,8 +149,8 @@ namespace LibationWinForms.BookLiberation processForm.OnBegin(sender, libraryBook); }; - if (completedAction != null) - strProc.Completed += completedAction; + //if (completedAction != null) + // strProc.Completed += completedAction; return strProc; } From a45ab61929e7ea3c1ed909fed2917b31ee8c6353 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 00:14:59 -0600 Subject: [PATCH 42/64] Typos. --- .../BookLiberation/BaseForms/ProcessBaseForm.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index e078c03a..2b59a31d 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -25,7 +25,6 @@ namespace LibationWinForms.BookLiberation //events need to live past that. processable.Completed += OnUnsubscribeAll; } - } private void OnUnsubscribeAll(object sender, LibraryBook e) @@ -42,17 +41,8 @@ namespace LibationWinForms.BookLiberation #region IProcessable event handlers public virtual void OnBegin(object sender, LibraryBook libraryBook) => LogMe.Info($"Begin: {libraryBook.Book}"); public virtual void OnStatusUpdate(object sender, string statusUpdate) => LogMe.Info("- " + statusUpdate); - public virtual void OnCompleted(object sender, LibraryBook libraryBook) - { - LogMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); + public virtual void OnCompleted(object sender, LibraryBook libraryBook) => LogMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); - if (Streamable is IProcessable processable) - { - processable.Begin -= OnBegin; - processable.Completed -= OnCompleted; - processable.StatusUpdate -= OnStatusUpdate; - } - } #endregion } } From f57a46c77282ca9941e0f5ead9d5b94fdd983275 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 00:19:30 -0600 Subject: [PATCH 43/64] fixed mistake. --- .../BookLiberation/ProcessorAutomationController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index c095a92a..1575be89 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -149,8 +149,8 @@ namespace LibationWinForms.BookLiberation processForm.OnBegin(sender, libraryBook); }; - //if (completedAction != null) - // strProc.Completed += completedAction; + if (completedAction != null) + strProc.Completed += completedAction; return strProc; } From 65027fd0017680c81ebc19d8c271f57e0acfb082 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 09:21:58 -0600 Subject: [PATCH 44/64] Renamed data grid columns. --- LibationWinForms/ProductsGrid.Designer.cs | 50 +++++++++++------------ 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/LibationWinForms/ProductsGrid.Designer.cs b/LibationWinForms/ProductsGrid.Designer.cs index f1338213..529a7783 100644 --- a/LibationWinForms/ProductsGrid.Designer.cs +++ b/LibationWinForms/ProductsGrid.Designer.cs @@ -41,9 +41,9 @@ this.dataGridViewTextBoxColumn5 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.dataGridViewTextBoxColumn6 = new System.Windows.Forms.DataGridViewTextBoxColumn(); this.dataGridViewTextBoxColumn7 = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.ProductRating = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.PurchaseDate = new System.Windows.Forms.DataGridViewTextBoxColumn(); - this.MyRating = 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(); this.dataGridViewImageButtonBoxColumn2 = new LibationWinForms.EditTagsDataGridViewImageButtonColumn(); ((System.ComponentModel.ISupportInitialize)(this.gridEntryBindingSource)).BeginInit(); @@ -71,9 +71,9 @@ this.dataGridViewTextBoxColumn5, this.dataGridViewTextBoxColumn6, this.dataGridViewTextBoxColumn7, - this.ProductRating, - this.PurchaseDate, - this.MyRating, + this.dataGridViewTextBoxColumn8, + this.dataGridViewTextBoxColumn9, + this.dataGridViewTextBoxColumn10, this.dataGridViewTextBoxColumn11, this.dataGridViewImageButtonBoxColumn2}); this.gridEntryDataGridView.DataSource = this.gridEntryBindingSource; @@ -121,7 +121,6 @@ this.dataGridViewTextBoxColumn1.HeaderText = "Title"; this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1"; this.dataGridViewTextBoxColumn1.ReadOnly = true; - this.dataGridViewTextBoxColumn1.ToolTipText = "Book Title"; this.dataGridViewTextBoxColumn1.Width = 200; // // dataGridViewTextBoxColumn2 @@ -130,7 +129,6 @@ this.dataGridViewTextBoxColumn2.HeaderText = "Authors"; this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2"; this.dataGridViewTextBoxColumn2.ReadOnly = true; - this.dataGridViewTextBoxColumn2.ToolTipText = "Book Author(s)"; // // dataGridViewTextBoxColumn3 // @@ -138,7 +136,6 @@ this.dataGridViewTextBoxColumn3.HeaderText = "Narrators"; this.dataGridViewTextBoxColumn3.Name = "dataGridViewTextBoxColumn3"; this.dataGridViewTextBoxColumn3.ReadOnly = true; - this.dataGridViewTextBoxColumn3.ToolTipText = "Audiobook Narrator(s)"; // // dataGridViewTextBoxColumn4 // @@ -154,7 +151,6 @@ this.dataGridViewTextBoxColumn5.HeaderText = "Series"; this.dataGridViewTextBoxColumn5.Name = "dataGridViewTextBoxColumn5"; this.dataGridViewTextBoxColumn5.ReadOnly = true; - this.dataGridViewTextBoxColumn5.ToolTipText = "Book Series"; // // dataGridViewTextBoxColumn6 // @@ -172,26 +168,26 @@ // // ProductRating // - this.ProductRating.DataPropertyName = "ProductRating"; - this.ProductRating.HeaderText = "Product Rating"; - this.ProductRating.Name = "ProductRating"; - this.ProductRating.ReadOnly = true; - this.ProductRating.Width = 108; + this.dataGridViewTextBoxColumn8.DataPropertyName = "ProductRating"; + this.dataGridViewTextBoxColumn8.HeaderText = "Product Rating"; + this.dataGridViewTextBoxColumn8.Name = "ProductRating"; + this.dataGridViewTextBoxColumn8.ReadOnly = true; + this.dataGridViewTextBoxColumn8.Width = 108; // // PurchaseDate // - this.PurchaseDate.DataPropertyName = "PurchaseDate"; - this.PurchaseDate.HeaderText = "Purchase Date"; - this.PurchaseDate.Name = "PurchaseDate"; - this.PurchaseDate.ReadOnly = true; + this.dataGridViewTextBoxColumn9.DataPropertyName = "PurchaseDate"; + this.dataGridViewTextBoxColumn9.HeaderText = "Purchase Date"; + this.dataGridViewTextBoxColumn9.Name = "PurchaseDate"; + this.dataGridViewTextBoxColumn9.ReadOnly = true; // // MyRating // - this.MyRating.DataPropertyName = "MyRating"; - this.MyRating.HeaderText = "My Rating"; - this.MyRating.Name = "MyRating"; - this.MyRating.ReadOnly = true; - this.MyRating.Width = 108; + this.dataGridViewTextBoxColumn10.DataPropertyName = "MyRating"; + this.dataGridViewTextBoxColumn10.HeaderText = "My Rating"; + this.dataGridViewTextBoxColumn10.Name = "MyRating"; + this.dataGridViewTextBoxColumn10.ReadOnly = true; + this.dataGridViewTextBoxColumn10.Width = 108; // // dataGridViewTextBoxColumn11 // @@ -237,9 +233,9 @@ private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn5; private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn6; private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn7; - private System.Windows.Forms.DataGridViewTextBoxColumn ProductRating; - private System.Windows.Forms.DataGridViewTextBoxColumn PurchaseDate; - private System.Windows.Forms.DataGridViewTextBoxColumn MyRating; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn8; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn9; + private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn10; private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn11; private EditTagsDataGridViewImageButtonColumn dataGridViewImageButtonBoxColumn2; } From 56a48c04bf6d74977b1eae7a872039412137515e Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 10:39:55 -0600 Subject: [PATCH 45/64] Minor refactoring. --- FileLiberator/DownloadFile.cs | 6 +--- FileLiberator/IProcessableExt.cs | 11 ++----- LibationLauncher/LibationLauncher.csproj | 2 +- LibationLauncher/Program.cs | 2 -- .../BaseForms/AudioDecodeBaseForm.cs | 16 +++------- .../BaseForms/ProcessBaseForm.cs | 3 +- .../ProcessorAutomationController.cs | 32 +++++++++---------- 7 files changed, 27 insertions(+), 45 deletions(-) diff --git a/FileLiberator/DownloadFile.cs b/FileLiberator/DownloadFile.cs index dd67f8e0..9f23b1e0 100644 --- a/FileLiberator/DownloadFile.cs +++ b/FileLiberator/DownloadFile.cs @@ -18,7 +18,7 @@ namespace FileLiberator var client = new HttpClient(); var progress = new Progress(); - progress.ProgressChanged += OnProgressChanged; + progress.ProgressChanged += (_, e) => StreamingProgressChanged?.Invoke(this, e); StreamingBegin?.Invoke(this, proposedDownloadFilePath); @@ -32,9 +32,5 @@ namespace FileLiberator StreamingCompleted?.Invoke(this, proposedDownloadFilePath); } } - private void OnProgressChanged(object sender, DownloadProgress e) - { - StreamingProgressChanged.Invoke(this, e); - } } } diff --git a/FileLiberator/IProcessableExt.cs b/FileLiberator/IProcessableExt.cs index eff721d2..3a4339e3 100644 --- a/FileLiberator/IProcessableExt.cs +++ b/FileLiberator/IProcessableExt.cs @@ -23,17 +23,12 @@ namespace FileLiberator .GetLibrary_Flat_NoTracking() .Where(libraryBook => processable.Validate(libraryBook)); - public static async Task ProcessSingleAsync(this IProcessable processable, LibraryBook libraryBook) + public static async Task ProcessSingleAsync(this IProcessable processable, LibraryBook libraryBook, bool validate) { - if (!processable.Validate(libraryBook)) + if (validate && !processable.Validate(libraryBook)) return new StatusHandler { "Validation failed" }; - return await processable.ProcessBookAsync_NoValidation(libraryBook); - } - - public static async Task ProcessBookAsync_NoValidation(this IProcessable processable, LibraryBook libraryBook) - { - Serilog.Log.Logger.Information("Begin " + nameof(ProcessBookAsync_NoValidation) + " {@DebugInfo}", new + Serilog.Log.Logger.Information("Begin " + nameof(ProcessSingleAsync) + " {@DebugInfo}", new { libraryBook.Book.Title, libraryBook.Book.AudibleProductId, diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 7f4ef331..67686809 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.264 + 5.4.9.269 diff --git a/LibationLauncher/Program.cs b/LibationLauncher/Program.cs index 1cfbda22..bcfb1f4d 100644 --- a/LibationLauncher/Program.cs +++ b/LibationLauncher/Program.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Forms; -using AudibleApi; using AudibleApi.Authorization; using DataLayer; using Microsoft.EntityFrameworkCore; @@ -13,7 +12,6 @@ using FileManager; using InternalUtilities; using LibationWinForms; using LibationWinForms.Dialogs; -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Serilog; diff --git a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs index 6ba3b4ee..a982958b 100644 --- a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs @@ -40,9 +40,9 @@ namespace LibationWinForms.BookLiberation private void OnUnsubscribeAll(object sender, EventArgs e) { - Disposed -= OnUnsubscribeAll; if (Streamable is IAudioDecodable audioDecodable) { + Disposed -= OnUnsubscribeAll; audioDecodable.RequestCoverArt -= OnRequestCoverArt; audioDecodable.TitleDiscovered -= OnTitleDiscovered; audioDecodable.AuthorsDiscovered -= OnAuthorsDiscovered; @@ -62,10 +62,10 @@ namespace LibationWinForms.BookLiberation FileManager.PictureSize._500x500)); //Set default values from library - OnTitleDiscovered(null, libraryBook.Book.Title); - OnAuthorsDiscovered(null, string.Join(", ", libraryBook.Book.Authors)); - OnNarratorsDiscovered(null, string.Join(", ", libraryBook.Book.NarratorNames)); - OnCoverImageDiscovered(null, + OnTitleDiscovered(sender, libraryBook.Book.Title); + OnAuthorsDiscovered(sender, string.Join(", ", libraryBook.Book.Authors)); + OnNarratorsDiscovered(sender, string.Join(", ", libraryBook.Book.NarratorNames)); + OnCoverImageDiscovered(sender, FileManager.PictureStorage.GetPicture( new FileManager.PictureDefinition( libraryBook.Book.PictureId, @@ -74,7 +74,6 @@ namespace LibationWinForms.BookLiberation #endregion #region IStreamable event handler overrides - public override void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { if (!downloadProgress.ProgressPercentage.HasValue) @@ -88,11 +87,9 @@ namespace LibationWinForms.BookLiberation public override void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) => updateRemainingTime((int)timeRemaining.TotalSeconds); - #endregion #region IAudioDecodable event handlers - public virtual void OnRequestCoverArt(object sender, Action setCoverArtDelegate) => setCoverArtDelegate(GetCoverArtDelegate?.Invoke()); @@ -117,16 +114,13 @@ namespace LibationWinForms.BookLiberation public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt) => pictureBox1.UIThread(() => 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}"); private void updateRemainingTime(int remaining) => remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = $"ETA:\r\n{remaining} sec"); - } } diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index 2b59a31d..c57aaa8b 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -22,7 +22,7 @@ namespace LibationWinForms.BookLiberation //Don't unsubscribe from Dispose because it fires on //IStreamable.StreamingCompleted, and the IProcessable - //events need to live past that. + //events need to live past that event. processable.Completed += OnUnsubscribeAll; } } @@ -42,7 +42,6 @@ namespace LibationWinForms.BookLiberation public virtual void OnBegin(object sender, LibraryBook libraryBook) => LogMe.Info($"Begin: {libraryBook.Book}"); public virtual void OnStatusUpdate(object sender, string statusUpdate) => LogMe.Info("- " + statusUpdate); public virtual void OnCompleted(object sender, LibraryBook libraryBook) => LogMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); - #endregion } } diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 1575be89..de57f6b5 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -79,7 +79,7 @@ namespace LibationWinForms.BookLiberation var automatedBackupsForm = new AutomatedBackupsForm(); var logMe = LogMe.RegisterForm(automatedBackupsForm); - var convertBook = CreateStreamProcessable(null, logMe); + var convertBook = CreateStreamProcessable(logMe); await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); } @@ -91,7 +91,7 @@ namespace LibationWinForms.BookLiberation var automatedBackupsForm = new AutomatedBackupsForm(); var logMe = LogMe.RegisterForm(automatedBackupsForm); - var downloadPdf = CreateStreamProcessable(completedAction, logMe); + var downloadPdf = CreateStreamProcessable(logMe, completedAction); await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); } @@ -107,25 +107,22 @@ namespace LibationWinForms.BookLiberation } var downloadFile = CreateStreamable(onDownloadFileStreamingCompleted); - - new System.Threading.Thread(() => downloadFile.PerformDownloadFileAsync(url, destination).GetAwaiter().GetResult()) - { IsBackground = true } - .Start(); + async void runDownload() => await downloadFile.PerformDownloadFileAsync(url, destination); + new Task(runDownload).Start(); } private static IProcessable CreateBackupBook(EventHandler completedAction, LogMe logMe) { - var downloadPdf = CreateStreamProcessable(null, logMe); + var downloadPdf = CreateStreamProcessable(logMe); //Chain pdf download on DownloadDecryptBook.Completed async void onDownloadDecryptBookCompleted(object sender, LibraryBook e) { await downloadPdf.TryProcessAsync(e); - completedAction(sender, e); } - var downloadDecryptBook = CreateStreamProcessable(onDownloadDecryptBookCompleted, logMe); + var downloadDecryptBook = CreateStreamProcessable(logMe, onDownloadDecryptBookCompleted); return downloadDecryptBook; } @@ -134,9 +131,10 @@ namespace LibationWinForms.BookLiberation /// /// The derrived type to create. /// The derrived Form to create on , Show on , and Close & Dispose on + /// The logger /// An additional event handler to handle /// A new of type - private static TStrProc CreateStreamProcessable(EventHandler completedAction = null, LogMe logMe = null) + private static TStrProc CreateStreamProcessable(LogMe logMe, EventHandler completedAction = null) where TForm : ProcessBaseForm, new() where TStrProc : IStreamProcessable, new() { @@ -192,9 +190,9 @@ namespace LibationWinForms.BookLiberation } protected abstract Task RunAsync(); - protected abstract string SkipDialogText { get; } protected abstract MessageBoxButtons SkipDialogButtons { get; } + protected abstract MessageBoxDefaultButton SkipDialogDefaultButton { get; } protected abstract DialogResult CreateSkipFileResult { get; } public async Task RunBackupAsync() @@ -214,13 +212,13 @@ namespace LibationWinForms.BookLiberation LogMe.Info("DONE"); } - protected async Task ProcessOneAsync(Func> func, LibraryBook libraryBook) + protected async Task ProcessOneAsync(LibraryBook libraryBook, bool validate) { string logMessage; try { - var statusHandler = await func(libraryBook); + var statusHandler = await Processable.ProcessSingleAsync(libraryBook, validate); if (statusHandler.IsSuccess) return true; @@ -258,7 +256,7 @@ $@" Title: {libraryBook.Book.Title} details = "[Error retrieving details]"; } - var dialogResult = MessageBox.Show(string.Format(SkipDialogText, details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question); + var dialogResult = MessageBox.Show(string.Format(SkipDialogText, details), "Skip importing this book?", SkipDialogButtons, MessageBoxIcon.Question, SkipDialogDefaultButton); if (dialogResult == DialogResult.Abort) return false; @@ -291,6 +289,7 @@ An error occurred while trying to process this book. Skip this book permanently? - Click NO to skip the book this time only. We'll try again later. ".Trim(); protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.YesNo; + protected override MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button2; protected override DialogResult CreateSkipFileResult => DialogResult.Yes; public BackupSingle(LogMe logMe, IProcessable processable, LibraryBook libraryBook) @@ -302,7 +301,7 @@ An error occurred while trying to process this book. Skip this book permanently? protected override async Task RunAsync() { if (_libraryBook is not null) - await ProcessOneAsync(Processable.ProcessSingleAsync, _libraryBook); + await ProcessOneAsync(_libraryBook, validate: true); } } @@ -319,6 +318,7 @@ An error occurred while trying to process this book. - IGNORE: Permanently ignore this book. Continue processing books. (Will not try this book again later.) ".Trim(); protected override MessageBoxButtons SkipDialogButtons => MessageBoxButtons.AbortRetryIgnore; + protected override MessageBoxDefaultButton SkipDialogDefaultButton => MessageBoxDefaultButton.Button1; protected override DialogResult CreateSkipFileResult => DialogResult.Ignore; public BackupLoop(LogMe logMe, IProcessable processable, AutomatedBackupsForm automatedBackupsForm) @@ -329,7 +329,7 @@ An error occurred while trying to process this book. // 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()) { - var keepGoing = await ProcessOneAsync(Processable.ProcessBookAsync_NoValidation, libraryBook); + var keepGoing = await ProcessOneAsync(libraryBook, validate: false); if (!keepGoing) return; From 448fd78b8f7cfb5686dbab0c4400ee9cff161d2b Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 12:23:55 -0600 Subject: [PATCH 46/64] Added Dispose on IProcessable.Completed in case form is created but never shown. --- LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index c57aaa8b..ee6f90e1 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -1,4 +1,5 @@ using DataLayer; +using Dinah.Core.Windows.Forms; using FileLiberator; using System; @@ -24,14 +25,17 @@ namespace LibationWinForms.BookLiberation //IStreamable.StreamingCompleted, and the IProcessable //events need to live past that event. processable.Completed += OnUnsubscribeAll; + processable.Completed += OnCompleetdDispose; } } + private void OnCompleetdDispose(object sender, LibraryBook e) => this.UIThread(() => Dispose()); private void OnUnsubscribeAll(object sender, LibraryBook e) { if (Streamable is IProcessable processable) { processable.Completed -= OnUnsubscribeAll; + processable.Completed -= OnCompleetdDispose; processable.Begin -= OnBegin; processable.Completed -= OnCompleted; processable.StatusUpdate -= OnStatusUpdate; From 7acaac7bd3711063e2847d430d8145be069f5796 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 12:25:28 -0600 Subject: [PATCH 47/64] Fixed subscription ordering. --- .../BookLiberation/BaseForms/ProcessBaseForm.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index ee6f90e1..ca902a57 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -21,21 +21,24 @@ namespace LibationWinForms.BookLiberation processable.Completed += OnCompleted; processable.StatusUpdate += OnStatusUpdate; + //If IStreamable.StreamingCompleted is never fired, we still + //need to dispose of the form after IProcessable.Completed + processable.Completed += OnCompletedDispose; + //Don't unsubscribe from Dispose because it fires on //IStreamable.StreamingCompleted, and the IProcessable //events need to live past that event. processable.Completed += OnUnsubscribeAll; - processable.Completed += OnCompleetdDispose; } } - private void OnCompleetdDispose(object sender, LibraryBook e) => this.UIThread(() => Dispose()); + private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThread(() => Dispose()); private void OnUnsubscribeAll(object sender, LibraryBook e) { if (Streamable is IProcessable processable) { processable.Completed -= OnUnsubscribeAll; - processable.Completed -= OnCompleetdDispose; + processable.Completed -= OnCompletedDispose; processable.Begin -= OnBegin; processable.Completed -= OnCompleted; processable.StatusUpdate -= OnStatusUpdate; From 5ab4183f9b75a596df69427387bc55ba38acc580 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 12:47:02 -0600 Subject: [PATCH 48/64] Fixed event handling. --- .../BaseForms/ProcessBaseForm.cs | 10 +++++----- .../BookLiberation/BaseForms/StreamBaseForm.cs | 18 ++++++------------ 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs index ca902a57..fd9d657c 100644 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs @@ -21,13 +21,13 @@ namespace LibationWinForms.BookLiberation processable.Completed += OnCompleted; processable.StatusUpdate += OnStatusUpdate; - //If IStreamable.StreamingCompleted is never fired, we still - //need to dispose of the form after IProcessable.Completed + //The form is created on IProcessable.Begin and we + //dispose of it on IProcessable.Completed processable.Completed += OnCompletedDispose; - //Don't unsubscribe from Dispose because it fires on - //IStreamable.StreamingCompleted, and the IProcessable - //events need to live past that event. + //Don't unsubscribe from Dispose because it fires when + //IStreamable.StreamingCompleted closes the form, and + //the IProcessable events need to live past that event. processable.Completed += OnUnsubscribeAll; } } diff --git a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs index 13337e13..58860aea 100644 --- a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs @@ -34,12 +34,12 @@ namespace LibationWinForms.BookLiberation Streamable.StreamingProgressChanged += OnStreamingProgressChanged; Streamable.StreamingTimeRemaining += OnStreamingTimeRemaining; - Disposed += OnUnsubscribeAll; + FormClosed += OnUnsubscribeAll; } private void OnUnsubscribeAll(object sender, EventArgs e) { - Disposed -= OnUnsubscribeAll; + FormClosed -= OnUnsubscribeAll; Streamable.StreamingBegin -= OnStreamingBegin; Streamable.StreamingCompleted -= OnStreamingCompleted; @@ -55,30 +55,24 @@ namespace LibationWinForms.BookLiberation //thread that created form, otherwise the form and the window handle will be on //different threads. Form.BeginInvoke won't work until the form is created //(ie. shown) because control doesn't get a window handle until it is Shown. - static void sendCallback(object asyncArgs) { var e = asyncArgs as AsyncCompletedEventArgs; ((Action)e.UserState)(); } - Action code = Show; + Action show = Show; if (InvokeRequired) SyncContext.Send( sendCallback, - new AsyncCompletedEventArgs(null, false, code)); + new AsyncCompletedEventArgs(null, false, show)); else - code(); + show(); } public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } - public virtual void OnStreamingCompleted(object sender, string completedString) - => this.UIThread(() => - { - Close(); - Dispose(); - }); + public virtual void OnStreamingCompleted(object sender, string completedString) => this.UIThread(() => Close()); #endregion } } From 97730d1793b98c9f0f1a199fd74ae300c590c3f7 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 13:14:45 -0600 Subject: [PATCH 49/64] Removed unnecessary cast. --- LibationLauncher/LibationLauncher.csproj | 2 +- .../BookLiberation/ProcessorAutomationController.cs | 1 - LibationWinForms/ObjectComparer[T].cs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index 67686809..b381edd2 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.269 + 5.4.9.274 diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index de57f6b5..c911243d 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -1,6 +1,5 @@ using DataLayer; using Dinah.Core; -using Dinah.Core.ErrorHandling; using FileLiberator; using System; using System.Linq; diff --git a/LibationWinForms/ObjectComparer[T].cs b/LibationWinForms/ObjectComparer[T].cs index 2ca0ddb9..5a02b215 100644 --- a/LibationWinForms/ObjectComparer[T].cs +++ b/LibationWinForms/ObjectComparer[T].cs @@ -5,6 +5,6 @@ namespace LibationWinForms { internal class ObjectComparer : IComparer where T : IComparable { - public int Compare(object x, object y) => ((T)x).CompareTo((T)y); + public int Compare(object x, object y) => ((T)x).CompareTo(y); } } From 2c20d03506337d4880a6a19f7133a5a8359a1793 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 13:36:13 -0600 Subject: [PATCH 50/64] Refactored StreamBaseForm --- .../BaseForms/StreamBaseForm.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs index 58860aea..a11fce86 100644 --- a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs @@ -29,10 +29,12 @@ namespace LibationWinForms.BookLiberation OnUnsubscribeAll(this, EventArgs.Empty); + Streamable.StreamingBegin += ShowFormHandler; Streamable.StreamingBegin += OnStreamingBegin; - Streamable.StreamingCompleted += OnStreamingCompleted; Streamable.StreamingProgressChanged += OnStreamingProgressChanged; Streamable.StreamingTimeRemaining += OnStreamingTimeRemaining; + Streamable.StreamingCompleted += OnStreamingCompleted; + Streamable.StreamingCompleted += CloseFormHandler; FormClosed += OnUnsubscribeAll; } @@ -41,14 +43,15 @@ namespace LibationWinForms.BookLiberation { FormClosed -= OnUnsubscribeAll; + Streamable.StreamingBegin -= ShowFormHandler; Streamable.StreamingBegin -= OnStreamingBegin; - Streamable.StreamingCompleted -= OnStreamingCompleted; Streamable.StreamingProgressChanged -= OnStreamingProgressChanged; Streamable.StreamingTimeRemaining -= OnStreamingTimeRemaining; + Streamable.StreamingCompleted -= OnStreamingCompleted; + Streamable.StreamingCompleted -= CloseFormHandler; } - #region IStreamable event handlers - public virtual void OnStreamingBegin(object sender, string beginString) + private void ShowFormHandler(object sender, string beginString) { //If StreamingBegin is fired from a worker thread, the window will be created on //that UI thread. We need to make certain that we show the window on the same @@ -70,9 +73,18 @@ namespace LibationWinForms.BookLiberation else show(); } + + /// + /// If the form was shown using Show (not ShowDialog), Form.Close calls Form.Dispose + /// + private void CloseFormHandler(object sender, string completedString) => this.UIThread(() => Close()); + + + #region IStreamable event handlers + public virtual void OnStreamingBegin(object sender, string beginString) { } public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } - public virtual void OnStreamingCompleted(object sender, string completedString) => this.UIThread(() => Close()); + public virtual void OnStreamingCompleted(object sender, string completedString) { } #endregion } } From 3e2d69606bd94826078bb1cdc0f0cedd38f87ca2 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 17:43:46 -0600 Subject: [PATCH 51/64] Consolidated base forms --- FileLiberator/IFileLiberator.cs | 4 + FileLiberator/IProcessable.cs | 2 +- FileLiberator/IStreamable.cs | 2 +- LibationLauncher/LibationLauncher.csproj | 2 +- .../BookLiberation/AudioConvertForm.cs | 4 +- ...esigner.cs => AudioDecodeForm.Designer.cs} | 2 +- ...ioDecodeBaseForm.cs => AudioDecodeForm.cs} | 59 ++---- ...codeBaseForm.resx => AudioDecodeForm.resx} | 0 .../BookLiberation/AudioDecryptForm.cs | 4 +- .../BaseForms/LiberationBaseForm.cs | 189 ++++++++++++++++++ .../BaseForms/ProcessBaseForm.cs | 54 ----- .../BaseForms/ProcessBaseForm.resx | 61 ------ .../BaseForms/StreamBaseForm.cs | 90 --------- .../BaseForms/StreamBaseForm.resx | 61 ------ .../BookLiberation/DownloadForm.Designer.cs | 10 +- .../BookLiberation/DownloadForm.cs | 13 +- .../ProcessorAutomationController.cs | 17 +- 17 files changed, 242 insertions(+), 332 deletions(-) create mode 100644 FileLiberator/IFileLiberator.cs rename LibationWinForms/BookLiberation/{BaseForms/AudioDecodeBaseForm.Designer.cs => AudioDecodeForm.Designer.cs} (99%) rename LibationWinForms/BookLiberation/{BaseForms/AudioDecodeBaseForm.cs => AudioDecodeForm.cs} (59%) rename LibationWinForms/BookLiberation/{BaseForms/AudioDecodeBaseForm.resx => AudioDecodeForm.resx} (100%) create mode 100644 LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs delete mode 100644 LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs delete mode 100644 LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.resx delete mode 100644 LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs delete mode 100644 LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.resx diff --git a/FileLiberator/IFileLiberator.cs b/FileLiberator/IFileLiberator.cs new file mode 100644 index 00000000..e6bdf7aa --- /dev/null +++ b/FileLiberator/IFileLiberator.cs @@ -0,0 +1,4 @@ +namespace FileLiberator +{ + public interface IFileLiberator { } +} diff --git a/FileLiberator/IProcessable.cs b/FileLiberator/IProcessable.cs index 9814f21d..254090b2 100644 --- a/FileLiberator/IProcessable.cs +++ b/FileLiberator/IProcessable.cs @@ -5,7 +5,7 @@ using Dinah.Core.ErrorHandling; namespace FileLiberator { - public interface IProcessable + public interface IProcessable : IFileLiberator { event EventHandler Begin; diff --git a/FileLiberator/IStreamable.cs b/FileLiberator/IStreamable.cs index 94049a7a..cdc5b1a3 100644 --- a/FileLiberator/IStreamable.cs +++ b/FileLiberator/IStreamable.cs @@ -3,7 +3,7 @@ using Dinah.Core.Net.Http; namespace FileLiberator { - public interface IStreamable + public interface IStreamable : IFileLiberator { event EventHandler StreamingBegin; event EventHandler StreamingProgressChanged; diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index b381edd2..b0816399 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.274 + 5.4.9.279 diff --git a/LibationWinForms/BookLiberation/AudioConvertForm.cs b/LibationWinForms/BookLiberation/AudioConvertForm.cs index 82a586f1..29020425 100644 --- a/LibationWinForms/BookLiberation/AudioConvertForm.cs +++ b/LibationWinForms/BookLiberation/AudioConvertForm.cs @@ -3,9 +3,9 @@ using System; namespace LibationWinForms.BookLiberation { - class AudioConvertForm : AudioDecodeBaseForm + class AudioConvertForm : AudioDecodeForm { - #region AudioDecodeBaseForm overrides + #region AudioDecodeForm overrides public override string DecodeActionName => "Converting"; #endregion diff --git a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.Designer.cs b/LibationWinForms/BookLiberation/AudioDecodeForm.Designer.cs similarity index 99% rename from LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.Designer.cs rename to LibationWinForms/BookLiberation/AudioDecodeForm.Designer.cs index d38e4bbb..b16425dd 100644 --- a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.Designer.cs +++ b/LibationWinForms/BookLiberation/AudioDecodeForm.Designer.cs @@ -1,6 +1,6 @@ namespace LibationWinForms.BookLiberation { - partial class AudioDecodeBaseForm + partial class AudioDecodeForm { /// /// Required designer variable. diff --git a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs b/LibationWinForms/BookLiberation/AudioDecodeForm.cs similarity index 59% rename from LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs rename to LibationWinForms/BookLiberation/AudioDecodeForm.cs index a982958b..1e0dd1a9 100644 --- a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.cs +++ b/LibationWinForms/BookLiberation/AudioDecodeForm.cs @@ -1,15 +1,20 @@ using DataLayer; using Dinah.Core.Net.Http; using Dinah.Core.Windows.Forms; -using FileLiberator; +using LibationWinForms.BookLiberation.BaseForms; using System; namespace LibationWinForms.BookLiberation { - public partial class AudioDecodeBaseForm : ProcessBaseForm + public partial class AudioDecodeForm +#if DEBUG + : DebugIntermediate +#else + : LiberationBaseForm +#endif { public virtual string DecodeActionName { get; } = "Decoding"; - public AudioDecodeBaseForm() => InitializeComponent(); + public AudioDecodeForm() => InitializeComponent(); private Func GetCoverArtDelegate; @@ -18,41 +23,6 @@ namespace LibationWinForms.BookLiberation private string authorNames; private string narratorNames; - #region ProcessBaseForm overrides - public override void SetProcessable(IStreamable streamProcessable, LogMe logMe) - { - base.SetProcessable(streamProcessable, logMe); - - if (Streamable is not null && Streamable is IAudioDecodable audioDecodable) - { - OnUnsubscribeAll(this, EventArgs.Empty); - - audioDecodable.RequestCoverArt += OnRequestCoverArt; - audioDecodable.TitleDiscovered += OnTitleDiscovered; - audioDecodable.AuthorsDiscovered += OnAuthorsDiscovered; - audioDecodable.NarratorsDiscovered += OnNarratorsDiscovered; - audioDecodable.CoverImageDiscovered += OnCoverImageDiscovered; - - Disposed += OnUnsubscribeAll; - } - } - #endregion - - private void OnUnsubscribeAll(object sender, EventArgs e) - { - if (Streamable is IAudioDecodable audioDecodable) - { - Disposed -= OnUnsubscribeAll; - audioDecodable.RequestCoverArt -= OnRequestCoverArt; - audioDecodable.TitleDiscovered -= OnTitleDiscovered; - audioDecodable.AuthorsDiscovered -= OnAuthorsDiscovered; - audioDecodable.NarratorsDiscovered -= OnNarratorsDiscovered; - audioDecodable.CoverImageDiscovered -= OnCoverImageDiscovered; - - audioDecodable.Cancel(); - } - } - #region IProcessable event handler overrides public override void OnBegin(object sender, LibraryBook libraryBook) { @@ -74,6 +44,7 @@ namespace LibationWinForms.BookLiberation #endregion #region IStreamable event handler overrides + public override void OnStreamingBegin(object sender, string beginString) { } public override void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { if (!downloadProgress.ProgressPercentage.HasValue) @@ -87,32 +58,34 @@ namespace LibationWinForms.BookLiberation public override void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) => updateRemainingTime((int)timeRemaining.TotalSeconds); + + public override void OnStreamingCompleted(object sender, string completedString) { } #endregion #region IAudioDecodable event handlers - public virtual void OnRequestCoverArt(object sender, Action setCoverArtDelegate) + public override void OnRequestCoverArt(object sender, Action setCoverArtDelegate) => setCoverArtDelegate(GetCoverArtDelegate?.Invoke()); - public virtual void OnTitleDiscovered(object sender, string title) + public override void OnTitleDiscovered(object sender, string title) { this.UIThread(() => this.Text = DecodeActionName + " " + title); this.title = title; updateBookInfo(); } - public virtual void OnAuthorsDiscovered(object sender, string authors) + public override void OnAuthorsDiscovered(object sender, string authors) { authorNames = authors; updateBookInfo(); } - public virtual void OnNarratorsDiscovered(object sender, string narrators) + public override void OnNarratorsDiscovered(object sender, string narrators) { narratorNames = narrators; updateBookInfo(); } - public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt) + public override void OnCoverImageDiscovered(object sender, byte[] coverArt) => pictureBox1.UIThread(() => pictureBox1.Image = Dinah.Core.Drawing.ImageReader.ToImage(coverArt)); #endregion diff --git a/LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.resx b/LibationWinForms/BookLiberation/AudioDecodeForm.resx similarity index 100% rename from LibationWinForms/BookLiberation/BaseForms/AudioDecodeBaseForm.resx rename to LibationWinForms/BookLiberation/AudioDecodeForm.resx diff --git a/LibationWinForms/BookLiberation/AudioDecryptForm.cs b/LibationWinForms/BookLiberation/AudioDecryptForm.cs index a27b936d..b28e106d 100644 --- a/LibationWinForms/BookLiberation/AudioDecryptForm.cs +++ b/LibationWinForms/BookLiberation/AudioDecryptForm.cs @@ -3,9 +3,9 @@ using System; namespace LibationWinForms.BookLiberation { - class AudioDecryptForm : AudioDecodeBaseForm + class AudioDecryptForm : AudioDecodeForm { - #region AudioDecodeBaseForm overrides + #region AudioDecodeForm overrides public override string DecodeActionName => "Decrypting"; #endregion diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs new file mode 100644 index 00000000..496ccd6f --- /dev/null +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -0,0 +1,189 @@ +using DataLayer; +using Dinah.Core.Net.Http; +using Dinah.Core.Windows.Forms; +using FileLiberator; +using System; +using System.ComponentModel; +using System.Threading; +using System.Windows.Forms; + +namespace LibationWinForms.BookLiberation.BaseForms +{ + public abstract class LiberationBaseForm : Form + { + protected IFileLiberator Liberation { get; private set; } + protected LogMe LogMe { get; private set; } + + private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; + public new bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; + private SynchronizationContext SyncContext { get; } + + public LiberationBaseForm() + { + //Will be null if set outside constructor. + SyncContext = SynchronizationContext.Current; + } + + public void RegisterLiberation(IFileLiberator liberation, LogMe logMe = null) + { + if (liberation is null) return; + + Liberation = liberation; + LogMe = logMe; + + if (Liberation is IStreamable streamable) + Subscribe(streamable); + if (Liberation is IProcessable processable) + Subscribe(processable); + if (Liberation is IAudioDecodable audioDecodable) + Subscribe(audioDecodable); + } + + #region Event Subscribers and Unsubscribers + private void Subscribe(IStreamable streamable) + { + UnsubscribeStreamable(this, EventArgs.Empty); + + streamable.StreamingBegin += ShowFormHandler; + streamable.StreamingBegin += OnStreamingBegin; + streamable.StreamingProgressChanged += OnStreamingProgressChanged; + streamable.StreamingTimeRemaining += OnStreamingTimeRemaining; + streamable.StreamingCompleted += OnStreamingCompleted; + streamable.StreamingCompleted += OnStreamingCompletedClose; + + FormClosed += UnsubscribeStreamable; + } + private void Subscribe(IProcessable processable) + { + UnsubscribeProcessable(this, null); + + processable.Begin += OnBegin; + processable.StatusUpdate += OnStatusUpdate; + processable.Completed += OnCompleted; + + //The form is created on IProcessable.Begin and we + //dispose of it on IProcessable.Completed + processable.Completed += OnCompletedDispose; + + //Don't unsubscribe from Dispose because it fires when + //IStreamable.StreamingCompleted closes the form, and + //the IProcessable events need to live past that event. + processable.Completed += UnsubscribeProcessable; + } + private void Subscribe(IAudioDecodable audioDecodable) + { + UnsubscribeAudioDecodable(this, EventArgs.Empty); + + audioDecodable.RequestCoverArt += OnRequestCoverArt; + audioDecodable.TitleDiscovered += OnTitleDiscovered; + audioDecodable.AuthorsDiscovered += OnAuthorsDiscovered; + audioDecodable.NarratorsDiscovered += OnNarratorsDiscovered; + audioDecodable.CoverImageDiscovered += OnCoverImageDiscovered; + + Disposed += UnsubscribeAudioDecodable; + } + private void UnsubscribeStreamable(object sender, EventArgs e) + { + if (Liberation is not IStreamable streamable) + return; + + FormClosed -= UnsubscribeStreamable; + + streamable.StreamingBegin -= ShowFormHandler; + streamable.StreamingBegin -= OnStreamingBegin; + streamable.StreamingProgressChanged -= OnStreamingProgressChanged; + streamable.StreamingTimeRemaining -= OnStreamingTimeRemaining; + streamable.StreamingCompleted -= OnStreamingCompleted; + streamable.StreamingCompleted -= OnStreamingCompletedClose; + } + private void UnsubscribeProcessable(object sender, LibraryBook e) + { + if (Liberation is not IProcessable processable) + return; + + processable.Completed -= UnsubscribeProcessable; + processable.Completed -= OnCompletedDispose; + processable.Completed -= OnCompleted; + processable.StatusUpdate -= OnStatusUpdate; + processable.Begin -= OnBegin; + } + private void UnsubscribeAudioDecodable(object sender, EventArgs e) + { + if (Liberation is not IAudioDecodable audioDecodable) + return; + + Disposed -= UnsubscribeAudioDecodable; + audioDecodable.RequestCoverArt -= OnRequestCoverArt; + audioDecodable.TitleDiscovered -= OnTitleDiscovered; + audioDecodable.AuthorsDiscovered -= OnAuthorsDiscovered; + audioDecodable.NarratorsDiscovered -= OnNarratorsDiscovered; + audioDecodable.CoverImageDiscovered -= OnCoverImageDiscovered; + + audioDecodable.Cancel(); + } + #endregion + + #region Form creation and disposal handling + + /// + /// If the form was shown using Show (not ShowDialog), Form.Close calls Form.Dispose + /// + private void OnStreamingCompletedClose(object sender, string completedString) => this.UIThread(() => Close()); + private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThread(() => Dispose()); + + /// + /// If StreamingBegin is fired from a worker thread, the window will be created on + /// that UI thread. We need to make certain that we show the window on the same + /// thread that created form, otherwise the form and the window handle will be on + /// different threads. Form.BeginInvoke won't work until the form is created + /// (ie. shown) because control doesn't get a window handle until it is Shown. + /// + private void ShowFormHandler(object sender, string beginString) + { + static void sendCallback(object asyncArgs) + { + var e = asyncArgs as AsyncCompletedEventArgs; + ((Action)e.UserState)(); + } + + Action show = Show; + + if (InvokeRequired) + SyncContext.Send( + sendCallback, + new AsyncCompletedEventArgs(null, false, show)); + else + show(); + } + #endregion + + #region IStreamable event handlers + public virtual void OnStreamingBegin(object sender, string beginString) { } + public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress){ } + public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining){ } + public virtual void OnStreamingCompleted(object sender, string completedString){ } + #endregion + + #region IProcessable event handlers + public virtual void OnBegin(object sender, LibraryBook libraryBook){ } + public virtual void OnStatusUpdate(object sender, string statusUpdate){ } + public virtual void OnCompleted(object sender, LibraryBook libraryBook){ } + #endregion + + #region IAudioDecodable event handlers + public virtual void OnRequestCoverArt(object sender, Action setCoverArtDelegate){ } + public virtual void OnTitleDiscovered(object sender, string title){ } + public virtual void OnAuthorsDiscovered(object sender, string authors){ } + public virtual void OnNarratorsDiscovered(object sender, string narrators){ } + public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt){ } + #endregion + } + + #region VS Design View Hack + /// + /// This class is a hack so that VS designer will work wif an abstract base class. + /// https://stackoverflow.com/questions/1620847/how-can-i-get-visual-studio-2008-windows-forms-designer-to-render-a-form-that-im/2406058#2406058 + /// + public class DebugIntermediate : LiberationBaseForm { } + #endregion +} diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs deleted file mode 100644 index fd9d657c..00000000 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.cs +++ /dev/null @@ -1,54 +0,0 @@ -using DataLayer; -using Dinah.Core.Windows.Forms; -using FileLiberator; -using System; - -namespace LibationWinForms.BookLiberation -{ - public class ProcessBaseForm : StreamBaseForm - { - protected LogMe LogMe { get; private set; } - public virtual void SetProcessable(IStreamable streamable, LogMe logMe) - { - LogMe = logMe; - SetStreamable(streamable); - - if (Streamable is not null && Streamable is IProcessable processable) - { - OnUnsubscribeAll(this, null); - - processable.Begin += OnBegin; - processable.Completed += OnCompleted; - processable.StatusUpdate += OnStatusUpdate; - - //The form is created on IProcessable.Begin and we - //dispose of it on IProcessable.Completed - processable.Completed += OnCompletedDispose; - - //Don't unsubscribe from Dispose because it fires when - //IStreamable.StreamingCompleted closes the form, and - //the IProcessable events need to live past that event. - processable.Completed += OnUnsubscribeAll; - } - } - private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThread(() => Dispose()); - - private void OnUnsubscribeAll(object sender, LibraryBook e) - { - if (Streamable is IProcessable processable) - { - processable.Completed -= OnUnsubscribeAll; - processable.Completed -= OnCompletedDispose; - processable.Begin -= OnBegin; - processable.Completed -= OnCompleted; - processable.StatusUpdate -= OnStatusUpdate; - } - } - - #region IProcessable event handlers - public virtual void OnBegin(object sender, LibraryBook libraryBook) => LogMe.Info($"Begin: {libraryBook.Book}"); - public virtual void OnStatusUpdate(object sender, string statusUpdate) => LogMe.Info("- " + statusUpdate); - public virtual void OnCompleted(object sender, LibraryBook libraryBook) => LogMe.Info($"Completed: {libraryBook.Book}{Environment.NewLine}"); - #endregion - } -} diff --git a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.resx b/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.resx deleted file mode 100644 index e8ae276d..00000000 --- a/LibationWinForms/BookLiberation/BaseForms/ProcessBaseForm.resx +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs deleted file mode 100644 index a11fce86..00000000 --- a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.cs +++ /dev/null @@ -1,90 +0,0 @@ -using Dinah.Core.Net.Http; -using Dinah.Core.Windows.Forms; -using FileLiberator; -using System; -using System.ComponentModel; -using System.Threading; -using System.Windows.Forms; - -namespace LibationWinForms.BookLiberation -{ - public class StreamBaseForm : Form - { - private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; - public new bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; - private SynchronizationContext SyncContext { get; } - - public StreamBaseForm() - { - //Will be null if set outside constructor. - SyncContext = SynchronizationContext.Current; - } - - protected IStreamable Streamable { get; private set; } - public void SetStreamable(IStreamable streamable) - { - Streamable = streamable; - - if (Streamable is null) return; - - OnUnsubscribeAll(this, EventArgs.Empty); - - Streamable.StreamingBegin += ShowFormHandler; - Streamable.StreamingBegin += OnStreamingBegin; - Streamable.StreamingProgressChanged += OnStreamingProgressChanged; - Streamable.StreamingTimeRemaining += OnStreamingTimeRemaining; - Streamable.StreamingCompleted += OnStreamingCompleted; - Streamable.StreamingCompleted += CloseFormHandler; - - FormClosed += OnUnsubscribeAll; - } - - private void OnUnsubscribeAll(object sender, EventArgs e) - { - FormClosed -= OnUnsubscribeAll; - - Streamable.StreamingBegin -= ShowFormHandler; - Streamable.StreamingBegin -= OnStreamingBegin; - Streamable.StreamingProgressChanged -= OnStreamingProgressChanged; - Streamable.StreamingTimeRemaining -= OnStreamingTimeRemaining; - Streamable.StreamingCompleted -= OnStreamingCompleted; - Streamable.StreamingCompleted -= CloseFormHandler; - } - - private void ShowFormHandler(object sender, string beginString) - { - //If StreamingBegin is fired from a worker thread, the window will be created on - //that UI thread. We need to make certain that we show the window on the same - //thread that created form, otherwise the form and the window handle will be on - //different threads. Form.BeginInvoke won't work until the form is created - //(ie. shown) because control doesn't get a window handle until it is Shown. - static void sendCallback(object asyncArgs) - { - var e = asyncArgs as AsyncCompletedEventArgs; - ((Action)e.UserState)(); - } - - Action show = Show; - - if (InvokeRequired) - SyncContext.Send( - sendCallback, - new AsyncCompletedEventArgs(null, false, show)); - else - show(); - } - - /// - /// If the form was shown using Show (not ShowDialog), Form.Close calls Form.Dispose - /// - private void CloseFormHandler(object sender, string completedString) => this.UIThread(() => Close()); - - - #region IStreamable event handlers - public virtual void OnStreamingBegin(object sender, string beginString) { } - public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } - public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } - public virtual void OnStreamingCompleted(object sender, string completedString) { } - #endregion - } -} diff --git a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.resx b/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.resx deleted file mode 100644 index e8ae276d..00000000 --- a/LibationWinForms/BookLiberation/BaseForms/StreamBaseForm.resx +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/LibationWinForms/BookLiberation/DownloadForm.Designer.cs b/LibationWinForms/BookLiberation/DownloadForm.Designer.cs index 7f5f80c3..b6746fe5 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.Designer.cs +++ b/LibationWinForms/BookLiberation/DownloadForm.Designer.cs @@ -1,4 +1,7 @@ -namespace LibationWinForms.BookLiberation +using DataLayer; +using System; + +namespace LibationWinForms.BookLiberation { partial class DownloadForm { @@ -95,9 +98,10 @@ } - #endregion - private System.Windows.Forms.Label filenameLbl; + #endregion + + private System.Windows.Forms.Label filenameLbl; private System.Windows.Forms.ProgressBar progressBar1; private System.Windows.Forms.Label progressLbl; private System.Windows.Forms.Label lastUpdateLbl; diff --git a/LibationWinForms/BookLiberation/DownloadForm.cs b/LibationWinForms/BookLiberation/DownloadForm.cs index 563a68fd..1eb50c9e 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.cs +++ b/LibationWinForms/BookLiberation/DownloadForm.cs @@ -1,13 +1,18 @@ -using DataLayer; -using Dinah.Core.Net.Http; +using Dinah.Core.Net.Http; using Dinah.Core.Windows.Forms; +using LibationWinForms.BookLiberation.BaseForms; using System; using System.Linq; using System.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class DownloadForm : ProcessBaseForm + public partial class DownloadForm +#if DEBUG + : DebugIntermediate +#else + : LiberationBaseForm +#endif { public DownloadForm() { @@ -17,11 +22,11 @@ namespace LibationWinForms.BookLiberation filenameLbl.Text = ""; } + #region IStreamable event handler overrides public override void OnStreamingBegin(object sender, string beginString) { filenameLbl.UIThread(() => filenameLbl.Text = beginString); - base.OnStreamingBegin(sender, beginString); } public override void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index c911243d..04359a42 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -1,6 +1,7 @@ using DataLayer; using Dinah.Core; using FileLiberator; +using LibationWinForms.BookLiberation.BaseForms; using System; using System.Linq; using System.Threading.Tasks; @@ -126,15 +127,15 @@ namespace LibationWinForms.BookLiberation } /// - /// Create a new and links it to a new . + /// Create a new and links it to a new . /// /// The derrived type to create. - /// The derrived Form to create on , Show on , and Close & Dispose on + /// The derrived Form to create on , Show on , Close on , and Dispose on /// The logger /// An additional event handler to handle /// A new of type private static TStrProc CreateStreamProcessable(LogMe logMe, EventHandler completedAction = null) - where TForm : ProcessBaseForm, new() + where TForm : LiberationBaseForm, new() where TStrProc : IStreamProcessable, new() { var strProc = new TStrProc(); @@ -142,7 +143,7 @@ namespace LibationWinForms.BookLiberation strProc.Begin += (sender, libraryBook) => { var processForm = new TForm(); - processForm.SetProcessable(strProc, logMe); + processForm.RegisterLiberation(strProc, logMe); processForm.OnBegin(sender, libraryBook); }; @@ -153,20 +154,20 @@ namespace LibationWinForms.BookLiberation } /// - /// Creates a new and links it to a new + /// Creates a new and links it to a new /// /// The derrived type to create. - /// The derrived Form to create, which will Show on and Close & Dispose on . + /// The derrived Form to create, which will Show on and Close, Dispose on . /// An additional event handler to handle /// A new of type private static TStr CreateStreamable(EventHandler completedAction = null) - where TForm : StreamBaseForm, new() + where TForm : LiberationBaseForm, new() where TStr : IStreamable, new() { var streamable = new TStr(); var streamForm = new TForm(); - streamForm.SetStreamable(streamable); + streamForm.RegisterLiberation(streamable); if (completedAction != null) streamable.StreamingCompleted += completedAction; From e37a2ccca9ce13c2554fd1c180c8ebd3bd432dfa Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 17:56:36 -0600 Subject: [PATCH 52/64] Typos and small error correction. --- FileLiberator/IAudioDecodable.cs | 5 +- .../BaseForms/LiberationBaseForm.cs | 58 ++++++++++--------- 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/FileLiberator/IAudioDecodable.cs b/FileLiberator/IAudioDecodable.cs index eb18bb91..e1b74e29 100644 --- a/FileLiberator/IAudioDecodable.cs +++ b/FileLiberator/IAudioDecodable.cs @@ -1,17 +1,14 @@ -using Dinah.Core.Net.Http; -using System; +using System; namespace FileLiberator { public interface IAudioDecodable : IStreamProcessable { - event EventHandler> RequestCoverArt; event EventHandler TitleDiscovered; event EventHandler AuthorsDiscovered; event EventHandler NarratorsDiscovered; event EventHandler CoverImageDiscovered; - void Cancel(); } } diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs index 496ccd6f..c1c4bef2 100644 --- a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -9,9 +9,9 @@ using System.Windows.Forms; namespace LibationWinForms.BookLiberation.BaseForms { - public abstract class LiberationBaseForm : Form + public abstract class LiberationBaseForm : Form { - protected IFileLiberator Liberation { get; private set; } + protected IFileLiberator FileLiberator { get; private set; } protected LogMe LogMe { get; private set; } private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; @@ -24,18 +24,19 @@ namespace LibationWinForms.BookLiberation.BaseForms SyncContext = SynchronizationContext.Current; } - public void RegisterLiberation(IFileLiberator liberation, LogMe logMe = null) + public void RegisterLiberation(IFileLiberator fileLiberator, LogMe logMe = null) { - if (liberation is null) return; + //IFileLiberator must at least be IStreamable, otherwise the Form won't ever Show() + if (fileLiberator is null || fileLiberator is not IStreamable streamable) return; - Liberation = liberation; + FileLiberator = fileLiberator; LogMe = logMe; - if (Liberation is IStreamable streamable) - Subscribe(streamable); - if (Liberation is IProcessable processable) + Subscribe(streamable); + + if (FileLiberator is IProcessable processable) Subscribe(processable); - if (Liberation is IAudioDecodable audioDecodable) + if (FileLiberator is IAudioDecodable audioDecodable) Subscribe(audioDecodable); } @@ -44,7 +45,7 @@ namespace LibationWinForms.BookLiberation.BaseForms { UnsubscribeStreamable(this, EventArgs.Empty); - streamable.StreamingBegin += ShowFormHandler; + streamable.StreamingBegin += OnStreamingBeginShow; streamable.StreamingBegin += OnStreamingBegin; streamable.StreamingProgressChanged += OnStreamingProgressChanged; streamable.StreamingTimeRemaining += OnStreamingTimeRemaining; @@ -84,12 +85,12 @@ namespace LibationWinForms.BookLiberation.BaseForms } private void UnsubscribeStreamable(object sender, EventArgs e) { - if (Liberation is not IStreamable streamable) + if (FileLiberator is not IStreamable streamable) return; FormClosed -= UnsubscribeStreamable; - streamable.StreamingBegin -= ShowFormHandler; + streamable.StreamingBegin -= OnStreamingBeginShow; streamable.StreamingBegin -= OnStreamingBegin; streamable.StreamingProgressChanged -= OnStreamingProgressChanged; streamable.StreamingTimeRemaining -= OnStreamingTimeRemaining; @@ -98,7 +99,7 @@ namespace LibationWinForms.BookLiberation.BaseForms } private void UnsubscribeProcessable(object sender, LibraryBook e) { - if (Liberation is not IProcessable processable) + if (FileLiberator is not IProcessable processable) return; processable.Completed -= UnsubscribeProcessable; @@ -109,7 +110,7 @@ namespace LibationWinForms.BookLiberation.BaseForms } private void UnsubscribeAudioDecodable(object sender, EventArgs e) { - if (Liberation is not IAudioDecodable audioDecodable) + if (FileLiberator is not IAudioDecodable audioDecodable) return; Disposed -= UnsubscribeAudioDecodable; @@ -135,10 +136,11 @@ namespace LibationWinForms.BookLiberation.BaseForms /// If StreamingBegin is fired from a worker thread, the window will be created on /// that UI thread. We need to make certain that we show the window on the same /// thread that created form, otherwise the form and the window handle will be on - /// different threads. Form.BeginInvoke won't work until the form is created - /// (ie. shown) because control doesn't get a window handle until it is Shown. + /// different threads, and the renderer will be on a worker thread which 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. /// - private void ShowFormHandler(object sender, string beginString) + private void OnStreamingBeginShow(object sender, string beginString) { static void sendCallback(object asyncArgs) { @@ -159,23 +161,23 @@ namespace LibationWinForms.BookLiberation.BaseForms #region IStreamable event handlers public virtual void OnStreamingBegin(object sender, string beginString) { } - public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress){ } - public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining){ } - public virtual void OnStreamingCompleted(object sender, string completedString){ } + public virtual void OnStreamingProgressChanged(object sender, DownloadProgress downloadProgress) { } + public virtual void OnStreamingTimeRemaining(object sender, TimeSpan timeRemaining) { } + public virtual void OnStreamingCompleted(object sender, string completedString) { } #endregion #region IProcessable event handlers - public virtual void OnBegin(object sender, LibraryBook libraryBook){ } - public virtual void OnStatusUpdate(object sender, string statusUpdate){ } - public virtual void OnCompleted(object sender, LibraryBook libraryBook){ } + public virtual void OnBegin(object sender, LibraryBook libraryBook) { } + public virtual void OnStatusUpdate(object sender, string statusUpdate) { } + public virtual void OnCompleted(object sender, LibraryBook libraryBook) { } #endregion #region IAudioDecodable event handlers - public virtual void OnRequestCoverArt(object sender, Action setCoverArtDelegate){ } - public virtual void OnTitleDiscovered(object sender, string title){ } - public virtual void OnAuthorsDiscovered(object sender, string authors){ } - public virtual void OnNarratorsDiscovered(object sender, string narrators){ } - public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt){ } + public virtual void OnRequestCoverArt(object sender, Action setCoverArtDelegate) { } + public virtual void OnTitleDiscovered(object sender, string title) { } + public virtual void OnAuthorsDiscovered(object sender, string authors) { } + public virtual void OnNarratorsDiscovered(object sender, string narrators) { } + public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt) { } #endregion } From f925d10d2b83a4ea8ad6b51c812e057778d34e88 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 18:02:49 -0600 Subject: [PATCH 53/64] Renamed. --- .../BookLiberation/BaseForms/LiberationBaseForm.cs | 2 +- .../BookLiberation/ProcessorAutomationController.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs index c1c4bef2..2fed2e82 100644 --- a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -24,7 +24,7 @@ namespace LibationWinForms.BookLiberation.BaseForms SyncContext = SynchronizationContext.Current; } - public void RegisterLiberation(IFileLiberator fileLiberator, LogMe logMe = null) + public void RegisterFileLiberator(IFileLiberator fileLiberator, LogMe logMe = null) { //IFileLiberator must at least be IStreamable, otherwise the Form won't ever Show() if (fileLiberator is null || fileLiberator is not IStreamable streamable) return; diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 04359a42..126f1321 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -143,7 +143,7 @@ namespace LibationWinForms.BookLiberation strProc.Begin += (sender, libraryBook) => { var processForm = new TForm(); - processForm.RegisterLiberation(strProc, logMe); + processForm.RegisterFileLiberator(strProc, logMe); processForm.OnBegin(sender, libraryBook); }; @@ -167,7 +167,7 @@ namespace LibationWinForms.BookLiberation var streamable = new TStr(); var streamForm = new TForm(); - streamForm.RegisterLiberation(streamable); + streamForm.RegisterFileLiberator(streamable); if (completedAction != null) streamable.StreamingCompleted += completedAction; From 081878b6f78db19c69a040a7b578516e741adff9 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 18:43:34 -0600 Subject: [PATCH 54/64] Remove IStreamProcessable. IProcessible inherits IStreamable. --- FileLiberator/DownloadableBase.cs | 2 +- FileLiberator/IAudioDecodable.cs | 2 +- FileLiberator/IFileLiberator.cs | 4 --- FileLiberator/IProcessable.cs | 2 +- FileLiberator/IStreamProcessable.cs | 5 --- FileLiberator/IStreamable.cs | 2 +- LibationLauncher/LibationLauncher.csproj | 2 +- .../BaseForms/LiberationBaseForm.cs | 31 +++++++++---------- .../ProcessorAutomationController.cs | 18 +++++------ 9 files changed, 28 insertions(+), 40 deletions(-) delete mode 100644 FileLiberator/IFileLiberator.cs delete mode 100644 FileLiberator/IStreamProcessable.cs diff --git a/FileLiberator/DownloadableBase.cs b/FileLiberator/DownloadableBase.cs index 9156e626..38b5a76e 100644 --- a/FileLiberator/DownloadableBase.cs +++ b/FileLiberator/DownloadableBase.cs @@ -6,7 +6,7 @@ using Dinah.Core.Net.Http; namespace FileLiberator { - public abstract class DownloadableBase : IStreamProcessable + public abstract class DownloadableBase : IProcessable { public event EventHandler Begin; public event EventHandler Completed; diff --git a/FileLiberator/IAudioDecodable.cs b/FileLiberator/IAudioDecodable.cs index e1b74e29..128242ef 100644 --- a/FileLiberator/IAudioDecodable.cs +++ b/FileLiberator/IAudioDecodable.cs @@ -2,7 +2,7 @@ namespace FileLiberator { - public interface IAudioDecodable : IStreamProcessable + public interface IAudioDecodable : IProcessable { event EventHandler> RequestCoverArt; event EventHandler TitleDiscovered; diff --git a/FileLiberator/IFileLiberator.cs b/FileLiberator/IFileLiberator.cs deleted file mode 100644 index e6bdf7aa..00000000 --- a/FileLiberator/IFileLiberator.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace FileLiberator -{ - public interface IFileLiberator { } -} diff --git a/FileLiberator/IProcessable.cs b/FileLiberator/IProcessable.cs index 254090b2..6def9301 100644 --- a/FileLiberator/IProcessable.cs +++ b/FileLiberator/IProcessable.cs @@ -5,7 +5,7 @@ using Dinah.Core.ErrorHandling; namespace FileLiberator { - public interface IProcessable : IFileLiberator + public interface IProcessable : IStreamable { event EventHandler Begin; diff --git a/FileLiberator/IStreamProcessable.cs b/FileLiberator/IStreamProcessable.cs deleted file mode 100644 index 360663ab..00000000 --- a/FileLiberator/IStreamProcessable.cs +++ /dev/null @@ -1,5 +0,0 @@ - -namespace FileLiberator -{ - public interface IStreamProcessable : IStreamable, IProcessable { } -} diff --git a/FileLiberator/IStreamable.cs b/FileLiberator/IStreamable.cs index cdc5b1a3..94049a7a 100644 --- a/FileLiberator/IStreamable.cs +++ b/FileLiberator/IStreamable.cs @@ -3,7 +3,7 @@ using Dinah.Core.Net.Http; namespace FileLiberator { - public interface IStreamable : IFileLiberator + public interface IStreamable { event EventHandler StreamingBegin; event EventHandler StreamingProgressChanged; diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj index b0816399..41db5551 100644 --- a/LibationLauncher/LibationLauncher.csproj +++ b/LibationLauncher/LibationLauncher.csproj @@ -13,7 +13,7 @@ win-x64 - 5.4.9.279 + 5.4.9.280 diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs index 2fed2e82..f04788b7 100644 --- a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -11,7 +11,7 @@ namespace LibationWinForms.BookLiberation.BaseForms { public abstract class LiberationBaseForm : Form { - protected IFileLiberator FileLiberator { get; private set; } + protected IStreamable Streamable { get; private set; } protected LogMe LogMe { get; private set; } private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; @@ -24,19 +24,19 @@ namespace LibationWinForms.BookLiberation.BaseForms SyncContext = SynchronizationContext.Current; } - public void RegisterFileLiberator(IFileLiberator fileLiberator, LogMe logMe = null) + public void RegisterFileLiberator(IStreamable streamable, LogMe logMe = null) { //IFileLiberator must at least be IStreamable, otherwise the Form won't ever Show() - if (fileLiberator is null || fileLiberator is not IStreamable streamable) return; + if (streamable is null) return; - FileLiberator = fileLiberator; + Streamable = streamable; LogMe = logMe; Subscribe(streamable); - if (FileLiberator is IProcessable processable) + if (Streamable is IProcessable processable) Subscribe(processable); - if (FileLiberator is IAudioDecodable audioDecodable) + if (Streamable is IAudioDecodable audioDecodable) Subscribe(audioDecodable); } @@ -85,21 +85,18 @@ namespace LibationWinForms.BookLiberation.BaseForms } private void UnsubscribeStreamable(object sender, EventArgs e) { - if (FileLiberator is not IStreamable streamable) - return; - FormClosed -= UnsubscribeStreamable; - streamable.StreamingBegin -= OnStreamingBeginShow; - streamable.StreamingBegin -= OnStreamingBegin; - streamable.StreamingProgressChanged -= OnStreamingProgressChanged; - streamable.StreamingTimeRemaining -= OnStreamingTimeRemaining; - streamable.StreamingCompleted -= OnStreamingCompleted; - streamable.StreamingCompleted -= OnStreamingCompletedClose; + Streamable.StreamingBegin -= OnStreamingBeginShow; + Streamable.StreamingBegin -= OnStreamingBegin; + Streamable.StreamingProgressChanged -= OnStreamingProgressChanged; + Streamable.StreamingTimeRemaining -= OnStreamingTimeRemaining; + Streamable.StreamingCompleted -= OnStreamingCompleted; + Streamable.StreamingCompleted -= OnStreamingCompletedClose; } private void UnsubscribeProcessable(object sender, LibraryBook e) { - if (FileLiberator is not IProcessable processable) + if (Streamable is not IProcessable processable) return; processable.Completed -= UnsubscribeProcessable; @@ -110,7 +107,7 @@ namespace LibationWinForms.BookLiberation.BaseForms } private void UnsubscribeAudioDecodable(object sender, EventArgs e) { - if (FileLiberator is not IAudioDecodable audioDecodable) + if (Streamable is not IAudioDecodable audioDecodable) return; Disposed -= UnsubscribeAudioDecodable; diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 126f1321..78faf7e4 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -79,7 +79,7 @@ namespace LibationWinForms.BookLiberation var automatedBackupsForm = new AutomatedBackupsForm(); var logMe = LogMe.RegisterForm(automatedBackupsForm); - var convertBook = CreateStreamProcessable(logMe); + var convertBook = CreateProcessable(logMe); await new BackupLoop(logMe, convertBook, automatedBackupsForm).RunBackupAsync(); } @@ -91,7 +91,7 @@ namespace LibationWinForms.BookLiberation var automatedBackupsForm = new AutomatedBackupsForm(); var logMe = LogMe.RegisterForm(automatedBackupsForm); - var downloadPdf = CreateStreamProcessable(logMe, completedAction); + var downloadPdf = CreateProcessable(logMe, completedAction); await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); } @@ -113,7 +113,7 @@ namespace LibationWinForms.BookLiberation private static IProcessable CreateBackupBook(EventHandler completedAction, LogMe logMe) { - var downloadPdf = CreateStreamProcessable(logMe); + var downloadPdf = CreateProcessable(logMe); //Chain pdf download on DownloadDecryptBook.Completed async void onDownloadDecryptBookCompleted(object sender, LibraryBook e) @@ -122,21 +122,21 @@ namespace LibationWinForms.BookLiberation completedAction(sender, e); } - var downloadDecryptBook = CreateStreamProcessable(logMe, onDownloadDecryptBookCompleted); + var downloadDecryptBook = CreateProcessable(logMe, onDownloadDecryptBookCompleted); return downloadDecryptBook; } /// - /// Create a new and links it to a new . + /// Create a new and links it to a new . /// - /// The derrived type to create. + /// The derrived type to create. /// The derrived Form to create on , Show on , Close on , and Dispose on /// The logger /// An additional event handler to handle - /// A new of type - private static TStrProc CreateStreamProcessable(LogMe logMe, EventHandler completedAction = null) + /// A new of type + private static TStrProc CreateProcessable(LogMe logMe, EventHandler completedAction = null) where TForm : LiberationBaseForm, new() - where TStrProc : IStreamProcessable, new() + where TStrProc : IProcessable, new() { var strProc = new TStrProc(); From 0e7930f2b6f9062af1c3b5ff56cbda777da4cac3 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Thu, 12 Aug 2021 18:50:51 -0600 Subject: [PATCH 55/64] Removed intermediate class. --- LibationWinForms/BookLiberation/AudioDecodeForm.cs | 7 +------ .../BookLiberation/BaseForms/LiberationBaseForm.cs | 10 +--------- LibationWinForms/BookLiberation/DownloadForm.cs | 7 +------ 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/LibationWinForms/BookLiberation/AudioDecodeForm.cs b/LibationWinForms/BookLiberation/AudioDecodeForm.cs index 1e0dd1a9..4d8f66b8 100644 --- a/LibationWinForms/BookLiberation/AudioDecodeForm.cs +++ b/LibationWinForms/BookLiberation/AudioDecodeForm.cs @@ -6,12 +6,7 @@ using System; namespace LibationWinForms.BookLiberation { - public partial class AudioDecodeForm -#if DEBUG - : DebugIntermediate -#else - : LiberationBaseForm -#endif + public partial class AudioDecodeForm : LiberationBaseForm { public virtual string DecodeActionName { get; } = "Decoding"; public AudioDecodeForm() => InitializeComponent(); diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs index f04788b7..21618888 100644 --- a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -9,7 +9,7 @@ using System.Windows.Forms; namespace LibationWinForms.BookLiberation.BaseForms { - public abstract class LiberationBaseForm : Form + public class LiberationBaseForm : Form { protected IStreamable Streamable { get; private set; } protected LogMe LogMe { get; private set; } @@ -177,12 +177,4 @@ namespace LibationWinForms.BookLiberation.BaseForms public virtual void OnCoverImageDiscovered(object sender, byte[] coverArt) { } #endregion } - - #region VS Design View Hack - /// - /// This class is a hack so that VS designer will work wif an abstract base class. - /// https://stackoverflow.com/questions/1620847/how-can-i-get-visual-studio-2008-windows-forms-designer-to-render-a-form-that-im/2406058#2406058 - /// - public class DebugIntermediate : LiberationBaseForm { } - #endregion } diff --git a/LibationWinForms/BookLiberation/DownloadForm.cs b/LibationWinForms/BookLiberation/DownloadForm.cs index 1eb50c9e..e503deac 100644 --- a/LibationWinForms/BookLiberation/DownloadForm.cs +++ b/LibationWinForms/BookLiberation/DownloadForm.cs @@ -7,12 +7,7 @@ using System.Windows.Forms; namespace LibationWinForms.BookLiberation { - public partial class DownloadForm -#if DEBUG - : DebugIntermediate -#else - : LiberationBaseForm -#endif + public partial class DownloadForm : LiberationBaseForm { public DownloadForm() { From 766d427b19aa70aed69f0db4055a9c822ac9813a Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Fri, 13 Aug 2021 09:31:19 -0600 Subject: [PATCH 56/64] Improved cross thread execution and minor refactoring. --- .../AsyncNotifyPropertyChanged.cs | 29 ++------ .../BaseForms/LiberationBaseForm.cs | 44 ++++-------- .../ProcessorAutomationController.cs | 71 +++++++------------ LibationWinForms/CrossThreadSync[T].cs | 45 ++++++++++++ 4 files changed, 90 insertions(+), 99 deletions(-) create mode 100644 LibationWinForms/CrossThreadSync[T].cs diff --git a/LibationWinForms/AsyncNotifyPropertyChanged.cs b/LibationWinForms/AsyncNotifyPropertyChanged.cs index 1c8c8775..d9511f2d 100644 --- a/LibationWinForms/AsyncNotifyPropertyChanged.cs +++ b/LibationWinForms/AsyncNotifyPropertyChanged.cs @@ -7,31 +7,12 @@ namespace LibationWinForms public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; - private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; - private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; - private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; + private CrossThreadSync ThreadSync { get; } = new CrossThreadSync(); + + public AsyncNotifyPropertyChanged() + =>ThreadSync.ObjectReceived += (_, args) => PropertyChanged?.Invoke(this, args); protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") - { - var propertyChangedArgs = new PropertyChangedEventArgs(propertyName); - - if (InvokeRequired) - { - SyncContext.Post( - PostPropertyChangedCallback, - new AsyncCompletedEventArgs(null, false, propertyChangedArgs)); - } - else - { - OnPropertyChanged(propertyChangedArgs); - } - } - private void PostPropertyChangedCallback(object asyncArgs) - { - var e = asyncArgs as AsyncCompletedEventArgs; - - OnPropertyChanged(e.UserState as PropertyChangedEventArgs); - } - private void OnPropertyChanged(PropertyChangedEventArgs e) => PropertyChanged?.Invoke(this, e); + => ThreadSync.Post(new PropertyChangedEventArgs(propertyName)); } } diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs index 21618888..e978aa79 100644 --- a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -3,8 +3,6 @@ using Dinah.Core.Net.Http; using Dinah.Core.Windows.Forms; using FileLiberator; using System; -using System.ComponentModel; -using System.Threading; using System.Windows.Forms; namespace LibationWinForms.BookLiberation.BaseForms @@ -13,15 +11,15 @@ namespace LibationWinForms.BookLiberation.BaseForms { protected IStreamable Streamable { get; private set; } protected LogMe LogMe { get; private set; } - - private int InstanceThreadId { get; } = Thread.CurrentThread.ManagedThreadId; - public new bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; - private SynchronizationContext SyncContext { get; } + private CrossThreadSync FormSync { get; } = new CrossThreadSync(); public LiberationBaseForm() { - //Will be null if set outside constructor. - SyncContext = SynchronizationContext.Current; + //SynchronizationContext.Current will be null until the process contains a Form. + //If this is the first form created, it will not exist until after execution + //reaches inside the constructor. So need to reset the context here. + FormSync.ResetContext(); + FormSync.ObjectReceived += (_, action) => action(); } public void RegisterFileLiberator(IStreamable streamable, LogMe logMe = null) @@ -130,30 +128,14 @@ namespace LibationWinForms.BookLiberation.BaseForms private void OnCompletedDispose(object sender, LibraryBook e) => this.UIThread(() => Dispose()); /// - /// If StreamingBegin is fired from a worker thread, the window will be created on - /// that UI thread. We need to make certain that we show the window on the same - /// thread that created form, otherwise the form and the window handle will be on - /// different threads, and the renderer will be on a worker thread which 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. + /// If StreamingBegin is fired from a worker thread, the window will be created on that + /// worker thread. We need to make certain that we show the window on the UI thread (same + /// thread that created form), otherwise the renderer will be on a worker thread which + /// 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. /// - private void OnStreamingBeginShow(object sender, string beginString) - { - static void sendCallback(object asyncArgs) - { - var e = asyncArgs as AsyncCompletedEventArgs; - ((Action)e.UserState)(); - } - - Action show = Show; - - if (InvokeRequired) - SyncContext.Send( - sendCallback, - new AsyncCompletedEventArgs(null, false, show)); - else - show(); - } + private void OnStreamingBeginShow(object sender, string beginString) => FormSync.Send(Show); + #endregion #region IStreamable event handlers diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 78faf7e4..736de469 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -52,7 +52,7 @@ namespace LibationWinForms.BookLiberation { public static async Task BackupSingleBookAsync(LibraryBook libraryBook, EventHandler completedAction = null) { - Serilog.Log.Logger.Information("Begin backup single {@DebugInfo}", new { libraryBook?.Book?.AudibleProductId }); + Serilog.Log.Logger.Information($"Begin {nameof(BackupSingleBookAsync)} {{@DebugInfo}}", new { libraryBook?.Book?.AudibleProductId }); var logMe = LogMe.RegisterForm(); var backupBook = CreateBackupBook(completedAction, logMe); @@ -96,21 +96,6 @@ namespace LibationWinForms.BookLiberation await new BackupLoop(logMe, downloadPdf, automatedBackupsForm).RunBackupAsync(); } - public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) - { - Serilog.Log.Logger.Information($"Begin {nameof(DownloadFile)} for {url}"); - - void onDownloadFileStreamingCompleted(object o, string s) - { - if (showDownloadCompletedDialog) - MessageBox.Show("File downloaded to:\r\n\r\n" + s); - } - - var downloadFile = CreateStreamable(onDownloadFileStreamingCompleted); - async void runDownload() => await downloadFile.PerformDownloadFileAsync(url, destination); - new Task(runDownload).Start(); - } - private static IProcessable CreateBackupBook(EventHandler completedAction, LogMe logMe) { var downloadPdf = CreateProcessable(logMe); @@ -126,19 +111,40 @@ namespace LibationWinForms.BookLiberation return downloadDecryptBook; } + public static void DownloadFile(string url, string destination, bool showDownloadCompletedDialog = false) + { + Serilog.Log.Logger.Information($"Begin {nameof(DownloadFile)} for {url}"); + + void onDownloadFileStreamingCompleted(object sender, string savedFile) + { + Serilog.Log.Logger.Information($"Completed {nameof(DownloadFile)} for {url}. Saved to {savedFile}"); + + if (showDownloadCompletedDialog) + MessageBox.Show($"File downloaded to:{Environment.NewLine}{Environment.NewLine}{savedFile}"); + } + + var downloadFile = new DownloadFile(); + var downloadForm = new DownloadForm(); + downloadForm.RegisterFileLiberator(downloadFile); + downloadFile.StreamingCompleted += onDownloadFileStreamingCompleted; + + async void runDownload() => await downloadFile.PerformDownloadFileAsync(url, destination); + new Task(runDownload).Start(); + } + /// /// Create a new and links it to a new . /// - /// The derrived type to create. + /// The derrived type to create. /// The derrived Form to create on , Show on , Close on , and Dispose on /// The logger /// An additional event handler to handle /// A new of type - private static TStrProc CreateProcessable(LogMe logMe, EventHandler completedAction = null) + private static TProcessable CreateProcessable(LogMe logMe, EventHandler completedAction = null) where TForm : LiberationBaseForm, new() - where TStrProc : IProcessable, new() + where TProcessable : IProcessable, new() { - var strProc = new TStrProc(); + var strProc = new TProcessable(); strProc.Begin += (sender, libraryBook) => { @@ -147,33 +153,10 @@ namespace LibationWinForms.BookLiberation processForm.OnBegin(sender, libraryBook); }; - if (completedAction != null) - strProc.Completed += completedAction; + strProc.Completed += completedAction; return strProc; } - - /// - /// Creates a new and links it to a new - /// - /// The derrived type to create. - /// The derrived Form to create, which will Show on and Close, Dispose on . - /// An additional event handler to handle - /// A new of type - private static TStr CreateStreamable(EventHandler completedAction = null) - where TForm : LiberationBaseForm, new() - where TStr : IStreamable, new() - { - var streamable = new TStr(); - - var streamForm = new TForm(); - streamForm.RegisterFileLiberator(streamable); - - if (completedAction != null) - streamable.StreamingCompleted += completedAction; - - return streamable; - } } internal abstract class BackupRunner diff --git a/LibationWinForms/CrossThreadSync[T].cs b/LibationWinForms/CrossThreadSync[T].cs new file mode 100644 index 00000000..5b9b6b63 --- /dev/null +++ b/LibationWinForms/CrossThreadSync[T].cs @@ -0,0 +1,45 @@ +using System; +using System.ComponentModel; +using System.Threading; + +namespace LibationWinForms +{ + internal class CrossThreadSync + { + public event EventHandler ObjectReceived; + private int InstanceThreadId { get; set; } = Thread.CurrentThread.ManagedThreadId; + private SynchronizationContext SyncContext { get; set; } = SynchronizationContext.Current; + private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; + + public void ResetContext() + { + SyncContext = SynchronizationContext.Current; + InstanceThreadId = Thread.CurrentThread.ManagedThreadId; + } + + public void Send(T obj) + { + if (InvokeRequired) + SyncContext.Send(SendOrPostCallback, new AsyncCompletedEventArgs(null, false, obj)); + else + ObjectReceived?.Invoke(this, obj); + } + + public void Post(T obj) + { + if (InvokeRequired) + SyncContext.Post(SendOrPostCallback, new AsyncCompletedEventArgs(null, false, obj)); + else + ObjectReceived?.Invoke(this, obj); + } + + private void SendOrPostCallback(object asyncArgs) + { + var e = asyncArgs as AsyncCompletedEventArgs; + + var userObject = (T)e.UserState; + + ObjectReceived?.Invoke(this, userObject); + } + } +} From a44c46333ffad8b30faa436de5c14bdea99b1017 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Fri, 13 Aug 2021 16:34:09 -0600 Subject: [PATCH 57/64] Improved cross threaded invocation. --- .../AsyncNotifyPropertyChanged.cs | 8 +- .../BaseForms/LiberationBaseForm.cs | 7 +- LibationWinForms/CrossThreadSync[T].cs | 45 ------ LibationWinForms/GridEntry.cs | 1 - LibationWinForms/SynchronizeInvoker.cs | 143 ++++++++++++++++++ 5 files changed, 149 insertions(+), 55 deletions(-) delete mode 100644 LibationWinForms/CrossThreadSync[T].cs create mode 100644 LibationWinForms/SynchronizeInvoker.cs diff --git a/LibationWinForms/AsyncNotifyPropertyChanged.cs b/LibationWinForms/AsyncNotifyPropertyChanged.cs index d9511f2d..20b8b512 100644 --- a/LibationWinForms/AsyncNotifyPropertyChanged.cs +++ b/LibationWinForms/AsyncNotifyPropertyChanged.cs @@ -4,15 +4,13 @@ using System.Threading; namespace LibationWinForms { - public abstract class AsyncNotifyPropertyChanged : INotifyPropertyChanged + public abstract class AsyncNotifyPropertyChanged : SynchronizeInvoker, INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; - private CrossThreadSync ThreadSync { get; } = new CrossThreadSync(); - public AsyncNotifyPropertyChanged() - =>ThreadSync.ObjectReceived += (_, args) => PropertyChanged?.Invoke(this, args); + public AsyncNotifyPropertyChanged() { } protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") - => ThreadSync.Post(new PropertyChangedEventArgs(propertyName)); + =>BeginInvoke(PropertyChanged, new object[] { this, new PropertyChangedEventArgs(propertyName) }); } } diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs index e978aa79..b7f48d94 100644 --- a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -11,15 +11,14 @@ namespace LibationWinForms.BookLiberation.BaseForms { protected IStreamable Streamable { get; private set; } protected LogMe LogMe { get; private set; } - private CrossThreadSync FormSync { get; } = new CrossThreadSync(); + private SynchronizeInvoker Invoker { get; init; } public LiberationBaseForm() { //SynchronizationContext.Current will be null until the process contains a Form. //If this is the first form created, it will not exist until after execution //reaches inside the constructor. So need to reset the context here. - FormSync.ResetContext(); - FormSync.ObjectReceived += (_, action) => action(); + Invoker = new SynchronizeInvoker(); } public void RegisterFileLiberator(IStreamable streamable, LogMe logMe = null) @@ -134,7 +133,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. /// - private void OnStreamingBeginShow(object sender, string beginString) => FormSync.Send(Show); + private void OnStreamingBeginShow(object sender, string beginString) => Invoker.Invoke(Show); #endregion diff --git a/LibationWinForms/CrossThreadSync[T].cs b/LibationWinForms/CrossThreadSync[T].cs deleted file mode 100644 index 5b9b6b63..00000000 --- a/LibationWinForms/CrossThreadSync[T].cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.ComponentModel; -using System.Threading; - -namespace LibationWinForms -{ - internal class CrossThreadSync - { - public event EventHandler ObjectReceived; - private int InstanceThreadId { get; set; } = Thread.CurrentThread.ManagedThreadId; - private SynchronizationContext SyncContext { get; set; } = SynchronizationContext.Current; - private bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; - - public void ResetContext() - { - SyncContext = SynchronizationContext.Current; - InstanceThreadId = Thread.CurrentThread.ManagedThreadId; - } - - public void Send(T obj) - { - if (InvokeRequired) - SyncContext.Send(SendOrPostCallback, new AsyncCompletedEventArgs(null, false, obj)); - else - ObjectReceived?.Invoke(this, obj); - } - - public void Post(T obj) - { - if (InvokeRequired) - SyncContext.Post(SendOrPostCallback, new AsyncCompletedEventArgs(null, false, obj)); - else - ObjectReceived?.Invoke(this, obj); - } - - private void SendOrPostCallback(object asyncArgs) - { - var e = asyncArgs as AsyncCompletedEventArgs; - - var userObject = (T)e.UserState; - - ObjectReceived?.Invoke(this, userObject); - } - } -} diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index b173051a..558d22ec 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -96,7 +96,6 @@ namespace LibationWinForms public string Description { get; } public string DisplayTags => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated); public (LiberatedState, PdfState) Liberate => (LibraryCommands.Liberated_Status(Book), LibraryCommands.Pdf_Status(Book)); - #endregion #region Data Sorting diff --git a/LibationWinForms/SynchronizeInvoker.cs b/LibationWinForms/SynchronizeInvoker.cs new file mode 100644 index 00000000..0d1388ad --- /dev/null +++ b/LibationWinForms/SynchronizeInvoker.cs @@ -0,0 +1,143 @@ +using System; +using System.ComponentModel; +using System.Threading; + +namespace LibationWinForms +{ + public class SynchronizeInvoker : ISynchronizeInvoke + { + public bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; + private int InstanceThreadId { get; set; } = Thread.CurrentThread.ManagedThreadId; + private SynchronizationContext SyncContext { get; init; } = SynchronizationContext.Current; + + public SynchronizeInvoker() + { + SyncContext = SynchronizationContext.Current; + if (SyncContext is null) + throw new NullReferenceException($"Could not capture a current {nameof(SynchronizationContext)}"); + } + + public IAsyncResult BeginInvoke(Action action) => BeginInvoke(action, null); + public IAsyncResult BeginInvoke(Delegate method) => BeginInvoke(method, null); + public IAsyncResult BeginInvoke(Delegate method, object[] args) + { + var tme = new ThreadMethodEntry(method, args); + + if (InvokeRequired) + { + SyncContext.Post(ThreadMethodEntry.OnSendOrPostCallback, tme); + } + else + { + tme.Complete(); + tme.CompletedSynchronously = true; + } + return tme; + } + + public object EndInvoke(IAsyncResult result) + { + if (result is not ThreadMethodEntry crossThread) + throw new ArgumentException($"{nameof(result)} was not returned by {nameof(BeginInvoke)}"); + + if (!crossThread.IsCompleted) + crossThread.AsyncWaitHandle.WaitOne(); + + return crossThread.ReturnValue; + } + + public object Invoke(Action action) => Invoke(action, null); + public object Invoke(Delegate method) => Invoke(method, null); + public object Invoke(Delegate method, object[] args) + { + var tme = new ThreadMethodEntry(method, args); + + if (InvokeRequired) + { + SyncContext.Send(ThreadMethodEntry.OnSendOrPostCallback, tme); + } + else + { + tme.Complete(); + tme.CompletedSynchronously = true; + } + + return tme.ReturnValue; + } + + private class ThreadMethodEntry : IAsyncResult + { + public object AsyncState => null; + public bool CompletedSynchronously { get; internal set; } + public bool IsCompleted { get; private set; } + public object ReturnValue { get; private set; } + public WaitHandle AsyncWaitHandle + { + get + { + if (resetEvent == null) + { + lock (invokeSyncObject) + { + if (resetEvent == null) + { + resetEvent = new ManualResetEvent(initialState: false); + } + } + } + return resetEvent; + } + } + + private object invokeSyncObject = new object(); + private Delegate method; + private object[] args; + private ManualResetEvent resetEvent; + + public ThreadMethodEntry(Delegate method, object[] args) + { + this.method = method; + this.args = args; + resetEvent = new ManualResetEvent(initialState: false); + } + /// + /// This callback executes on the SynchronizationContext thread. + /// + public static void OnSendOrPostCallback(object asyncArgs) + { + var e = asyncArgs as ThreadMethodEntry; + + e.Complete(); + } + + public void Complete() + { + try + { + switch (method) + { + case Action actiton: + actiton(); + break; + default: + ReturnValue = method.DynamicInvoke(args); + break; + } + } + finally + { + IsCompleted = true; + resetEvent.Set(); + } + } + + ~ThreadMethodEntry() + { + if (resetEvent != null) + { + resetEvent.Close(); + } + } + } + } +} \ No newline at end of file From 7bdcf4eef0cfff781b9cee3a79c6c083dbe7120c Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Fri, 13 Aug 2021 22:53:17 -0600 Subject: [PATCH 58/64] Improved NetworkFileStream asynchronous operation. --- AaxDecrypter/AaxcDownloadConverter.cs | 1 - AaxDecrypter/NetworkFileStream.cs | 126 +++++++++++--------------- 2 files changed, 55 insertions(+), 72 deletions(-) diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs index 86794123..f9490ecc 100644 --- a/AaxDecrypter/AaxcDownloadConverter.cs +++ b/AaxDecrypter/AaxcDownloadConverter.cs @@ -112,7 +112,6 @@ namespace AaxDecrypter { nfsPersister = NewNetworkFilePersister(); } - nfsPersister.NetworkFileStream.BeginDownloading(); aaxFile = new AaxFile(nfsPersister.NetworkFileStream); diff --git a/AaxDecrypter/NetworkFileStream.cs b/AaxDecrypter/NetworkFileStream.cs index d3849992..eae48ccd 100644 --- a/AaxDecrypter/NetworkFileStream.cs +++ b/AaxDecrypter/NetworkFileStream.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Net; using System.Threading; -using System.Threading.Tasks; namespace AaxDecrypter { @@ -27,7 +26,7 @@ namespace AaxDecrypter public CookieCollection GetCookies() { - return base.GetCookies(Uri); + return GetCookies(Uri); } } @@ -79,15 +78,14 @@ namespace AaxDecrypter #endregion #region Private Properties - private HttpWebRequest HttpRequest { get; set; } private FileStream _writeFile { get; } private FileStream _readFile { get; } private Stream _networkStream { get; set; } private bool hasBegunDownloading { get; set; } private bool isCancelled { get; set; } - private bool finishedDownloading { get; set; } - private Action downloadThreadCompleteCallback { get; set; } + private EventWaitHandle downloadEnded { get; set; } + private EventWaitHandle downloadedPiece { get; set; } #endregion @@ -147,7 +145,7 @@ namespace AaxDecrypter private void Update() { RequestHeaders = HttpRequest.Headers; - Updated?.Invoke(this, new EventArgs()); + Updated?.Invoke(this, EventArgs.Empty); } /// @@ -160,8 +158,8 @@ namespace AaxDecrypter if (uriToSameFile.Host != Uri.Host) throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}"); - if (hasBegunDownloading && !finishedDownloading) - throw new Exception("Cannot change Uri during a download operation."); + if (hasBegunDownloading) + throw new InvalidOperationException("Cannot change Uri after download has started."); Uri = uriToSameFile; HttpRequest = WebRequest.CreateHttp(Uri); @@ -176,25 +174,27 @@ namespace AaxDecrypter /// /// Begins downloading to in a background thread. /// - public void BeginDownloading() + private void BeginDownloading() { + downloadEnded = new EventWaitHandle(false, EventResetMode.ManualReset); + if (ContentLength != 0 && WritePosition == ContentLength) { hasBegunDownloading = true; - finishedDownloading = true; + downloadEnded.Set(); return; } if (ContentLength != 0 && WritePosition > ContentLength) - throw new Exception($"Specified write position (0x{WritePosition:X10}) is larger than the file size."); + throw new WebException($"Specified write position (0x{WritePosition:X10}) is larger than {nameof(ContentLength)} (0x{ContentLength:X10})."); var response = HttpRequest.GetResponse() as HttpWebResponse; if (response.StatusCode != HttpStatusCode.PartialContent) - throw new Exception($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}."); + throw new WebException($"Server at {Uri.Host} responded with unexpected status code: {response.StatusCode}."); if (response.Headers.GetValues("Accept-Ranges").FirstOrDefault(r => r.EqualsInsensitive("bytes")) is null) - throw new Exception($"Server at {Uri.Host} does not support Http ranges"); + throw new WebException($"Server at {Uri.Host} does not support Http ranges"); //Content length is the length of the range request, and it is only equal //to the complete file length if requesting Range: bytes=0- @@ -202,10 +202,12 @@ namespace AaxDecrypter ContentLength = response.ContentLength; _networkStream = response.GetResponseStream(); + downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset); //Download the file in the background. - Thread downloadThread = new Thread(() => DownloadFile()) { IsBackground = true }; - downloadThread.Start(); + new Thread(() => DownloadFile()) + { IsBackground = true } + .Start(); hasBegunDownloading = true; return; @@ -216,13 +218,13 @@ namespace AaxDecrypter /// private void DownloadFile() { - long downloadPosition = WritePosition; - long nextFlush = downloadPosition + DATA_FLUSH_SZ; + var downloadPosition = WritePosition; + var nextFlush = downloadPosition + DATA_FLUSH_SZ; - byte[] buff = new byte[DOWNLOAD_BUFF_SZ]; + var buff = new byte[DOWNLOAD_BUFF_SZ]; do { - int bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); + var bytesRead = _networkStream.Read(buff, 0, DOWNLOAD_BUFF_SZ); _writeFile.Write(buff, 0, bytesRead); downloadPosition += bytesRead; @@ -233,6 +235,7 @@ namespace AaxDecrypter WritePosition = downloadPosition; Update(); nextFlush = downloadPosition + DATA_FLUSH_SZ; + downloadedPiece.Set(); } } while (downloadPosition < ContentLength && !isCancelled); @@ -243,13 +246,12 @@ namespace AaxDecrypter _networkStream.Close(); if (!isCancelled && WritePosition < ContentLength) - throw new Exception("File download ended before finishing."); + throw new WebException($"Downloaded size (0x{WritePosition:X10}) is less than {nameof(ContentLength)} (0x{ContentLength:X10})."); if (WritePosition > ContentLength) - throw new Exception("Downloaded file is larger than expected."); + throw new WebException($"Downloaded size (0x{WritePosition:X10}) is greater than {nameof(ContentLength)} (0x{ContentLength:X10})."); - finishedDownloading = true; - downloadThreadCompleteCallback?.Invoke(); + downloadEnded.Set(); } #endregion @@ -330,9 +332,7 @@ namespace AaxDecrypter var result = new WebHeaderCollection(); foreach (var kvp in jObj) - { result.Add(kvp.Key, kvp.Value.Value()); - } return result; } @@ -341,8 +341,8 @@ namespace AaxDecrypter public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - JObject jObj = new JObject(); - Type type = value.GetType(); + var jObj = new JObject(); + var type = value.GetType(); var headers = value as WebHeaderCollection; var jHeaders = headers.AllKeys.Select(k => new JProperty(k, headers[k])); jObj.Add(jHeaders); @@ -364,13 +364,21 @@ namespace AaxDecrypter public override bool CanWrite => false; [JsonIgnore] - public override long Length => ContentLength; + public override long Length + { + get + { + if (!hasBegunDownloading) + BeginDownloading(); + return ContentLength; + } + } [JsonIgnore] public override long Position { get => _readFile.Position; set => Seek(value, SeekOrigin.Begin); } [JsonIgnore] - public override bool CanTimeout => base.CanTimeout; + public override bool CanTimeout => false; [JsonIgnore] public override int ReadTimeout { get => base.ReadTimeout; set => base.ReadTimeout = value; } @@ -387,69 +395,45 @@ namespace AaxDecrypter if (!hasBegunDownloading) BeginDownloading(); - long toRead = Math.Min(count, Length - Position); - long requiredPosition = Position + toRead; - - //read operation will block until file contains enough data - //to fulfil the request, or until cancelled. - while (requiredPosition > WritePosition && !isCancelled) - Thread.Sleep(2); - + var toRead = Math.Min(count, Length - Position); + WaitToPosition(Position + toRead); return _readFile.Read(buffer, offset, count); } public override long Seek(long offset, SeekOrigin origin) { - long newPosition; + var newPosition = origin switch + { + SeekOrigin.Current => Position + offset, + SeekOrigin.End => ContentLength + offset, + _ => offset, + }; - switch (origin) - { - case SeekOrigin.Current: - newPosition = Position + offset; - break; - case SeekOrigin.End: - newPosition = ContentLength + offset; - break; - default: - newPosition = offset; - break; - } - ReadToPosition(newPosition); - - _readFile.Position = newPosition; - return newPosition; + WaitToPosition(newPosition); + return _readFile.Position = newPosition; } /// - /// Ensures that the file has downloaded to at least , then returns. + /// Blocks until the file has downloaded to at least , then returns. /// - /// The minimum required data length in . - private void ReadToPosition(long neededPosition) - { - byte[] buff = new byte[DOWNLOAD_BUFF_SZ]; - do - { - Read(buff, 0, DOWNLOAD_BUFF_SZ); - } while (neededPosition > WritePosition); + /// The minimum required flished data length in . + private void WaitToPosition(long requiredPosition) + { + while (requiredPosition > WritePosition && !isCancelled && hasBegunDownloading && !downloadedPiece.WaitOne(1000)) ; } + public override void Close() { isCancelled = true; - downloadThreadCompleteCallback = CloseAction; - //ensure that close will run even if called after callback was fired. - if (finishedDownloading) - CloseAction(); + while (downloadEnded is not null && !downloadEnded.WaitOne(1000)) ; - } - private void CloseAction() - { _readFile.Close(); _writeFile.Close(); _networkStream?.Close(); Update(); } - + #endregion } } From 52fb0a27ce9ed1d6fa3056780f93019d43599a73 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Fri, 13 Aug 2021 23:18:52 -0600 Subject: [PATCH 59/64] Code cleanup. --- AaxDecrypter/NetworkFileStream.cs | 7 +++- LibationWinForms/SynchronizeInvoker.cs | 57 ++++++++------------------ 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/AaxDecrypter/NetworkFileStream.cs b/AaxDecrypter/NetworkFileStream.cs index eae48ccd..5d7544b5 100644 --- a/AaxDecrypter/NetworkFileStream.cs +++ b/AaxDecrypter/NetworkFileStream.cs @@ -433,7 +433,12 @@ namespace AaxDecrypter _networkStream?.Close(); Update(); } - + #endregion + ~NetworkFileStream() + { + downloadEnded?.Close(); + downloadedPiece?.Close(); + } } } diff --git a/LibationWinForms/SynchronizeInvoker.cs b/LibationWinForms/SynchronizeInvoker.cs index 0d1388ad..c38c0499 100644 --- a/LibationWinForms/SynchronizeInvoker.cs +++ b/LibationWinForms/SynchronizeInvoker.cs @@ -8,11 +8,10 @@ namespace LibationWinForms { public bool InvokeRequired => Thread.CurrentThread.ManagedThreadId != InstanceThreadId; private int InstanceThreadId { get; set; } = Thread.CurrentThread.ManagedThreadId; - private SynchronizationContext SyncContext { get; init; } = SynchronizationContext.Current; + private SynchronizationContext SyncContext { get; } = SynchronizationContext.Current; public SynchronizeInvoker() { - SyncContext = SynchronizationContext.Current; if (SyncContext is null) throw new NullReferenceException($"Could not capture a current {nameof(SynchronizationContext)}"); } @@ -25,7 +24,7 @@ namespace LibationWinForms if (InvokeRequired) { - SyncContext.Post(ThreadMethodEntry.OnSendOrPostCallback, tme); + SyncContext.Post(OnSendOrPostCallback, tme); } else { @@ -38,7 +37,7 @@ namespace LibationWinForms public object EndInvoke(IAsyncResult result) { if (result is not ThreadMethodEntry crossThread) - throw new ArgumentException($"{nameof(result)} was not returned by {nameof(BeginInvoke)}"); + throw new ArgumentException($"{nameof(result)} was not returned by {nameof(SynchronizeInvoker)}.{nameof(BeginInvoke)}"); if (!crossThread.IsCompleted) crossThread.AsyncWaitHandle.WaitOne(); @@ -54,7 +53,7 @@ namespace LibationWinForms if (InvokeRequired) { - SyncContext.Send(ThreadMethodEntry.OnSendOrPostCallback, tme); + SyncContext.Send(OnSendOrPostCallback, tme); } else { @@ -65,49 +64,32 @@ namespace LibationWinForms return tme.ReturnValue; } + /// + /// This callback executes on the SynchronizationContext thread. + /// + private static void OnSendOrPostCallback(object asyncArgs) + { + var e = asyncArgs as ThreadMethodEntry; + e.Complete(); + } + private class ThreadMethodEntry : IAsyncResult { public object AsyncState => null; public bool CompletedSynchronously { get; internal set; } public bool IsCompleted { get; private set; } public object ReturnValue { get; private set; } - public WaitHandle AsyncWaitHandle - { - get - { - if (resetEvent == null) - { - lock (invokeSyncObject) - { - if (resetEvent == null) - { - resetEvent = new ManualResetEvent(initialState: false); - } - } - } - return resetEvent; - } - } + public WaitHandle AsyncWaitHandle => completedEvent; - private object invokeSyncObject = new object(); private Delegate method; private object[] args; - private ManualResetEvent resetEvent; + private ManualResetEvent completedEvent; public ThreadMethodEntry(Delegate method, object[] args) { this.method = method; this.args = args; - resetEvent = new ManualResetEvent(initialState: false); - } - /// - /// This callback executes on the SynchronizationContext thread. - /// - public static void OnSendOrPostCallback(object asyncArgs) - { - var e = asyncArgs as ThreadMethodEntry; - - e.Complete(); + completedEvent = new ManualResetEvent(initialState: false); } public void Complete() @@ -127,16 +109,13 @@ namespace LibationWinForms finally { IsCompleted = true; - resetEvent.Set(); + completedEvent.Set(); } } ~ThreadMethodEntry() { - if (resetEvent != null) - { - resetEvent.Close(); - } + completedEvent.Close(); } } } From f74b0d78dbff999f572069318cec59bf31e33d9e Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 Aug 2021 00:25:32 -0600 Subject: [PATCH 60/64] Improved BackgroundFileSystem thread safety --- FileManager/AudibleFileStorage.cs | 13 ++- FileManager/BackgroundFileSystem.cs | 110 ++++++++++-------- .../EditTagsDataGridViewImageButtonColumn.cs | 2 - 3 files changed, 73 insertions(+), 52 deletions(-) diff --git a/FileManager/AudibleFileStorage.cs b/FileManager/AudibleFileStorage.cs index b2ee12a5..fdbf1a77 100644 --- a/FileManager/AudibleFileStorage.cs +++ b/FileManager/AudibleFileStorage.cs @@ -34,7 +34,7 @@ namespace FileManager } } - private static BackgroundFileSystem BookDirectoryFiles { get; } = new BackgroundFileSystem(); + private static BackgroundFileSystem BookDirectoryFiles { get; set; } #endregion #region instance @@ -47,6 +47,7 @@ namespace FileManager { extensions_noDots = Extensions.Select(ext => ext.Trim('.')).ToList(); extAggr = extensions_noDots.Aggregate((a, b) => $"{a}|{b}"); + BookDirectoryFiles ??= new BackgroundFileSystem(BookDirectoryFiles, "*.*", SearchOption.AllDirectories); } public void Refresh() @@ -76,7 +77,15 @@ namespace FileManager { //If user changed the BooksDirectory, reinitialize. if (storageDir != BookDirectoryFiles.RootDirectory) - BookDirectoryFiles.Init(storageDir, "*.*", SearchOption.AllDirectories); + { + lock (BookDirectoryFiles) + { + if (storageDir != BookDirectoryFiles.RootDirectory) + { + BookDirectoryFiles = new BackgroundFileSystem(storageDir, "*.*", SearchOption.AllDirectories); + } + } + } firstOrNull = BookDirectoryFiles.FindFile(regexPattern, RegexOptions.IgnoreCase); } diff --git a/FileManager/BackgroundFileSystem.cs b/FileManager/BackgroundFileSystem.cs index a239581c..1ed670ac 100644 --- a/FileManager/BackgroundFileSystem.cs +++ b/FileManager/BackgroundFileSystem.cs @@ -18,12 +18,19 @@ namespace FileManager private FileSystemWatcher fileSystemWatcher { get; set; } private BlockingCollection directoryChangesEvents { get; set; } private Task backgroundScanner { get; set; } - private List fsCache { get; set; } + private List fsCache { get; } = new(); + + public BackgroundFileSystem(string rootDirectory, string searchPattern, SearchOption searchOptions) + { + RootDirectory = rootDirectory; + SearchPattern = searchPattern; + SearchOption = searchOptions; + + Init(); + } public string FindFile(string regexPattern, RegexOptions options) { - if (fsCache is null) return null; - lock (fsCache) { return fsCache.FirstOrDefault(s => Regex.IsMatch(s, regexPattern, options)); @@ -32,8 +39,6 @@ namespace FileManager public void RefreshFiles() { - if (fsCache is null) return; - lock (fsCache) { fsCache.Clear(); @@ -41,19 +46,12 @@ namespace FileManager } } - public void Init(string rootDirectory, string searchPattern, SearchOption searchOptions) + private void Init() { - RootDirectory = rootDirectory; - SearchPattern = searchPattern; - SearchOption = searchOptions; + Stop(); - //Calling CompleteAdding() will cause background scanner to terminate. - directoryChangesEvents?.CompleteAdding(); - fsCache?.Clear(); - directoryChangesEvents?.Dispose(); - fileSystemWatcher?.Dispose(); - - fsCache = Directory.EnumerateFiles(RootDirectory, SearchPattern, SearchOption).ToList(); + lock (fsCache) + fsCache.AddRange(Directory.EnumerateFiles(RootDirectory, SearchPattern, SearchOption)); directoryChangesEvents = new BlockingCollection(); fileSystemWatcher = new FileSystemWatcher(RootDirectory); @@ -64,28 +62,31 @@ namespace FileManager fileSystemWatcher.IncludeSubdirectories = true; fileSystemWatcher.EnableRaisingEvents = true; - //Wait for background scanner to terminate before reinitializing. - backgroundScanner?.Wait(); backgroundScanner = new Task(BackgroundScanner); backgroundScanner.Start(); } + private void Stop() + { + //Stop raising events + fileSystemWatcher?.Dispose(); - private void AddUniqueFiles(IEnumerable newFiles) - { - foreach (var file in newFiles) - { - AddUniqueFile(file); - } - } - private void AddUniqueFile(string newFile) - { - if (!fsCache.Contains(newFile)) - fsCache.Add(newFile); + //Calling CompleteAdding() will cause background scanner to terminate. + directoryChangesEvents?.CompleteAdding(); + + //Wait for background scanner to terminate before reinitializing. + backgroundScanner?.Wait(); + + //Dispose of directoryChangesEvents after backgroundScanner exists. + directoryChangesEvents?.Dispose(); + + lock (fsCache) + fsCache.Clear(); } private void FileSystemWatcher_Error(object sender, ErrorEventArgs e) { - Init(RootDirectory, SearchPattern, SearchOption); + Stop(); + Init(); } private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e) @@ -97,28 +98,26 @@ namespace FileManager private void BackgroundScanner() { while (directoryChangesEvents.TryTake(out FileSystemEventArgs change, -1)) - UpdateLocalCache(change); + { + lock (fsCache) + UpdateLocalCache(change); + } } - private void UpdateLocalCache(FileSystemEventArgs change) + private void UpdateLocalCache(FileSystemEventArgs change) { - lock (fsCache) + if (change.ChangeType == WatcherChangeTypes.Deleted) { - if (change.ChangeType == WatcherChangeTypes.Deleted) - { - RemovePath(change.FullPath); - } - else if (change.ChangeType == WatcherChangeTypes.Created) - { - AddPath(change.FullPath); - } - else if (change.ChangeType == WatcherChangeTypes.Renamed) - { - var renameChange = change as RenamedEventArgs; - - RemovePath(renameChange.OldFullPath); - AddPath(renameChange.FullPath); - } + RemovePath(change.FullPath); + } + else if (change.ChangeType == WatcherChangeTypes.Created) + { + AddPath(change.FullPath); + } + else if (change.ChangeType == WatcherChangeTypes.Renamed && change is RenamedEventArgs renameChange) + { + RemovePath(renameChange.OldFullPath); + AddPath(renameChange.FullPath); } } @@ -137,6 +136,21 @@ namespace FileManager else AddUniqueFile(path); } + private void AddUniqueFiles(IEnumerable newFiles) + { + foreach (var file in newFiles) + { + AddUniqueFile(file); + } + } + private void AddUniqueFile(string newFile) + { + if (!fsCache.Contains(newFile)) + fsCache.Add(newFile); + } + #endregion + + ~BackgroundFileSystem() => Stop(); } } diff --git a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs index 1f4cf04d..e736b938 100644 --- a/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs +++ b/LibationWinForms/EditTagsDataGridViewImageButtonColumn.cs @@ -23,8 +23,6 @@ namespace LibationWinForms if (DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor != foreColor) { DataGridView.Rows[RowIndex].DefaultCellStyle.ForeColor = foreColor; - - DataGridView.InvalidateRow(RowIndex); } if (tagsString.Length == 0) From 7a90d9fba9fd54ed885cd2d0f932b8b295ed508c Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sat, 14 Aug 2021 00:31:16 -0600 Subject: [PATCH 61/64] Typo --- FileManager/AudibleFileStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FileManager/AudibleFileStorage.cs b/FileManager/AudibleFileStorage.cs index fdbf1a77..6af95a17 100644 --- a/FileManager/AudibleFileStorage.cs +++ b/FileManager/AudibleFileStorage.cs @@ -47,7 +47,7 @@ namespace FileManager { extensions_noDots = Extensions.Select(ext => ext.Trim('.')).ToList(); extAggr = extensions_noDots.Aggregate((a, b) => $"{a}|{b}"); - BookDirectoryFiles ??= new BackgroundFileSystem(BookDirectoryFiles, "*.*", SearchOption.AllDirectories); + BookDirectoryFiles ??= new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories); } public void Refresh() From 27ae5facbe5bd5383fbe104dd9369b4a7fd67a13 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Sun, 15 Aug 2021 13:39:06 -0600 Subject: [PATCH 62/64] Improved PictureStorage thread safety and more intuitive naming. --- FileManager/PictureStorage.cs | 61 ++++++++++--------- LibationWinForms/GridEntry.cs | 2 +- ...mberComparable.cs => IMemberComparable.cs} | 2 +- ...MemberComparer[T].cs => MemberComparer.cs} | 2 +- LibationWinForms/SortableBindingList2[T].cs | 4 +- 5 files changed, 38 insertions(+), 33 deletions(-) rename LibationWinForms/{IObjectMemberComparable.cs => IMemberComparable.cs} (79%) rename LibationWinForms/{ObjectMemberComparer[T].cs => MemberComparer.cs} (85%) diff --git a/FileManager/PictureStorage.cs b/FileManager/PictureStorage.cs index f9f5a35d..8c143dcf 100644 --- a/FileManager/PictureStorage.cs +++ b/FileManager/PictureStorage.cs @@ -34,12 +34,10 @@ namespace FileManager private static string getPath(PictureDefinition def) => Path.Combine(ImagesDirectory, $"{def.PictureId}{def.Size}.jpg"); - static Task backgroundDownloader; - static PictureStorage() { - backgroundDownloader = new Task(BackgroundDownloader); - backgroundDownloader.Start(); + new Task(BackgroundDownloader, TaskCreationOptions.LongRunning) + .Start(); } public static event EventHandler PictureCached; @@ -49,39 +47,45 @@ namespace FileManager private static Dictionary defaultImages { get; } = new Dictionary(); public static (bool isDefault, byte[] bytes) GetPicture(PictureDefinition def) { - if (cache.ContainsKey(def)) - return (false, cache[def]); - - var path = getPath(def); - - if (File.Exists(path)) + lock (cache) { - cache[def] = File.ReadAllBytes(path); - return (false, cache[def]); - } + if (cache.ContainsKey(def)) + return (false, cache[def]); - DownloadQueue.Add(def); - return (true, getDefaultImage(def.Size)); + var path = getPath(def); + + if (File.Exists(path)) + { + cache[def] = File.ReadAllBytes(path); + return (false, cache[def]); + } + + DownloadQueue.Add(def); + return (true, getDefaultImage(def.Size)); + } } public static byte[] GetPictureSynchronously(PictureDefinition def) { - if (!cache.ContainsKey(def) || cache[def] == null) + lock (cache) { - var path = getPath(def); - byte[] bytes; - - if (File.Exists(path)) - bytes = File.ReadAllBytes(path); - else + if (!cache.ContainsKey(def) || cache[def] == null) { - bytes = downloadBytes(def); - saveFile(def, bytes); - } + var path = getPath(def); + byte[] bytes; - cache[def] = bytes; + if (File.Exists(path)) + bytes = File.ReadAllBytes(path); + else + { + bytes = downloadBytes(def); + saveFile(def, bytes); + } + + cache[def] = bytes; + } + return cache[def]; } - return cache[def]; } public static void SetDefaultImage(PictureSize pictureSize, byte[] bytes) @@ -100,7 +104,8 @@ namespace FileManager var bytes = downloadBytes(def); saveFile(def, bytes); - cache[def] = bytes; + lock (cache) + cache[def] = bytes; PictureCached?.Invoke(nameof(PictureStorage), new PictureCachedEventArgs { Definition = def, Picture = bytes }); } diff --git a/LibationWinForms/GridEntry.cs b/LibationWinForms/GridEntry.cs index 558d22ec..032828b9 100644 --- a/LibationWinForms/GridEntry.cs +++ b/LibationWinForms/GridEntry.cs @@ -10,7 +10,7 @@ using System.Linq; namespace LibationWinForms { - internal class GridEntry : AsyncNotifyPropertyChanged, IObjectMemberComparable + internal class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable { #region implementation properties // hide from public fields from Data Source GUI with [Browsable(false)] diff --git a/LibationWinForms/IObjectMemberComparable.cs b/LibationWinForms/IMemberComparable.cs similarity index 79% rename from LibationWinForms/IObjectMemberComparable.cs rename to LibationWinForms/IMemberComparable.cs index bfacb0cd..bbc6471e 100644 --- a/LibationWinForms/IObjectMemberComparable.cs +++ b/LibationWinForms/IMemberComparable.cs @@ -3,7 +3,7 @@ using System.Collections; namespace LibationWinForms { - internal interface IObjectMemberComparable + internal interface IMemberComparable { IComparer GetMemberComparer(Type memberType); object GetMemberValue(string memberName); diff --git a/LibationWinForms/ObjectMemberComparer[T].cs b/LibationWinForms/MemberComparer.cs similarity index 85% rename from LibationWinForms/ObjectMemberComparer[T].cs rename to LibationWinForms/MemberComparer.cs index 3024477e..1bca2c82 100644 --- a/LibationWinForms/ObjectMemberComparer[T].cs +++ b/LibationWinForms/MemberComparer.cs @@ -3,7 +3,7 @@ using System.ComponentModel; namespace LibationWinForms { - internal class ObjectMemberComparer : IComparer where T : IObjectMemberComparable + internal class MemberComparer : IComparer where T : IMemberComparable { public ListSortDirection Direction { get; set; } = ListSortDirection.Ascending; public string PropertyName { get; set; } diff --git a/LibationWinForms/SortableBindingList2[T].cs b/LibationWinForms/SortableBindingList2[T].cs index 23f9a6ad..c29d1f65 100644 --- a/LibationWinForms/SortableBindingList2[T].cs +++ b/LibationWinForms/SortableBindingList2[T].cs @@ -5,7 +5,7 @@ using System.ComponentModel; namespace LibationWinForms { - internal class SortableBindingList2 : BindingList where T : IObjectMemberComparable + internal class SortableBindingList2 : BindingList where T : IMemberComparable { private bool isSorted; private ListSortDirection listSortDirection; @@ -14,7 +14,7 @@ namespace LibationWinForms public SortableBindingList2() : base(new List()) { } public SortableBindingList2(IEnumerable enumeration) : base(new List(enumeration)) { } - private ObjectMemberComparer Comparer { get; } = new(); + private MemberComparer Comparer { get; } = new(); protected override bool SupportsSortingCore => true; protected override bool SupportsSearchingCore => true; protected override bool IsSortedCore => isSorted; From 560880b53dcfbb1bb581967a7b459bbbbf290195 Mon Sep 17 00:00:00 2001 From: Michael Bucari-Tovo Date: Mon, 16 Aug 2021 08:04:03 -0600 Subject: [PATCH 63/64] Fixed typos. --- .../BookLiberation/ProcessorAutomationController.cs | 6 +++--- LibationWinForms/MessageBoxWarnIfVerboseLogging.cs | 2 +- LibationWinForms/ProductsGrid.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs index 736de469..f30618ae 100644 --- a/LibationWinForms/BookLiberation/ProcessorAutomationController.cs +++ b/LibationWinForms/BookLiberation/ProcessorAutomationController.cs @@ -135,11 +135,11 @@ namespace LibationWinForms.BookLiberation /// /// Create a new and links it to a new . /// - /// The derrived type to create. - /// The derrived Form to create on , Show on , Close on , and Dispose on + /// The derived type to create. + /// The derived Form to create on , Show on , Close on , and Dispose on /// The logger /// An additional event handler to handle - /// A new of type + /// A new of type private static TProcessable CreateProcessable(LogMe logMe, EventHandler completedAction = null) where TForm : LiberationBaseForm, new() where TProcessable : IProcessable, new() diff --git a/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs b/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs index 5e0462bc..0f47ef3c 100644 --- a/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs +++ b/LibationWinForms/MessageBoxWarnIfVerboseLogging.cs @@ -17,7 +17,7 @@ Warning: verbose logging is enabled. This should be used for debugging only. It creates many more logs and debug files, neither of which are as -strictly anonomous. +strictly anonymous. When you are finished debugging, it's highly recommended to set your debug MinimumLevel to Information and restart diff --git a/LibationWinForms/ProductsGrid.cs b/LibationWinForms/ProductsGrid.cs index 4afe1f64..330e7ef1 100644 --- a/LibationWinForms/ProductsGrid.cs +++ b/LibationWinForms/ProductsGrid.cs @@ -21,7 +21,7 @@ namespace LibationWinForms // AudibleDTO // GridEntry // - go to Design view - // - click on Data Sources > ProductItem. drowdown: DataGridView + // - click on Data Sources > ProductItem. dropdown: DataGridView // - drag/drop ProductItem on design surface // AS OF AUGUST 2021 THIS DOES NOT WORK IN VS2019 WITH .NET-5 PROJECTS From 88253cdb559c9520c5057ed0ccfe8128fb0e366b Mon Sep 17 00:00:00 2001 From: Mbucari <37587114+Mbucari@users.noreply.github.com> Date: Mon, 16 Aug 2021 10:08:21 -0600 Subject: [PATCH 64/64] Update LiberationBaseForm.cs --- .../BookLiberation/BaseForms/LiberationBaseForm.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs index b7f48d94..b69fa90d 100644 --- a/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs +++ b/LibationWinForms/BookLiberation/BaseForms/LiberationBaseForm.cs @@ -17,13 +17,12 @@ namespace LibationWinForms.BookLiberation.BaseForms { //SynchronizationContext.Current will be null until the process contains a Form. //If this is the first form created, it will not exist until after execution - //reaches inside the constructor. So need to reset the context here. + //reaches inside the constructor (after base class has been initialized). Invoker = new SynchronizeInvoker(); } public void RegisterFileLiberator(IStreamable streamable, LogMe logMe = null) { - //IFileLiberator must at least be IStreamable, otherwise the Form won't ever Show() if (streamable is null) return; Streamable = streamable;