Merge branch 'rmcrackan:master' into master
This commit is contained in:
commit
255e26435c
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0-windows</TargetFramework>
|
<TargetFramework>net6.0-windows</TargetFramework>
|
||||||
<Version>7.4.0.1</Version>
|
<Version>7.5.0.1</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -313,7 +313,7 @@ namespace ApplicationServices
|
|||||||
|
|
||||||
// must be here instead of in db layer due to AaxcExists
|
// must be here instead of in db layer due to AaxcExists
|
||||||
public static LiberatedStatus Liberated_Status(Book book)
|
public static LiberatedStatus Liberated_Status(Book book)
|
||||||
=> book.Audio_Exists ? book.UserDefinedItem.BookStatus
|
=> book.Audio_Exists() ? book.UserDefinedItem.BookStatus
|
||||||
: AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
|
: AudibleFileStorage.AaxcExists(book.AudibleProductId) ? LiberatedStatus.PartialDownload
|
||||||
: LiberatedStatus.NotLiberated;
|
: LiberatedStatus.NotLiberated;
|
||||||
|
|
||||||
@ -340,7 +340,7 @@ namespace ApplicationServices
|
|||||||
|
|
||||||
var boolResults = libraryBooks
|
var boolResults = libraryBooks
|
||||||
.AsParallel()
|
.AsParallel()
|
||||||
.Where(lb => lb.Book.HasPdf)
|
.Where(lb => lb.Book.HasPdf())
|
||||||
.Select(lb => Pdf_Status(lb.Book))
|
.Select(lb => Pdf_Status(lb.Book))
|
||||||
.ToList();
|
.ToList();
|
||||||
var pdfsDownloaded = boolResults.Count(r => r == LiberatedStatus.Liberated);
|
var pdfsDownloaded = boolResults.Count(r => r == LiberatedStatus.Liberated);
|
||||||
|
|||||||
@ -111,13 +111,13 @@ namespace ApplicationServices
|
|||||||
AudibleProductId = a.Book.AudibleProductId,
|
AudibleProductId = a.Book.AudibleProductId,
|
||||||
Locale = a.Book.Locale,
|
Locale = a.Book.Locale,
|
||||||
Title = a.Book.Title,
|
Title = a.Book.Title,
|
||||||
AuthorNames = a.Book.AuthorNames,
|
AuthorNames = a.Book.AuthorNames(),
|
||||||
NarratorNames = a.Book.NarratorNames,
|
NarratorNames = a.Book.NarratorNames(),
|
||||||
LengthInMinutes = a.Book.LengthInMinutes,
|
LengthInMinutes = a.Book.LengthInMinutes,
|
||||||
Description = a.Book.Description,
|
Description = a.Book.Description,
|
||||||
Publisher = a.Book.Publisher,
|
Publisher = a.Book.Publisher,
|
||||||
HasPdf = a.Book.HasPdf,
|
HasPdf = a.Book.HasPdf(),
|
||||||
SeriesNames = a.Book.SeriesNames,
|
SeriesNames = a.Book.SeriesNames(),
|
||||||
SeriesOrder = a.Book.SeriesLink.Any() ? a.Book.SeriesLink?.Select(sl => $"{sl.Order} : {sl.Series.Name}").Aggregate((a, b) => $"{a}, {b}") : "",
|
SeriesOrder = a.Book.SeriesLink.Any() ? a.Book.SeriesLink?.Select(sl => $"{sl.Order} : {sl.Series.Name}").Aggregate((a, b) => $"{a}, {b}") : "",
|
||||||
CommunityRatingOverall = a.Book.Rating?.OverallRating,
|
CommunityRatingOverall = a.Book.Rating?.OverallRating,
|
||||||
CommunityRatingPerformance = a.Book.Rating?.PerformanceRating,
|
CommunityRatingPerformance = a.Book.Rating?.PerformanceRating,
|
||||||
@ -125,7 +125,7 @@ namespace ApplicationServices
|
|||||||
PictureId = a.Book.PictureId,
|
PictureId = a.Book.PictureId,
|
||||||
IsAbridged = a.Book.IsAbridged,
|
IsAbridged = a.Book.IsAbridged,
|
||||||
DatePublished = a.Book.DatePublished,
|
DatePublished = a.Book.DatePublished,
|
||||||
CategoriesNames = a.Book.CategoriesNames.Any() ? a.Book.CategoriesNames.Aggregate((a, b) => $"{a}, {b}") : "",
|
CategoriesNames = a.Book.CategoriesNames().Any() ? a.Book.CategoriesNames().Aggregate((a, b) => $"{a}, {b}") : "",
|
||||||
MyRatingOverall = a.Book.UserDefinedItem.Rating.OverallRating,
|
MyRatingOverall = a.Book.UserDefinedItem.Rating.OverallRating,
|
||||||
MyRatingPerformance = a.Book.UserDefinedItem.Rating.PerformanceRating,
|
MyRatingPerformance = a.Book.UserDefinedItem.Rating.PerformanceRating,
|
||||||
MyRatingStory = a.Book.UserDefinedItem.Rating.StoryRating,
|
MyRatingStory = a.Book.UserDefinedItem.Rating.StoryRating,
|
||||||
|
|||||||
@ -43,27 +43,10 @@ namespace DataLayer
|
|||||||
// non-null. use "empty pattern"
|
// non-null. use "empty pattern"
|
||||||
internal int CategoryId { get; private set; }
|
internal int CategoryId { get; private set; }
|
||||||
public Category Category { get; private set; }
|
public Category Category { get; private set; }
|
||||||
public string[] CategoriesNames
|
|
||||||
=> Category is null ? new string[0]
|
|
||||||
: Category.ParentCategory is null ? new[] { Category.Name }
|
|
||||||
: new[] { Category.ParentCategory.Name, Category.Name };
|
|
||||||
public string[] CategoriesIds
|
|
||||||
=> Category is null ? null
|
|
||||||
: Category.ParentCategory is null ? new[] { Category.AudibleCategoryId }
|
|
||||||
: new[] { Category.ParentCategory.AudibleCategoryId, Category.AudibleCategoryId };
|
|
||||||
|
|
||||||
public string TitleSortable => Formatters.GetSortName(Title);
|
|
||||||
public string SeriesSortable => Formatters.GetSortName(SeriesNames);
|
|
||||||
|
|
||||||
// is owned, not optional 1:1
|
// is owned, not optional 1:1
|
||||||
public UserDefinedItem UserDefinedItem { get; private set; }
|
public UserDefinedItem UserDefinedItem { get; private set; }
|
||||||
|
|
||||||
// UserDefinedItem convenience properties
|
|
||||||
/// <summary>True if IsLiberated or Error. False if NotLiberated</summary>
|
|
||||||
public bool Audio_Exists => UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated;
|
|
||||||
/// <summary>True if exists and IsLiberated. Else false</summary>
|
|
||||||
public bool PDF_Exists => UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
|
|
||||||
|
|
||||||
// is owned, not optional 1:1
|
// is owned, not optional 1:1
|
||||||
/// <summary>The product's aggregate community rating</summary>
|
/// <summary>The product's aggregate community rating</summary>
|
||||||
public Rating Rating { get; private set; } = new Rating(0, 0, 0);
|
public Rating Rating { get; private set; } = new Rating(0, 0, 0);
|
||||||
@ -125,11 +108,7 @@ namespace DataLayer
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
public IEnumerable<Contributor> Authors => getContributions(Role.Author).Select(bc => bc.Contributor).ToList();
|
public IEnumerable<Contributor> Authors => getContributions(Role.Author).Select(bc => bc.Contributor).ToList();
|
||||||
public string AuthorNames => string.Join(", ", Authors.Select(a => a.Name));
|
|
||||||
|
|
||||||
public IEnumerable<Contributor> Narrators => getContributions(Role.Narrator).Select(bc => bc.Contributor).ToList();
|
public IEnumerable<Contributor> Narrators => getContributions(Role.Narrator).Select(bc => bc.Contributor).ToList();
|
||||||
public string NarratorNames => string.Join(", ", Narrators.Select(n => n.Name));
|
|
||||||
|
|
||||||
public string Publisher => getContributions(Role.Publisher).SingleOrDefault()?.Contributor.Name;
|
public string Publisher => getContributions(Role.Publisher).SingleOrDefault()?.Contributor.Name;
|
||||||
|
|
||||||
public void ReplaceAuthors(IEnumerable<Contributor> authors, DbContext context = null)
|
public void ReplaceAuthors(IEnumerable<Contributor> authors, DbContext context = null)
|
||||||
@ -185,30 +164,6 @@ namespace DataLayer
|
|||||||
#region series
|
#region series
|
||||||
private HashSet<SeriesBook> _seriesLink;
|
private HashSet<SeriesBook> _seriesLink;
|
||||||
public IEnumerable<SeriesBook> SeriesLink => _seriesLink?.ToList();
|
public IEnumerable<SeriesBook> SeriesLink => _seriesLink?.ToList();
|
||||||
public string SeriesNames
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_seriesLink is null)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
// first: alphabetical by name
|
|
||||||
var withNames = _seriesLink
|
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
|
||||||
.Select(s => s.Series.Name)
|
|
||||||
.OrderBy(a => a)
|
|
||||||
.ToList();
|
|
||||||
// then un-named are alpha by series id
|
|
||||||
var nullNames = _seriesLink
|
|
||||||
.Where(s => string.IsNullOrWhiteSpace(s.Series.Name))
|
|
||||||
.Select(s => s.Series.AudibleSeriesId)
|
|
||||||
.OrderBy(a => a)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var all = withNames.Union(nullNames).ToList();
|
|
||||||
return string.Join(", ", all);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpsertSeries(Series series, string order, DbContext context = null)
|
public void UpsertSeries(Series series, string order, DbContext context = null)
|
||||||
{
|
{
|
||||||
@ -230,7 +185,6 @@ namespace DataLayer
|
|||||||
#region supplements
|
#region supplements
|
||||||
private HashSet<Supplement> _supplements;
|
private HashSet<Supplement> _supplements;
|
||||||
public IEnumerable<Supplement> Supplements => _supplements?.ToList();
|
public IEnumerable<Supplement> Supplements => _supplements?.ToList();
|
||||||
public bool HasPdf => Supplements.Any();
|
|
||||||
|
|
||||||
public void AddSupplementDownloadUrl(string url)
|
public void AddSupplementDownloadUrl(string url)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -38,41 +38,6 @@ namespace DataLayer
|
|||||||
yield return StoryRating;
|
yield return StoryRating;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float FirstScore
|
|
||||||
=> OverallRating > 0 ? OverallRating
|
|
||||||
: PerformanceRating > 0 ? PerformanceRating
|
|
||||||
: StoryRating;
|
|
||||||
|
|
||||||
/// <summary>character: ★</summary>
|
|
||||||
const char STAR = '\u2605';
|
|
||||||
/// <summary>character: ½</summary>
|
|
||||||
const char HALF = '\u00BD';
|
|
||||||
string getStars(float score)
|
|
||||||
{
|
|
||||||
var fullStars = (int)Math.Floor(score);
|
|
||||||
|
|
||||||
var starString = "".PadLeft(fullStars, STAR);
|
|
||||||
|
|
||||||
if (score - fullStars == 0.5f)
|
|
||||||
starString += HALF;
|
|
||||||
|
|
||||||
return starString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string ToStarString()
|
|
||||||
{
|
|
||||||
var items = new List<string>();
|
|
||||||
|
|
||||||
if (OverallRating > 0)
|
|
||||||
items.Add($"Overall: {getStars(OverallRating)}");
|
|
||||||
if (PerformanceRating > 0)
|
|
||||||
items.Add($"Perform: {getStars(PerformanceRating)}");
|
|
||||||
if (StoryRating > 0)
|
|
||||||
items.Add($"Story: {getStars(StoryRating)}");
|
|
||||||
|
|
||||||
return string.Join("\r\n", items);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString() => $"Overall={OverallRating} Perf={PerformanceRating} Story={StoryRating}";
|
public override string ToString() => $"Overall={OverallRating} Perf={PerformanceRating} Story={StoryRating}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
102
Source/DataLayer/EntityExtensions.cs
Normal file
102
Source/DataLayer/EntityExtensions.cs
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DataLayer
|
||||||
|
{
|
||||||
|
public static class EntityExtensions
|
||||||
|
{
|
||||||
|
public static string TitleSortable(this Book book) => Formatters.GetSortName(book.Title);
|
||||||
|
|
||||||
|
public static string AuthorNames(this Book book) => string.Join(", ", book.Authors.Select(a => a.Name));
|
||||||
|
public static string NarratorNames(this Book book) => string.Join(", ", book.Narrators.Select(n => n.Name));
|
||||||
|
|
||||||
|
/// <summary>True if IsLiberated or Error. False if NotLiberated</summary>
|
||||||
|
public static bool Audio_Exists(this Book book) => book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated;
|
||||||
|
/// <summary>True if exists and IsLiberated. Else false</summary>
|
||||||
|
public static bool PDF_Exists(this Book book) => book.UserDefinedItem.PdfStatus == LiberatedStatus.Liberated;
|
||||||
|
|
||||||
|
public static string SeriesSortable(this Book book) => Formatters.GetSortName(book.SeriesNames());
|
||||||
|
public static bool HasPdf(this Book book) => book.Supplements.Any();
|
||||||
|
public static string SeriesNames(this Book book)
|
||||||
|
{
|
||||||
|
if (book.SeriesLink is null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
// first: alphabetical by name
|
||||||
|
var withNames = book.SeriesLink
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
||||||
|
.Select(s => s.Series.Name)
|
||||||
|
.OrderBy(a => a)
|
||||||
|
.ToList();
|
||||||
|
// then un-named are alpha by series id
|
||||||
|
var nullNames = book.SeriesLink
|
||||||
|
.Where(s => string.IsNullOrWhiteSpace(s.Series.Name))
|
||||||
|
.Select(s => s.Series.AudibleSeriesId)
|
||||||
|
.OrderBy(a => a)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var all = withNames.Union(nullNames).ToList();
|
||||||
|
return string.Join(", ", all);
|
||||||
|
}
|
||||||
|
public static string[] CategoriesNames(this Book book)
|
||||||
|
=> book.Category is null ? new string[0]
|
||||||
|
: book.Category.ParentCategory is null ? new[] { book.Category.Name }
|
||||||
|
: new[] { book.Category.ParentCategory.Name, book.Category.Name };
|
||||||
|
public static string[] CategoriesIds(this Book book)
|
||||||
|
=> book.Category is null ? null
|
||||||
|
: book.Category.ParentCategory is null ? new[] { book.Category.AudibleCategoryId }
|
||||||
|
: new[] { book.Category.ParentCategory.AudibleCategoryId, book.Category.AudibleCategoryId };
|
||||||
|
|
||||||
|
public static string AggregateTitles(this IEnumerable<LibraryBook> libraryBooks, int max = 5)
|
||||||
|
{
|
||||||
|
if (libraryBooks is null || !libraryBooks.Any())
|
||||||
|
return "";
|
||||||
|
|
||||||
|
max = Math.Max(max, 1);
|
||||||
|
|
||||||
|
var titles = libraryBooks.Select(lb => "- " + lb.Book.Title).ToList();
|
||||||
|
var titlesAgg = titles.Take(max).Aggregate((a, b) => $"{a}\r\n{b}");
|
||||||
|
if (titles.Count == max + 1)
|
||||||
|
titlesAgg += $"\r\n\r\nand 1 other";
|
||||||
|
else if (titles.Count > max + 1)
|
||||||
|
titlesAgg += $"\r\n\r\nand {titles.Count - max } others";
|
||||||
|
return titlesAgg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float FirstScore(this Rating rating)
|
||||||
|
=> rating.OverallRating > 0 ? rating.OverallRating
|
||||||
|
: rating.PerformanceRating > 0 ? rating.PerformanceRating
|
||||||
|
: rating.StoryRating;
|
||||||
|
public static string ToStarString(this Rating rating)
|
||||||
|
{
|
||||||
|
var items = new List<string>();
|
||||||
|
|
||||||
|
if (rating.OverallRating > 0)
|
||||||
|
items.Add($"Overall: {getStars(rating.OverallRating)}");
|
||||||
|
if (rating.PerformanceRating > 0)
|
||||||
|
items.Add($"Perform: {getStars(rating.PerformanceRating)}");
|
||||||
|
if (rating.StoryRating > 0)
|
||||||
|
items.Add($"Story: {getStars(rating.StoryRating)}");
|
||||||
|
|
||||||
|
return string.Join("\r\n", items);
|
||||||
|
}
|
||||||
|
/// <summary>character: ★</summary>
|
||||||
|
const char STAR = '\u2605';
|
||||||
|
/// <summary>character: ½</summary>
|
||||||
|
const char HALF = '\u00BD';
|
||||||
|
private static string getStars(float score)
|
||||||
|
{
|
||||||
|
var fullStars = (int)Math.Floor(score);
|
||||||
|
|
||||||
|
var starString = new string(STAR, fullStars);
|
||||||
|
|
||||||
|
if (score - fullStars >= 0.25f)
|
||||||
|
starString += HALF;
|
||||||
|
|
||||||
|
return starString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,7 +18,7 @@ namespace FileLiberator
|
|||||||
public override string Name => "Download & Decrypt";
|
public override string Name => "Download & Decrypt";
|
||||||
private AudiobookDownloadBase abDownloader;
|
private AudiobookDownloadBase abDownloader;
|
||||||
|
|
||||||
public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists;
|
public override bool Validate(LibraryBook libraryBook) => !libraryBook.Book.Audio_Exists();
|
||||||
|
|
||||||
public override void Cancel() => abDownloader?.Cancel();
|
public override void Cancel() => abDownloader?.Cancel();
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ namespace FileLiberator
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (libraryBook.Book.Audio_Exists)
|
if (libraryBook.Book.Audio_Exists())
|
||||||
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
|
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
@ -120,7 +120,7 @@ namespace FileLiberator
|
|||||||
: new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic);
|
: new AaxcDownloadSingleConverter(outFileName, cacheDir, audiobookDlLic);
|
||||||
|
|
||||||
if (config.AllowLibationFixup)
|
if (config.AllowLibationFixup)
|
||||||
converter.RetrievedMetadata += (_, tags) => tags.Generes = string.Join(", ", libraryBook.Book.CategoriesNames);
|
converter.RetrievedMetadata += (_, tags) => tags.Generes = string.Join(", ", libraryBook.Book.CategoriesNames());
|
||||||
|
|
||||||
abDownloader = converter;
|
abDownloader = converter;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ namespace FileLiberator
|
|||||||
public override string Name => "Download Pdf";
|
public override string Name => "Download Pdf";
|
||||||
public override bool Validate(LibraryBook libraryBook)
|
public override bool Validate(LibraryBook libraryBook)
|
||||||
=> !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook))
|
=> !string.IsNullOrWhiteSpace(getdownloadUrl(libraryBook))
|
||||||
&& !libraryBook.Book.PDF_Exists;
|
&& !libraryBook.Book.PDF_Exists();
|
||||||
|
|
||||||
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -31,6 +31,12 @@ namespace LibationSearchEngine
|
|||||||
public const string ALL_QUERY = "*:*";
|
public const string ALL_QUERY = "*:*";
|
||||||
|
|
||||||
#region index rules
|
#region index rules
|
||||||
|
// common fields used in the "all" default search field
|
||||||
|
public const string ALL_AUDIBLE_PRODUCT_ID = nameof(Book.AudibleProductId);
|
||||||
|
public const string ALL_TITLE = nameof(Book.Title);
|
||||||
|
public const string ALL_AUTHOR_NAMES = "AuthorNames";
|
||||||
|
public const string ALL_NARRATOR_NAMES = "NarratorNames";
|
||||||
|
|
||||||
private static ReadOnlyDictionary<string, Func<LibraryBook, string>> idIndexRules { get; }
|
private static ReadOnlyDictionary<string, Func<LibraryBook, string>> idIndexRules { get; }
|
||||||
= new ReadOnlyDictionary<string, Func<LibraryBook, string>>(
|
= new ReadOnlyDictionary<string, Func<LibraryBook, string>>(
|
||||||
new Dictionary<string, Func<LibraryBook, string>>
|
new Dictionary<string, Func<LibraryBook, string>>
|
||||||
@ -50,15 +56,15 @@ namespace LibationSearchEngine
|
|||||||
[nameof(Book.DatePublished)] = lb => lb.Book.DatePublished?.ToLuceneString(),
|
[nameof(Book.DatePublished)] = lb => lb.Book.DatePublished?.ToLuceneString(),
|
||||||
|
|
||||||
[nameof(Book.Title)] = lb => lb.Book.Title,
|
[nameof(Book.Title)] = lb => lb.Book.Title,
|
||||||
[nameof(Book.AuthorNames)] = lb => lb.Book.AuthorNames,
|
[ALL_AUTHOR_NAMES] = lb => lb.Book.AuthorNames(),
|
||||||
["Author"] = lb => lb.Book.AuthorNames,
|
["Author"] = lb => lb.Book.AuthorNames(),
|
||||||
["Authors"] = lb => lb.Book.AuthorNames,
|
["Authors"] = lb => lb.Book.AuthorNames(),
|
||||||
[nameof(Book.NarratorNames)] = lb => lb.Book.NarratorNames,
|
[ALL_NARRATOR_NAMES] = lb => lb.Book.NarratorNames(),
|
||||||
["Narrator"] = lb => lb.Book.NarratorNames,
|
["Narrator"] = lb => lb.Book.NarratorNames(),
|
||||||
["Narrators"] = lb => lb.Book.NarratorNames,
|
["Narrators"] = lb => lb.Book.NarratorNames(),
|
||||||
[nameof(Book.Publisher)] = lb => lb.Book.Publisher,
|
[nameof(Book.Publisher)] = lb => lb.Book.Publisher,
|
||||||
|
|
||||||
[nameof(Book.SeriesNames)] = lb => string.Join(
|
["SeriesNames"] = lb => string.Join(
|
||||||
", ",
|
", ",
|
||||||
lb.Book.SeriesLink
|
lb.Book.SeriesLink
|
||||||
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
.Where(s => !string.IsNullOrWhiteSpace(s.Series.Name))
|
||||||
@ -70,11 +76,11 @@ namespace LibationSearchEngine
|
|||||||
.Select(s => s.Series.AudibleSeriesId)),
|
.Select(s => s.Series.AudibleSeriesId)),
|
||||||
["SeriesId"] = lb => string.Join(", ", lb.Book.SeriesLink.Select(s => s.Series.AudibleSeriesId)),
|
["SeriesId"] = lb => string.Join(", ", lb.Book.SeriesLink.Select(s => s.Series.AudibleSeriesId)),
|
||||||
|
|
||||||
[nameof(Book.CategoriesNames)] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
["CategoriesNames"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||||
[nameof(Book.Category)] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
[nameof(Book.Category)] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||||
["Categories"] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
["Categories"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||||
["CategoriesId"] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
["CategoriesId"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||||
["CategoryId"] = lb => lb.Book.CategoriesIds is null ? null : string.Join(", ", lb.Book.CategoriesIds),
|
["CategoryId"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
||||||
|
|
||||||
[TAGS.FirstCharToUpper()] = lb => lb.Book.UserDefinedItem.Tags,
|
[TAGS.FirstCharToUpper()] = lb => lb.Book.UserDefinedItem.Tags,
|
||||||
|
|
||||||
@ -107,14 +113,14 @@ namespace LibationSearchEngine
|
|||||||
= new ReadOnlyDictionary<string, Func<LibraryBook, bool>>(
|
= new ReadOnlyDictionary<string, Func<LibraryBook, bool>>(
|
||||||
new Dictionary<string, Func<LibraryBook, bool>>
|
new Dictionary<string, Func<LibraryBook, bool>>
|
||||||
{
|
{
|
||||||
["HasDownloads"] = lb => lb.Book.HasPdf,
|
["HasDownloads"] = lb => lb.Book.HasPdf(),
|
||||||
["HasDownload"] = lb => lb.Book.HasPdf,
|
["HasDownload"] = lb => lb.Book.HasPdf(),
|
||||||
["Downloads"] = lb => lb.Book.HasPdf,
|
["Downloads"] = lb => lb.Book.HasPdf(),
|
||||||
["Download"] = lb => lb.Book.HasPdf,
|
["Download"] = lb => lb.Book.HasPdf(),
|
||||||
["HasPDFs"] = lb => lb.Book.HasPdf,
|
["HasPDFs"] = lb => lb.Book.HasPdf(),
|
||||||
["HasPDF"] = lb => lb.Book.HasPdf,
|
["HasPDF"] = lb => lb.Book.HasPdf(),
|
||||||
["PDFs"] = lb => lb.Book.HasPdf,
|
["PDFs"] = lb => lb.Book.HasPdf(),
|
||||||
["PDF"] = lb => lb.Book.HasPdf,
|
["PDF"] = lb => lb.Book.HasPdf(),
|
||||||
|
|
||||||
["IsRated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
|
["IsRated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
|
||||||
["Rated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
|
["Rated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
|
||||||
@ -151,10 +157,10 @@ namespace LibationSearchEngine
|
|||||||
private static IEnumerable<Func<LibraryBook, string>> allFieldIndexRules { get; }
|
private static IEnumerable<Func<LibraryBook, string>> allFieldIndexRules { get; }
|
||||||
= new List<Func<LibraryBook, string>>
|
= new List<Func<LibraryBook, string>>
|
||||||
{
|
{
|
||||||
idIndexRules[nameof(Book.AudibleProductId)],
|
idIndexRules[ALL_AUDIBLE_PRODUCT_ID],
|
||||||
stringIndexRules[nameof(Book.Title)],
|
stringIndexRules[ALL_TITLE],
|
||||||
stringIndexRules[nameof(Book.AuthorNames)],
|
stringIndexRules[ALL_AUTHOR_NAMES],
|
||||||
stringIndexRules[nameof(Book.NarratorNames)]
|
stringIndexRules[ALL_NARRATOR_NAMES]
|
||||||
};
|
};
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@ -26,8 +26,8 @@ namespace LibationWinForms.BookLiberation
|
|||||||
|
|
||||||
//Set default values from library
|
//Set default values from library
|
||||||
AudioDecodable_TitleDiscovered(sender, libraryBook.Book.Title);
|
AudioDecodable_TitleDiscovered(sender, libraryBook.Book.Title);
|
||||||
AudioDecodable_AuthorsDiscovered(sender, libraryBook.Book.AuthorNames);
|
AudioDecodable_AuthorsDiscovered(sender, libraryBook.Book.AuthorNames());
|
||||||
AudioDecodable_NarratorsDiscovered(sender, libraryBook.Book.NarratorNames);
|
AudioDecodable_NarratorsDiscovered(sender, libraryBook.Book.NarratorNames());
|
||||||
AudioDecodable_CoverImageDiscovered(sender,
|
AudioDecodable_CoverImageDiscovered(sender,
|
||||||
PictureStorage.GetPicture(
|
PictureStorage.GetPicture(
|
||||||
new PictureDefinition(
|
new PictureDefinition(
|
||||||
|
|||||||
@ -241,8 +241,8 @@ namespace LibationWinForms.BookLiberation
|
|||||||
details =
|
details =
|
||||||
$@" Title: {libraryBook.Book.Title}
|
$@" Title: {libraryBook.Book.Title}
|
||||||
ID: {libraryBook.Book.AudibleProductId}
|
ID: {libraryBook.Book.AudibleProductId}
|
||||||
Author: {trunc(libraryBook.Book.AuthorNames)}
|
Author: {trunc(libraryBook.Book.AuthorNames())}
|
||||||
Narr: {trunc(libraryBook.Book.NarratorNames)}";
|
Narr: {trunc(libraryBook.Book.NarratorNames())}";
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@ -128,7 +128,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBoxAlertAdmin.Show("Error attempting to save accounts", "Error saving accounts", ex);
|
MessageBoxLib.ShowAdminAlert("Error attempting to save accounts", "Error saving accounts", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,15 +46,16 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
var t = @$"
|
var t = @$"
|
||||||
Title: {Book.Title}
|
Title: {Book.Title}
|
||||||
Author(s): {Book.AuthorNames}
|
Author(s): {Book.AuthorNames()}
|
||||||
Narrator(s): {Book.NarratorNames}
|
Narrator(s): {Book.NarratorNames()}
|
||||||
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
|
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
|
||||||
Category: {string.Join(" > ", Book.CategoriesNames)}
|
Category: {string.Join(" > ", Book.CategoriesNames())}
|
||||||
Purchase Date: {_libraryBook.DateAdded.ToString("d")}
|
Purchase Date: {_libraryBook.DateAdded.ToString("d")}
|
||||||
".Trim();
|
".Trim();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(Book.SeriesNames))
|
var seriesNames = Book.SeriesNames();
|
||||||
t += $"\r\nSeries: {Book.SeriesNames}";
|
if (!string.IsNullOrWhiteSpace(seriesNames))
|
||||||
|
t += $"\r\nSeries: {seriesNames}";
|
||||||
|
|
||||||
var bookRating = Book.Rating?.ToStarString();
|
var bookRating = Book.Rating?.ToStarString();
|
||||||
if (!string.IsNullOrWhiteSpace(bookRating))
|
if (!string.IsNullOrWhiteSpace(bookRating))
|
||||||
|
|||||||
@ -46,7 +46,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
if (template is null)
|
if (template is null)
|
||||||
{
|
{
|
||||||
MessageBoxAlertAdmin.Show($"Programming error. {nameof(EditTemplateDialog)} was not created correctly", "Edit template error", new NullReferenceException($"{nameof(template)} is null"));
|
MessageBoxLib.ShowAdminAlert($"Programming error. {nameof(EditTemplateDialog)} was not created correctly", "Edit template error", new NullReferenceException($"{nameof(template)} is null"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -77,7 +77,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBoxAlertAdmin.Show(
|
MessageBoxLib.ShowAdminAlert(
|
||||||
"Error scanning library. You may still manually select books to remove from Libation's library.",
|
"Error scanning library. You may still manually select books to remove from Libation's library.",
|
||||||
"Error scanning library",
|
"Error scanning library",
|
||||||
ex);
|
ex);
|
||||||
@ -95,27 +95,16 @@ namespace LibationWinForms.Dialogs
|
|||||||
if (selectedBooks.Count == 0)
|
if (selectedBooks.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var titles = selectedBooks.Select(rge => "- " + rge.Title).ToList();
|
var libraryBooks = selectedBooks.Select(rge => rge.LibraryBook).ToList();
|
||||||
var titlesAgg = titles.Take(5).Aggregate((a, b) => $"{a}\r\n{b}");
|
var result = MessageBoxLib.ShowConfirmationDialog(
|
||||||
if (titles.Count == 6)
|
libraryBooks,
|
||||||
titlesAgg += $"\r\n\r\nand 1 other";
|
$"Are you sure you want to remove {0} from Libation's library?",
|
||||||
else if (titles.Count > 6)
|
"Remove books from Libation?");
|
||||||
titlesAgg += $"\r\n\r\nand {titles.Count - 5} others";
|
|
||||||
|
|
||||||
string thisThese = selectedBooks.Count > 1 ? "these" : "this";
|
if (result != DialogResult.Yes)
|
||||||
string bookBooks = selectedBooks.Count > 1 ? "books" : "book";
|
return;
|
||||||
|
|
||||||
var result = MessageBox.Show(
|
var idsToRemove = libraryBooks.Select(lb => lb.Book.AudibleProductId).ToList();
|
||||||
this,
|
|
||||||
$"Are you sure you want to remove {thisThese} {selectedBooks.Count} {bookBooks} from Libation's library?\r\n\r\n{titlesAgg}",
|
|
||||||
"Remove books from Libation?",
|
|
||||||
MessageBoxButtons.YesNo,
|
|
||||||
MessageBoxIcon.Question,
|
|
||||||
MessageBoxDefaultButton.Button1);
|
|
||||||
|
|
||||||
if (result == DialogResult.Yes)
|
|
||||||
{
|
|
||||||
var idsToRemove = selectedBooks.Select(rge => rge.AudibleProductId).ToList();
|
|
||||||
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
var removeLibraryBooks = await LibraryCommands.RemoveBooksAsync(idsToRemove);
|
||||||
|
|
||||||
foreach (var rEntry in selectedBooks)
|
foreach (var rEntry in selectedBooks)
|
||||||
@ -123,7 +112,6 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
UpdateSelection();
|
UpdateSelection();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateSelection()
|
private void UpdateSelection()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -181,7 +181,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
// only warn if changed during this time. don't want to warn every time user happens to change settings while level is verbose
|
// only warn if changed during this time. don't want to warn every time user happens to change settings while level is verbose
|
||||||
if (logLevelOld != logLevelNew)
|
if (logLevelOld != logLevelNew)
|
||||||
MessageBoxVerboseLoggingWarning.ShowIfTrue();
|
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
using System;
|
|
||||||
using LibationWinForms.Dialogs;
|
|
||||||
|
|
||||||
namespace LibationWinForms
|
|
||||||
{
|
|
||||||
public static class MessageBoxAlertAdmin
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Logs error. Displays a message box dialog with specified text and caption.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">The text to display in the message box.</param>
|
|
||||||
/// <param name="caption">The text to display in the title bar of the message box.</param>
|
|
||||||
/// <param name="exception">Exception to log</param>
|
|
||||||
/// <returns>One of the System.Windows.Forms.DialogResult values.</returns>
|
|
||||||
public static System.Windows.Forms.DialogResult Show(string text, string caption, Exception exception)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Serilog.Log.Logger.Error(exception, "Alert admin error: {@DebugText}", new { text, caption });
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
using var form = new MessageBoxAlertAdminDialog(text, caption, exception);
|
|
||||||
return form.ShowDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using Dinah.Core.Logging;
|
|
||||||
using Serilog;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace LibationWinForms
|
|
||||||
{
|
|
||||||
public static class MessageBoxVerboseLoggingWarning
|
|
||||||
{
|
|
||||||
public static void ShowIfTrue()
|
|
||||||
{
|
|
||||||
// when turning on debug (and especially Verbose) to share logs, some privacy settings may not be obscured
|
|
||||||
if (Log.Logger.IsVerboseEnabled())
|
|
||||||
MessageBox.Show(@"
|
|
||||||
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 anonymous.
|
|
||||||
|
|
||||||
When you are finished debugging, it's highly recommended
|
|
||||||
to set your debug MinimumLevel to Information and restart
|
|
||||||
Libation.
|
|
||||||
".Trim(), "Verbose logging enabled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -49,7 +49,7 @@ namespace LibationWinForms
|
|||||||
// migrations which require Forms or are long-running
|
// migrations which require Forms or are long-running
|
||||||
RunWindowsOnlyMigrations(config);
|
RunWindowsOnlyMigrations(config);
|
||||||
|
|
||||||
MessageBoxVerboseLoggingWarning.ShowIfTrue();
|
MessageBoxLib.VerboseLoggingWarning_ShowIfTrue();
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
checkForUpdate();
|
checkForUpdate();
|
||||||
@ -63,7 +63,7 @@ namespace LibationWinForms
|
|||||||
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
|
var body = "An unrecoverable error occurred. Since this error happened before logging could be initialized, this error can not be written to the log file.";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MessageBoxAlertAdmin.Show(body, title, ex);
|
MessageBoxLib.ShowAdminAlert(body, title, ex);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@ -178,7 +178,7 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBoxAlertAdmin.Show("Error checking for update", "Error checking for update", ex);
|
MessageBoxLib.ShowAdminAlert("Error checking for update", "Error checking for update", ex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ namespace LibationWinForms
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MessageBoxAlertAdmin.Show("Error downloading update", "Error downloading update", ex);
|
MessageBoxLib.ShowAdminAlert("Error downloading update", "Error downloading update", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,14 +128,14 @@ namespace LibationWinForms
|
|||||||
// Immutable properties
|
// Immutable properties
|
||||||
{
|
{
|
||||||
Title = Book.Title;
|
Title = Book.Title;
|
||||||
Series = Book.SeriesNames;
|
Series = Book.SeriesNames();
|
||||||
Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min";
|
Length = Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min";
|
||||||
MyRating = Book.UserDefinedItem.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
|
MyRating = Book.UserDefinedItem.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
|
||||||
PurchaseDate = libraryBook.DateAdded.ToString("d");
|
PurchaseDate = libraryBook.DateAdded.ToString("d");
|
||||||
ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
|
ProductRating = Book.Rating?.ToStarString()?.DefaultIfNullOrWhiteSpace("");
|
||||||
Authors = Book.AuthorNames;
|
Authors = Book.AuthorNames();
|
||||||
Narrators = Book.NarratorNames;
|
Narrators = Book.NarratorNames();
|
||||||
Category = string.Join(" > ", Book.CategoriesNames);
|
Category = string.Join(" > ", Book.CategoriesNames());
|
||||||
Misc = GetMiscDisplay(libraryBook);
|
Misc = GetMiscDisplay(libraryBook);
|
||||||
LongDescription = GetDescriptionDisplay(Book);
|
LongDescription = GetDescriptionDisplay(Book);
|
||||||
Description = TrimTextToWord(LongDescription, 62);
|
Description = TrimTextToWord(LongDescription, 62);
|
||||||
@ -232,12 +232,12 @@ namespace LibationWinForms
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private Dictionary<string, Func<object>> CreateMemberValueDictionary() => new()
|
private Dictionary<string, Func<object>> CreateMemberValueDictionary() => new()
|
||||||
{
|
{
|
||||||
{ nameof(Title), () => Book.TitleSortable },
|
{ nameof(Title), () => Book.TitleSortable() },
|
||||||
{ nameof(Series), () => Book.SeriesSortable },
|
{ nameof(Series), () => Book.SeriesSortable() },
|
||||||
{ nameof(Length), () => Book.LengthInMinutes },
|
{ nameof(Length), () => Book.LengthInMinutes },
|
||||||
{ nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore },
|
{ nameof(MyRating), () => Book.UserDefinedItem.Rating.FirstScore() },
|
||||||
{ nameof(PurchaseDate), () => LibraryBook.DateAdded },
|
{ nameof(PurchaseDate), () => LibraryBook.DateAdded },
|
||||||
{ nameof(ProductRating), () => Book.Rating.FirstScore },
|
{ nameof(ProductRating), () => Book.Rating.FirstScore() },
|
||||||
{ nameof(Authors), () => Authors },
|
{ nameof(Authors), () => Authors },
|
||||||
{ nameof(Narrators), () => Narrators },
|
{ nameof(Narrators), () => Narrators },
|
||||||
{ nameof(Description), () => Description },
|
{ nameof(Description), () => Description },
|
||||||
@ -292,7 +292,7 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
details.Add($"Account: {locale} - {acct}");
|
details.Add($"Account: {locale} - {acct}");
|
||||||
|
|
||||||
if (libraryBook.Book.HasPdf)
|
if (libraryBook.Book.HasPdf())
|
||||||
details.Add("Has PDF");
|
details.Add("Has PDF");
|
||||||
if (libraryBook.Book.IsAbridged)
|
if (libraryBook.Book.IsAbridged)
|
||||||
details.Add("Abridged");
|
details.Add("Abridged");
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
|
using DataLayer;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Dinah.Core.DataBinding;
|
using Dinah.Core.DataBinding;
|
||||||
using Dinah.Core.Threading;
|
using Dinah.Core.Threading;
|
||||||
@ -130,7 +131,7 @@ namespace LibationWinForms
|
|||||||
var libraryBook = liveGridEntry.LibraryBook;
|
var libraryBook = liveGridEntry.LibraryBook;
|
||||||
|
|
||||||
// liberated: open explorer to file
|
// liberated: open explorer to file
|
||||||
if (libraryBook.Book.Audio_Exists)
|
if (libraryBook.Book.Audio_Exists())
|
||||||
{
|
{
|
||||||
var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
|
var filePath = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId);
|
||||||
if (!Go.To.File(filePath))
|
if (!Go.To.File(filePath))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user