Compare commits

...

4 Commits

14 changed files with 148 additions and 79 deletions

View File

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

View File

@@ -167,8 +167,8 @@ namespace ApplicationServices
{
logTime("importIntoDbAsync -- pre db");
using var context = DbContexts.GetContext();
var libraryImporter = new LibraryBookImporter(context);
var newCount = await Task.Run(() => libraryImporter.Import(importItems));
var libraryBookImporter = new LibraryBookImporter(context);
var newCount = await Task.Run(() => libraryBookImporter.Import(importItems));
logTime("importIntoDbAsync -- post Import()");
var qtyChanges = context.SaveChanges();
logTime("importIntoDbAsync -- post SaveChanges");

View File

@@ -166,8 +166,7 @@ namespace AudibleUtilities
Serilog.Log.Logger.Information($"{parents.Count} series of shows/podcasts found");
// remove episode parents. even if the following stuff fails, these will still be removed from the collection.
// also must happen before processing children because children abuses this flag
// remove episode parents. even if the following stuff fails, these will still be removed from the collection
items.RemoveAll(i => i.IsEpisodes);
if (importEpisodes)
@@ -192,6 +191,14 @@ namespace AudibleUtilities
{
var children = await getEpisodeChildrenAsync(parent);
// actual individual episode, not the parent of a series.
// for now I'm keeping it inside this method since it fits the work flow, incl. importEpisodes logic
if (!children.Any())
{
results.Add(parent);
continue;
}
foreach (var child in children)
{
// use parent's 'DateAdded'. DateAdded is just a convenience prop for: PurchaseDate.UtcDateTime

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AudibleApi" Version="2.3.4.1" />
<PackageReference Include="AudibleApi" Version="2.3.5.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -5,7 +5,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core" Version="3.0.1.1" />
<PackageReference Include="Dinah.Core" Version="3.0.2.1" />
<PackageReference Include="Polly" Version="7.2.2" />
</ItemGroup>

View File

@@ -30,8 +30,7 @@ namespace FileManager
if (IsReadOnly)
return;
File.WriteAllText(Filepath, "{}");
System.Threading.Thread.Sleep(100);
createNewFile();
}
public string GetString(string propertyName)
@@ -214,6 +213,13 @@ namespace FileManager
}
var settingsJsonContents = File.ReadAllText(Filepath);
if (string.IsNullOrWhiteSpace(settingsJsonContents))
{
createNewFile();
settingsJsonContents = File.ReadAllText(Filepath);
}
var jObject = JsonConvert.DeserializeObject<JObject>(settingsJsonContents);
if (jObject is null)
@@ -226,5 +232,11 @@ namespace FileManager
return jObject;
}
private void createNewFile()
{
File.WriteAllText(Filepath, "{}");
System.Threading.Thread.Sleep(100);
}
}
}

View File

@@ -21,8 +21,8 @@ namespace LibationFileManager
// putting these first is the incredibly lazy way to make them show up first in the EditTemplateDialog
public static TemplateTags ChCount { get; } = new TemplateTags("ch count", "Number of chapters", true);
public static TemplateTags ChTitle { get; } = new TemplateTags("ch title", "Chapter title", true);
public static TemplateTags ChNumber { get; } = new TemplateTags("ch#", "Chapter number", true);
public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter number with leading zeros", true);
public static TemplateTags ChNumber { get; } = new TemplateTags("ch#", "Chapter #", true);
public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter # with leading zeros", true);
public static TemplateTags Id { get; } = new TemplateTags("id", "Audible ID");
public static TemplateTags Title { get; } = new TemplateTags("title", "Full title");
@@ -36,5 +36,9 @@ namespace LibationFileManager
public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series");
public static TemplateTags Account { get; } = new TemplateTags("account", "Audible account of this book");
public static TemplateTags Locale { get; } = new TemplateTags("locale", "Region/country");
// Special case. Isn't mapped to a replacement in Templates.cs
// Included here for display by EditTemplateDialog
public static TemplateTags IfSeries { get; } = new TemplateTags("if series->...<-if series", "Only include if part of a series");
}
}

View File

@@ -1,9 +1,10 @@
using Dinah.Core;
using FileManager;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Dinah.Core;
using FileManager;
namespace LibationFileManager
{
@@ -106,12 +107,21 @@ namespace LibationFileManager
: getFileNamingTemplate(libraryBookDto, template, null, null)
.GetFilePath();
private static Regex ifSeriesRegex { get; } = new Regex("<if series->(.*?)<-if series>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static FileNamingTemplate getFileNamingTemplate(LibraryBookDto libraryBookDto, string template, string dirFullPath, string extension)
{
ArgumentValidator.EnsureNotNullOrWhiteSpace(template, nameof(template));
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
dirFullPath = dirFullPath?.Trim() ?? "";
// for non-series, remove <if series-> and <-if series> tags and everything in between
// for series, remove <if series-> and <-if series> tags, what's in between will remain
template = ifSeriesRegex.Replace(
template,
string.IsNullOrWhiteSpace(libraryBookDto.SeriesName) ? "" : "$1");
var t = template + FileUtility.GetStandardizedExtension(extension);
var fullfilename = dirFullPath == "" ? t : Path.Combine(dirFullPath, t);

View File

@@ -112,6 +112,9 @@ namespace LibationWinForms.Dialogs
{
try
{
if (!inputIsValid())
return;
// without transaction, accounts persister will write ANY EDIT immediately to file
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
@@ -129,6 +132,28 @@ namespace LibationWinForms.Dialogs
}
}
private bool inputIsValid()
{
var dtos = getRowDtos();
foreach (var dto in dtos)
{
if (string.IsNullOrWhiteSpace(dto.AccountId))
{
MessageBox.Show("Account id cannot be blank. Please enter an account id for all accounts.", "Blank account", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if (string.IsNullOrWhiteSpace(dto.LocaleName))
{
MessageBox.Show("Please select a locale (i.e.: country or region) for all accounts.", "Blank region", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
return true;
}
private void persist(AccountsSettings accountsSettings)
{
var existingAccounts = accountsSettings.Accounts;
@@ -152,11 +177,6 @@ namespace LibationWinForms.Dialogs
// upsert each. validation occurs through Account and AccountsSettings
foreach (var dto in dtos)
{
if (string.IsNullOrWhiteSpace(dto.AccountId))
throw new Exception("Please enter an account id for all accounts");
if (string.IsNullOrWhiteSpace(dto.LocaleName))
throw new Exception("Please select a locale (i.e.: country or region) for all accounts");
var acct = accountsSettings.Upsert(dto.AccountId, dto.LocaleName);
acct.LibraryScan = dto.LibraryScan;
acct.AccountName

View File

@@ -114,12 +114,12 @@
// columnHeader1
//
this.columnHeader1.Text = "Tag";
this.columnHeader1.Width = 90;
this.columnHeader1.Width = 137;
//
// columnHeader2
//
this.columnHeader2.Text = "Description";
this.columnHeader2.Width = 230;
this.columnHeader2.Width = 170;
//
// richTextBox1
//
@@ -137,14 +137,13 @@
// warningsLbl
//
this.warningsLbl.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
this.warningsLbl.AutoSize = true;
this.warningsLbl.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
this.warningsLbl.ForeColor = System.Drawing.Color.Firebrick;
this.warningsLbl.Location = new System.Drawing.Point(346, 262);
this.warningsLbl.Name = "warningsLbl";
this.warningsLbl.Size = new System.Drawing.Size(14, 15);
this.warningsLbl.TabIndex = 100;
this.warningsLbl.Text = "6";
this.warningsLbl.Size = new System.Drawing.Size(574, 77);
this.warningsLbl.TabIndex = 6;
this.warningsLbl.Text = "[warnings]";
//
// exampleLbl
//

View File

@@ -18,7 +18,7 @@ namespace LibationWinForms
/// </summary>
internal class GridEntry : AsyncNotifyPropertyChanged, IMemberComparable
{
#region implementation properties
#region implementation properties NOT exposed to the view
// hide from public fields from Data Source GUI with [Browsable(false)]
[Browsable(false)]
@@ -28,10 +28,52 @@ namespace LibationWinForms
#endregion
#region Model properties exposed to the view
private Image _cover;
public Image Cover
{
get => _cover;
private set
{
_cover = value;
NotifyPropertyChanged();
}
}
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
{
get => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
set => Book.UserDefinedItem.Tags = value;
}
// these 2 values being in 1 field is the trick behind getting the liberated+pdf 'stoplight' icon to draw. See: LiberateDataGridViewImageButtonCell.Paint
public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate
{
get => (LibraryCommands.Liberated_Status(LibraryBook.Book), LibraryCommands.Pdf_Status(LibraryBook.Book));
set
{
LibraryBook.Book.UserDefinedItem.BookStatus = value.BookStatus;
LibraryBook.Book.UserDefinedItem.PdfStatus = value.PdfStatus;
}
}
#endregion
public event EventHandler Committed;
private Book Book => LibraryBook.Book;
private Image _cover;
public GridEntry(LibraryBook libraryBook)
{
@@ -145,53 +187,13 @@ namespace LibationWinForms
Committed?.Invoke(this, null);
}
#endregion
#region Model properties exposed to the view
public Image Cover
{
get
{
return _cover;
}
private set
{
_cover = value;
NotifyPropertyChanged();
}
}
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
{
get => string.Join("\r\n", Book.UserDefinedItem.TagsEnumerated);
set => Book.UserDefinedItem.Tags = value;
}
// these 2 values being in 1 field is the trick behind getting the liberated+pdf 'stoplight' icon to draw. See: LiberateDataGridViewImageButtonCell.Paint
public (LiberatedStatus BookStatus, LiberatedStatus? PdfStatus) Liberate
{
get => (LibraryCommands.Liberated_Status(LibraryBook.Book), LibraryCommands.Pdf_Status(LibraryBook.Book));
set
{
LibraryBook.Book.UserDefinedItem.BookStatus = value.BookStatus;
LibraryBook.Book.UserDefinedItem.PdfStatus = value.PdfStatus;
}
}
#endregion
#region Data Sorting
// These methods are implementation of Dinah.Core.DataBinding.IMemberComparable
// Used by Dinah.Core.DataBinding.SortableBindingList<T> for all sorting
public virtual object GetMemberValue(string memberName) => _memberValues[memberName]();
public virtual IComparer GetMemberComparer(Type memberType) => _memberTypeComparers[memberType];
private Dictionary<string, Func<object>> _memberValues { get; }
@@ -225,9 +227,6 @@ namespace LibationWinForms
{ typeof(LiberatedStatus), new ObjectComparer<LiberatedStatus>() },
};
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)
{

View File

@@ -29,7 +29,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="2.1.1.1" />
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="2.1.2.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -71,7 +71,7 @@ namespace LibationWinForms
}
}
private async Task Liberate_Click(GridEntry liveGridEntry)
private static async Task Liberate_Click(GridEntry liveGridEntry)
{
var libraryBook = liveGridEntry.LibraryBook;
@@ -91,7 +91,7 @@ namespace LibationWinForms
await BookLiberation.ProcessorAutomationController.BackupSingleBookAsync(libraryBook);
}
private void Details_Click(GridEntry liveGridEntry)
private static void Details_Click(GridEntry liveGridEntry)
{
var bookDetailsForm = new BookDetailsDialog(liveGridEntry.LibraryBook);
if (bookDetailsForm.ShowDialog() != DialogResult.OK)

View File

@@ -13,7 +13,7 @@ namespace TemplatesTests
{
public static class Shared
{
public static LibraryBookDto GetLibraryBook(string asin)
public static LibraryBookDto GetLibraryBook(string asin, string seriesName = "Sherlock Holmes")
=> new()
{
Account = "my account",
@@ -22,7 +22,7 @@ namespace TemplatesTests
Locale = "us",
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
Narrators = new List<string> { "Stephen Fry" },
SeriesName = "Sherlock Holmes",
SeriesName = seriesName ?? "",
SeriesNumber = "1"
};
}
@@ -75,6 +75,24 @@ namespace TemplatesTests
=> Templates.getFileNamingTemplate(GetLibraryBook(asin), template, dirFullPath, extension)
.GetFilePath()
.Should().Be(expected);
[TestMethod]
public void IfSeries_empty()
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", "Sherlock Holmes"), "foo<if series-><-if series>bar", @"C:\a\b", "ext")
.GetFilePath()
.Should().Be(@"C:\a\b\foobar.ext");
[TestMethod]
public void IfSeries_no_series()
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", ""), "foo<if series->-<series>-<id>-<-if series>bar", @"C:\a\b", "ext")
.GetFilePath()
.Should().Be(@"C:\a\b\foobar.ext");
[TestMethod]
public void IfSeries_with_series()
=> Templates.getFileNamingTemplate(GetLibraryBook("asin", "Sherlock Holmes"), "foo<if series->-<series>-<id>-<-if series>bar", @"C:\a\b", "ext")
.GetFilePath()
.Should().Be(@"C:\a\b\foo-Sherlock Holmes-asin-bar.ext");
}
}