Merge pull request #625 from Mbucari/master
Refactor LibationSearchEngine
This commit is contained in:
commit
ee51fd9da6
@ -10,7 +10,7 @@ namespace ApplicationServices
|
|||||||
{
|
{
|
||||||
public class BulkSetDownloadStatus
|
public class BulkSetDownloadStatus
|
||||||
{
|
{
|
||||||
private List<(string message, LiberatedStatus newStatus, IEnumerable<Book> Books)> actionSets { get; } = new();
|
private List<(string message, LiberatedStatus newStatus, IEnumerable<LibraryBook> LibraryBooks)> actionSets { get; } = new();
|
||||||
|
|
||||||
public int Count => actionSets.Count;
|
public int Count => actionSets.Count;
|
||||||
|
|
||||||
@ -33,7 +33,7 @@ namespace ApplicationServices
|
|||||||
var bookExistsList = _libraryBooks
|
var bookExistsList = _libraryBooks
|
||||||
.Select(libraryBook => new
|
.Select(libraryBook => new
|
||||||
{
|
{
|
||||||
libraryBook.Book,
|
LibraryBook = libraryBook,
|
||||||
FileExists = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId) is not null
|
FileExists = AudibleFileStorage.Audio.GetPath(libraryBook.Book.AudibleProductId) is not null
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
@ -41,8 +41,8 @@ namespace ApplicationServices
|
|||||||
if (_setDownloaded)
|
if (_setDownloaded)
|
||||||
{
|
{
|
||||||
var books2change = bookExistsList
|
var books2change = bookExistsList
|
||||||
.Where(a => a.FileExists && a.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated)
|
.Where(a => a.FileExists && a.LibraryBook.Book.UserDefinedItem.BookStatus != LiberatedStatus.Liberated)
|
||||||
.Select(a => a.Book)
|
.Select(a => a.LibraryBook)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (books2change.Any())
|
if (books2change.Any())
|
||||||
@ -55,8 +55,8 @@ namespace ApplicationServices
|
|||||||
if (_setNotDownloaded)
|
if (_setNotDownloaded)
|
||||||
{
|
{
|
||||||
var books2change = bookExistsList
|
var books2change = bookExistsList
|
||||||
.Where(a => !a.FileExists && a.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated)
|
.Where(a => !a.FileExists && a.LibraryBook.Book.UserDefinedItem.BookStatus != LiberatedStatus.NotLiberated)
|
||||||
.Select(a => a.Book)
|
.Select(a => a.LibraryBook)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (books2change.Any())
|
if (books2change.Any())
|
||||||
@ -72,7 +72,7 @@ namespace ApplicationServices
|
|||||||
public void Execute()
|
public void Execute()
|
||||||
{
|
{
|
||||||
foreach (var a in actionSets)
|
foreach (var a in actionSets)
|
||||||
a.Books.UpdateBookStatus(a.newStatus);
|
a.LibraryBooks.UpdateBookStatus(a.newStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -446,25 +446,25 @@ namespace ApplicationServices
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Occurs when the size of the library does not change but book(s) details do. Especially when <see cref="UserDefinedItem.Tags"/>, <see cref="UserDefinedItem.BookStatus"/>, or <see cref="UserDefinedItem.PdfStatus"/> changed values are successfully persisted.
|
/// Occurs when the size of the library does not change but book(s) details do. Especially when <see cref="UserDefinedItem.Tags"/>, <see cref="UserDefinedItem.BookStatus"/>, or <see cref="UserDefinedItem.PdfStatus"/> changed values are successfully persisted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static event EventHandler<IEnumerable<Book>> BookUserDefinedItemCommitted;
|
public static event EventHandler<IEnumerable<LibraryBook>> BookUserDefinedItemCommitted;
|
||||||
|
|
||||||
#region Update book details
|
#region Update book details
|
||||||
public static int UpdateUserDefinedItem(
|
public static int UpdateUserDefinedItem(
|
||||||
this Book book,
|
this LibraryBook lb,
|
||||||
string tags = null,
|
string tags = null,
|
||||||
LiberatedStatus? bookStatus = null,
|
LiberatedStatus? bookStatus = null,
|
||||||
LiberatedStatus? pdfStatus = null,
|
LiberatedStatus? pdfStatus = null,
|
||||||
Rating rating = null)
|
Rating rating = null)
|
||||||
=> new[] { book }.UpdateUserDefinedItem(tags, bookStatus, pdfStatus, rating);
|
=> new[] { lb }.UpdateUserDefinedItem(tags, bookStatus, pdfStatus, rating);
|
||||||
|
|
||||||
public static int UpdateUserDefinedItem(
|
public static int UpdateUserDefinedItem(
|
||||||
this IEnumerable<Book> books,
|
this IEnumerable<LibraryBook> lb,
|
||||||
string tags = null,
|
string tags = null,
|
||||||
LiberatedStatus? bookStatus = null,
|
LiberatedStatus? bookStatus = null,
|
||||||
LiberatedStatus? pdfStatus = null,
|
LiberatedStatus? pdfStatus = null,
|
||||||
Rating rating = null)
|
Rating rating = null)
|
||||||
=> updateUserDefinedItem(
|
=> updateUserDefinedItem(
|
||||||
books,
|
lb,
|
||||||
udi => {
|
udi => {
|
||||||
// blank tags are expected. null tags are not
|
// blank tags are expected. null tags are not
|
||||||
if (tags is not null)
|
if (tags is not null)
|
||||||
@ -480,66 +480,52 @@ namespace ApplicationServices
|
|||||||
udi.UpdateRating(rating.OverallRating, rating.PerformanceRating, rating.StoryRating);
|
udi.UpdateRating(rating.OverallRating, rating.PerformanceRating, rating.StoryRating);
|
||||||
});
|
});
|
||||||
|
|
||||||
public static int UpdateBookStatus(this Book book, LiberatedStatus bookStatus, Version libationVersion)
|
public static int UpdateBookStatus(this LibraryBook lb, LiberatedStatus bookStatus, Version libationVersion)
|
||||||
=> book.UpdateUserDefinedItem(udi => { udi.BookStatus = bookStatus; udi.SetLastDownloaded(libationVersion); });
|
=> lb.UpdateUserDefinedItem(udi => { udi.BookStatus = bookStatus; udi.SetLastDownloaded(libationVersion); });
|
||||||
public static int UpdateBookStatus(this Book book, LiberatedStatus bookStatus)
|
|
||||||
=> book.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus);
|
|
||||||
public static int UpdateBookStatus(this IEnumerable<Book> books, LiberatedStatus bookStatus)
|
|
||||||
=> books.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus);
|
|
||||||
public static int UpdateBookStatus(this LibraryBook libraryBook, LiberatedStatus bookStatus)
|
public static int UpdateBookStatus(this LibraryBook libraryBook, LiberatedStatus bookStatus)
|
||||||
=> libraryBook.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus);
|
=> libraryBook.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus);
|
||||||
public static int UpdateBookStatus(this IEnumerable<LibraryBook> libraryBooks, LiberatedStatus bookStatus)
|
public static int UpdateBookStatus(this IEnumerable<LibraryBook> libraryBooks, LiberatedStatus bookStatus)
|
||||||
=> libraryBooks.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus);
|
=> libraryBooks.UpdateUserDefinedItem(udi => udi.BookStatus = bookStatus);
|
||||||
|
|
||||||
public static int UpdatePdfStatus(this Book book, LiberatedStatus pdfStatus)
|
|
||||||
=> book.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus));
|
|
||||||
public static int UpdatePdfStatus(this IEnumerable<Book> books, LiberatedStatus pdfStatus)
|
|
||||||
=> books.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus));
|
|
||||||
public static int UpdatePdfStatus(this LibraryBook libraryBook, LiberatedStatus pdfStatus)
|
public static int UpdatePdfStatus(this LibraryBook libraryBook, LiberatedStatus pdfStatus)
|
||||||
=> libraryBook.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus));
|
=> libraryBook.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus));
|
||||||
public static int UpdatePdfStatus(this IEnumerable<LibraryBook> libraryBooks, LiberatedStatus pdfStatus)
|
public static int UpdatePdfStatus(this IEnumerable<LibraryBook> libraryBooks, LiberatedStatus pdfStatus)
|
||||||
=> libraryBooks.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus));
|
=> libraryBooks.UpdateUserDefinedItem(udi => udi.SetPdfStatus(pdfStatus));
|
||||||
|
|
||||||
public static int UpdateTags(this Book book, string tags)
|
|
||||||
=> book.UpdateUserDefinedItem(udi => udi.Tags = tags);
|
|
||||||
public static int UpdateTags(this IEnumerable<Book> books, string tags)
|
|
||||||
=> books.UpdateUserDefinedItem(udi => udi.Tags = tags);
|
|
||||||
public static int UpdateTags(this LibraryBook libraryBook, string tags)
|
public static int UpdateTags(this LibraryBook libraryBook, string tags)
|
||||||
=> libraryBook.UpdateUserDefinedItem(udi => udi.Tags = tags);
|
=> libraryBook.UpdateUserDefinedItem(udi => udi.Tags = tags);
|
||||||
public static int UpdateTags(this IEnumerable<LibraryBook> libraryBooks, string tags)
|
public static int UpdateTags(this IEnumerable<LibraryBook> libraryBooks, string tags)
|
||||||
=> libraryBooks.UpdateUserDefinedItem(udi => udi.Tags = tags);
|
=> libraryBooks.UpdateUserDefinedItem(udi => udi.Tags = tags);
|
||||||
|
|
||||||
public static int UpdateUserDefinedItem(this LibraryBook libraryBook, Action<UserDefinedItem> action)
|
public static int UpdateUserDefinedItem(this LibraryBook libraryBook, Action<UserDefinedItem> action)
|
||||||
=> libraryBook.Book.updateUserDefinedItem(action);
|
=> libraryBook.updateUserDefinedItem(action);
|
||||||
public static int UpdateUserDefinedItem(this IEnumerable<LibraryBook> libraryBooks, Action<UserDefinedItem> action)
|
public static int UpdateUserDefinedItem(this IEnumerable<LibraryBook> libraryBooks, Action<UserDefinedItem> action)
|
||||||
=> libraryBooks.Select(lb => lb.Book).updateUserDefinedItem(action);
|
=> libraryBooks.updateUserDefinedItem(action);
|
||||||
|
|
||||||
public static int UpdateUserDefinedItem(this Book book, Action<UserDefinedItem> action) => book.updateUserDefinedItem(action);
|
private static int updateUserDefinedItem(this LibraryBook libraryBook, Action<UserDefinedItem> action) => new[] { libraryBook }.updateUserDefinedItem(action);
|
||||||
public static int UpdateUserDefinedItem(this IEnumerable<Book> books, Action<UserDefinedItem> action) => books.updateUserDefinedItem(action);
|
private static int updateUserDefinedItem(this IEnumerable<LibraryBook> libraryBooks, Action<UserDefinedItem> action)
|
||||||
|
|
||||||
private static int updateUserDefinedItem(this Book book, Action<UserDefinedItem> action) => new[] { book }.updateUserDefinedItem(action);
|
|
||||||
private static int updateUserDefinedItem(this IEnumerable<Book> books, Action<UserDefinedItem> action)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (books is null || !books.Any())
|
if (libraryBooks is null || !libraryBooks.Any())
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
foreach (var book in books)
|
foreach (var book in libraryBooks)
|
||||||
action?.Invoke(book.UserDefinedItem);
|
action?.Invoke(book.Book.UserDefinedItem);
|
||||||
|
|
||||||
using var context = DbContexts.GetContext();
|
using var context = DbContexts.GetContext();
|
||||||
|
|
||||||
// Attach() NoTracking entities before SaveChanges()
|
// Attach() NoTracking entities before SaveChanges()
|
||||||
foreach (var book in books)
|
foreach (var book in libraryBooks)
|
||||||
{
|
{
|
||||||
context.Attach(book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
context.Attach(book.Book.UserDefinedItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||||
context.Attach(book.UserDefinedItem.Rating).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
context.Attach(book.Book.UserDefinedItem.Rating).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
var qtyChanges = context.SaveChanges();
|
var qtyChanges = context.SaveChanges();
|
||||||
if (qtyChanges > 0)
|
if (qtyChanges > 0)
|
||||||
BookUserDefinedItemCommitted?.Invoke(null, books);
|
BookUserDefinedItemCommitted?.Invoke(null, libraryBooks);
|
||||||
|
|
||||||
return qtyChanges;
|
return qtyChanges;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,7 @@ namespace ApplicationServices
|
|||||||
#region Update
|
#region Update
|
||||||
private static bool isUpdating;
|
private static bool isUpdating;
|
||||||
|
|
||||||
public static void UpdateBooks(IEnumerable<Book> books)
|
public static void UpdateBooks(IEnumerable<LibraryBook> books)
|
||||||
{
|
{
|
||||||
// Semi-arbitrary. At some point it's more worth it to do a full re-index than to do one offs.
|
// Semi-arbitrary. At some point it's more worth it to do a full re-index than to do one offs.
|
||||||
// I did not benchmark before choosing the number here
|
// I did not benchmark before choosing the number here
|
||||||
@ -49,10 +49,10 @@ namespace ApplicationServices
|
|||||||
|
|
||||||
public static void FullReIndex() => performSafeCommand(fullReIndex);
|
public static void FullReIndex() => performSafeCommand(fullReIndex);
|
||||||
|
|
||||||
internal static void UpdateUserDefinedItems(Book book) => performSafeCommand(e =>
|
internal static void UpdateUserDefinedItems(LibraryBook book) => performSafeCommand(e =>
|
||||||
{
|
{
|
||||||
e.UpdateLiberatedStatus(book);
|
e.UpdateLiberatedStatus(book);
|
||||||
e.UpdateTags(book.AudibleProductId, book.UserDefinedItem.Tags);
|
e.UpdateTags(book.Book.AudibleProductId, book.Book.UserDefinedItem.Tags);
|
||||||
e.UpdateUserRatings(book);
|
e.UpdateUserRatings(book);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@ -80,7 +80,7 @@ namespace FileLiberator
|
|||||||
{
|
{
|
||||||
Task.Run(() => downloadCoverArt(libraryBook)),
|
Task.Run(() => downloadCoverArt(libraryBook)),
|
||||||
Task.Run(() => moveFilesToBooksDir(libraryBook, entries)),
|
Task.Run(() => moveFilesToBooksDir(libraryBook, entries)),
|
||||||
Task.Run(() => libraryBook.Book.UpdateBookStatus(LiberatedStatus.Liberated, Configuration.LibationVersion)),
|
Task.Run(() => libraryBook.UpdateBookStatus(LiberatedStatus.Liberated, Configuration.LibationVersion)),
|
||||||
Task.Run(() => WindowsDirectory.SetCoverAsFolderIcon(libraryBook.Book.PictureId, finalStorageDir))
|
Task.Run(() => WindowsDirectory.SetCoverAsFolderIcon(libraryBook.Book.PictureId, finalStorageDir))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -30,7 +30,7 @@ namespace FileLiberator
|
|||||||
var actualDownloadedFilePath = await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
|
var actualDownloadedFilePath = await downloadPdfAsync(libraryBook, proposedDownloadFilePath);
|
||||||
var result = verifyDownload(actualDownloadedFilePath);
|
var result = verifyDownload(actualDownloadedFilePath);
|
||||||
|
|
||||||
libraryBook.Book.UpdatePdfStatus(result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated);
|
libraryBook.UpdatePdfStatus(result.IsSuccess ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,7 +48,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
|
|
||||||
protected override void SaveAndClose()
|
protected override void SaveAndClose()
|
||||||
{
|
{
|
||||||
LibraryBook.Book.UpdateUserDefinedItem(NewTags, bookStatus: BookLiberatedStatus, pdfStatus: PdfLiberatedStatus);
|
LibraryBook.UpdateUserDefinedItem(NewTags, bookStatus: BookLiberatedStatus, pdfStatus: PdfLiberatedStatus);
|
||||||
base.SaveAndClose();
|
base.SaveAndClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
using LibationSearchEngine;
|
||||||
|
|
||||||
namespace LibationAvalonia.Dialogs
|
namespace LibationAvalonia.Dialogs
|
||||||
{
|
{
|
||||||
public partial class SearchSyntaxDialog : DialogWindow
|
public partial class SearchSyntaxDialog : DialogWindow
|
||||||
@ -18,7 +20,7 @@ Search for wizard of oz:
|
|||||||
title:""wizard of oz""
|
title:""wizard of oz""
|
||||||
|
|
||||||
|
|
||||||
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchStringFields());
|
" + string.Join("\r\n", SearchEngine.FieldIndexRules.StringFieldNames);
|
||||||
|
|
||||||
NumberFields = @"
|
NumberFields = @"
|
||||||
Find books between 1-100 minutes long
|
Find books between 1-100 minutes long
|
||||||
@ -30,14 +32,14 @@ Find books published from 2020-1-1 to
|
|||||||
datepublished:[20200101 TO 20231231]
|
datepublished:[20200101 TO 20231231]
|
||||||
|
|
||||||
|
|
||||||
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields());
|
" + string.Join("\r\n", SearchEngine.FieldIndexRules.NumberFieldNames);
|
||||||
|
|
||||||
BoolFields = @"
|
BoolFields = @"
|
||||||
Find books that you haven't rated:
|
Find books that you haven't rated:
|
||||||
-IsRated
|
-IsRated
|
||||||
|
|
||||||
|
|
||||||
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields());
|
" + string.Join("\r\n", SearchEngine.FieldIndexRules.BoolFieldNames);
|
||||||
|
|
||||||
IdFields = @"
|
IdFields = @"
|
||||||
Alice's Adventures in
|
Alice's Adventures in
|
||||||
@ -49,7 +51,7 @@ All of these are synonyms
|
|||||||
for the ID field
|
for the ID field
|
||||||
|
|
||||||
|
|
||||||
" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields());
|
" + string.Join("\r\n", SearchEngine.FieldIndexRules.IdFieldNames);
|
||||||
|
|
||||||
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
|||||||
@ -390,7 +390,7 @@ $@" Title: {libraryBook.Book.Title}
|
|||||||
|
|
||||||
if (dialogResult == SkipResult)
|
if (dialogResult == SkipResult)
|
||||||
{
|
{
|
||||||
libraryBook.Book.UpdateBookStatus(LiberatedStatus.Error);
|
libraryBook.UpdateBookStatus(LiberatedStatus.Error);
|
||||||
|
|
||||||
Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
||||||
|
|
||||||
|
|||||||
@ -223,7 +223,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
else if (result == ProcessBookResult.FailedAbort)
|
else if (result == ProcessBookResult.FailedAbort)
|
||||||
Queue.ClearQueue();
|
Queue.ClearQueue();
|
||||||
else if (result == ProcessBookResult.FailedSkip)
|
else if (result == ProcessBookResult.FailedSkip)
|
||||||
nextBook.LibraryBook.Book.UpdateBookStatus(LiberatedStatus.Error);
|
nextBook.LibraryBook.UpdateBookStatus(LiberatedStatus.Error);
|
||||||
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
||||||
{
|
{
|
||||||
await MessageBox.Show(@$"
|
await MessageBox.Show(@$"
|
||||||
|
|||||||
@ -112,7 +112,7 @@ namespace LibationAvalonia.Views
|
|||||||
if (entry.Liberate.IsSeries)
|
if (entry.Liberate.IsSeries)
|
||||||
setDownloadMenuItem.Click += (_, __) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
|
setDownloadMenuItem.Click += (_, __) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
|
||||||
else
|
else
|
||||||
setDownloadMenuItem.Click += (_, __) => entry.Book.UpdateBookStatus(LiberatedStatus.Liberated);
|
setDownloadMenuItem.Click += (_, __) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Set Download status to Not Downloaded
|
#region Set Download status to Not Downloaded
|
||||||
@ -128,7 +128,7 @@ namespace LibationAvalonia.Views
|
|||||||
if (entry.Liberate.IsSeries)
|
if (entry.Liberate.IsSeries)
|
||||||
setNotDownloadMenuItem.Click += (_, __) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
|
setNotDownloadMenuItem.Click += (_, __) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
|
||||||
else
|
else
|
||||||
setNotDownloadMenuItem.Click += (_, __) => entry.Book.UpdateBookStatus(LiberatedStatus.NotLiberated);
|
setNotDownloadMenuItem.Click += (_, __) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Remove from library
|
#region Remove from library
|
||||||
|
|||||||
42
Source/LibationSearchEngine/IndexRule.cs
Normal file
42
Source/LibationSearchEngine/IndexRule.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using DataLayer;
|
||||||
|
using Dinah.Core;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LibationSearchEngine;
|
||||||
|
|
||||||
|
public enum FieldType
|
||||||
|
{
|
||||||
|
Bool,
|
||||||
|
String,
|
||||||
|
Number,
|
||||||
|
ID,
|
||||||
|
Raw
|
||||||
|
}
|
||||||
|
|
||||||
|
public class IndexRule
|
||||||
|
{
|
||||||
|
public FieldType FieldType { get; }
|
||||||
|
public Func<LibraryBook, string> GetValue { get; }
|
||||||
|
public ReadOnlyCollection<string> FieldNames { get; }
|
||||||
|
|
||||||
|
public IndexRule(FieldType fieldType, Func<LibraryBook, string> valueGetter, params string[] fieldNames)
|
||||||
|
{
|
||||||
|
ArgumentValidator.EnsureNotNull(valueGetter, nameof(valueGetter));
|
||||||
|
ArgumentValidator.EnsureNotNull(fieldNames, nameof(fieldNames));
|
||||||
|
ArgumentValidator.EnsureGreaterThan(fieldNames.Length, $"{nameof(fieldNames)}.{nameof(fieldNames.Length)}", 0);
|
||||||
|
var fieldNamesValidated
|
||||||
|
= fieldNames
|
||||||
|
.Select((n, i) => ArgumentValidator.EnsureNotNullOrWhiteSpace(n, $"{nameof(fieldNames)}[{i}]")
|
||||||
|
.Trim());
|
||||||
|
|
||||||
|
GetValue = valueGetter;
|
||||||
|
FieldType = fieldType;
|
||||||
|
FieldNames = new ReadOnlyCollection<string>(fieldNamesValidated.ToList());
|
||||||
|
}
|
||||||
|
public override string ToString()
|
||||||
|
=> FieldNames.Count == 1
|
||||||
|
? $"{FieldNames.First()}"
|
||||||
|
: $"{FieldNames.First()} ({string.Join(", ", FieldNames.Skip(1))})";
|
||||||
|
}
|
||||||
27
Source/LibationSearchEngine/IndexRuleCollection.cs
Normal file
27
Source/LibationSearchEngine/IndexRuleCollection.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using DataLayer;
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace LibationSearchEngine;
|
||||||
|
|
||||||
|
[DebuggerDisplay("Count = {rules.Count,nq}")]
|
||||||
|
public class IndexRuleCollection : IEnumerable<IndexRule>
|
||||||
|
{
|
||||||
|
private readonly List<IndexRule> rules = new();
|
||||||
|
public IEnumerable<string> IdFieldNames => rules.Where(x => x.FieldType is FieldType.ID).SelectMany(r => r.FieldNames);
|
||||||
|
public IEnumerable<string> BoolFieldNames => rules.Where(x => x.FieldType is FieldType.Bool).SelectMany(r => r.FieldNames);
|
||||||
|
public IEnumerable<string> StringFieldNames => rules.Where(x => x.FieldType is FieldType.String).SelectMany(r => r.FieldNames);
|
||||||
|
public IEnumerable<string> NumberFieldNames => rules.Where(x => x.FieldType is FieldType.Number).SelectMany(r => r.FieldNames);
|
||||||
|
|
||||||
|
public void Add(FieldType fieldType, Func<LibraryBook, string> getter, params string[] fieldNames)
|
||||||
|
=> rules.Add(new IndexRule(fieldType, getter, fieldNames));
|
||||||
|
|
||||||
|
public IndexRule GetRuleByFieldName(string fieldName)
|
||||||
|
=> rules.SingleOrDefault(r => r.FieldNames.Any(n => n.Equals(fieldName, StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
|
public IEnumerator<IndexRule> GetEnumerator() => rules.GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using DataLayer;
|
||||||
using Lucene.Net.Analysis;
|
using Lucene.Net.Analysis;
|
||||||
using Lucene.Net.Documents;
|
using Lucene.Net.Documents;
|
||||||
using Lucene.Net.QueryParsers;
|
using Lucene.Net.QueryParsers;
|
||||||
@ -11,20 +11,46 @@ namespace LibationSearchEngine
|
|||||||
// field names are case specific and, due to StandardAnalyzer, content is case INspecific
|
// field names are case specific and, due to StandardAnalyzer, content is case INspecific
|
||||||
internal static class LuceneExtensions
|
internal static class LuceneExtensions
|
||||||
{
|
{
|
||||||
internal static void AddRaw(this Document document, string name, string value)
|
|
||||||
=> document.Add(new Field(name, value, Field.Store.YES, Field.Index.NOT_ANALYZED));
|
|
||||||
|
|
||||||
internal static void AddAnalyzed(this Document document, string name, string value)
|
internal static void AddAnalyzed(this Document document, string name, string value)
|
||||||
{
|
{
|
||||||
if (value is not null)
|
if (value is not null)
|
||||||
document.Add(new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.ANALYZED));
|
document.Add(new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.ANALYZED));
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static void AddNotAnalyzed(this Document document, string name, string value)
|
internal static void RemoveRule(this Document document, IndexRule rule)
|
||||||
=> document.Add(new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.NOT_ANALYZED));
|
{
|
||||||
|
// fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY.
|
||||||
|
// ie: must remove old before adding new else will create unwanted duplicates.
|
||||||
|
foreach (var name in rule.FieldNames)
|
||||||
|
document.RemoveFields(name.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
internal static void AddBool(this Document document, string name, bool value)
|
internal static void AddIndexRule(this Document document, IndexRule rule, LibraryBook libraryBook)
|
||||||
=> document.Add(new Field(name.ToLowerInvariant(), value.ToString(), Field.Store.YES, Field.Index.ANALYZED_NO_NORMS));
|
{
|
||||||
|
string value = rule.GetValue(libraryBook);
|
||||||
|
if (value is null) return;
|
||||||
|
|
||||||
|
foreach (var name in rule.FieldNames)
|
||||||
|
{
|
||||||
|
// fields are key value pairs and MULTIPLE FIELDS CAN HAVE THE SAME KEY.
|
||||||
|
// splitting authors and narrators and/or tags into multiple fields could be interesting research.
|
||||||
|
// it could allow for more advanced searches, or maybe it could break broad searches.
|
||||||
|
|
||||||
|
// all searching should be lowercase
|
||||||
|
// external callers have the reasonable expectation that product id will be returned CASE SPECIFIC
|
||||||
|
var field = rule.FieldType switch
|
||||||
|
{
|
||||||
|
FieldType.Bool => new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.ANALYZED_NO_NORMS),
|
||||||
|
FieldType.String => new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.ANALYZED),
|
||||||
|
FieldType.Number => new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.NOT_ANALYZED),
|
||||||
|
FieldType.ID => new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.NOT_ANALYZED),
|
||||||
|
FieldType.Raw => new Field(name, value, Field.Store.YES, Field.Index.NOT_ANALYZED),
|
||||||
|
_ => throw new KeyNotFoundException(),
|
||||||
|
};
|
||||||
|
|
||||||
|
document.Add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal static Query GetQuery(this Analyzer analyzer, string defaultField, string searchString)
|
internal static Query GetQuery(this Analyzer analyzer, string defaultField, string searchString)
|
||||||
=> new QueryParser(SearchEngine.Version, defaultField.ToLowerInvariant(), analyzer).Parse(searchString);
|
=> new QueryParser(SearchEngine.Version, defaultField.ToLowerInvariant(), analyzer).Parse(searchString);
|
||||||
|
|||||||
@ -8,21 +8,19 @@ namespace LibationSearchEngine
|
|||||||
internal static class QuerySanitizer
|
internal static class QuerySanitizer
|
||||||
{
|
{
|
||||||
private static readonly HashSet<string> idTerms
|
private static readonly HashSet<string> idTerms
|
||||||
= SearchEngine.idIndexRules.Keys
|
= SearchEngine.FieldIndexRules.IdFieldNames
|
||||||
.Select(s => s.ToLowerInvariant())
|
.Select(n => n.ToLowerInvariant())
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
|
|
||||||
private static readonly HashSet<string> boolTerms
|
private static readonly HashSet<string> boolTerms
|
||||||
= SearchEngine.boolIndexRules.Keys
|
= SearchEngine.FieldIndexRules.BoolFieldNames
|
||||||
.Select(s => s.ToLowerInvariant())
|
.Select(n => n.ToLowerInvariant())
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
|
|
||||||
private static readonly HashSet<string> fieldTerms
|
private static readonly HashSet<string> fieldTerms
|
||||||
= SearchEngine.stringIndexRules.Keys
|
= SearchEngine.FieldIndexRules
|
||||||
.Union(SearchEngine.numberIndexRules.Keys)
|
.SelectMany(r => r.FieldNames)
|
||||||
.Select(s => s.ToLowerInvariant())
|
.Select(n => n.ToLowerInvariant())
|
||||||
.Union(idTerms)
|
|
||||||
.Union(boolTerms)
|
|
||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
|
|
||||||
internal static string Sanitize(string searchString, StandardAnalyzer analyzer)
|
internal static string Sanitize(string searchString, StandardAnalyzer analyzer)
|
||||||
|
|||||||
@ -1,13 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using Lucene.Net.Analysis.Standard;
|
using Lucene.Net.Analysis.Standard;
|
||||||
using Lucene.Net.Analysis.Tokenattributes;
|
|
||||||
using Lucene.Net.Documents;
|
using Lucene.Net.Documents;
|
||||||
using Lucene.Net.Index;
|
using Lucene.Net.Index;
|
||||||
using Lucene.Net.Search;
|
using Lucene.Net.Search;
|
||||||
@ -25,166 +22,49 @@ namespace LibationSearchEngine
|
|||||||
public const string ALL = "all";
|
public const string ALL = "all";
|
||||||
|
|
||||||
#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";
|
|
||||||
public const string ALL_SERIES_NAMES = "SeriesNames";
|
|
||||||
|
|
||||||
internal static ReadOnlyDictionary<string, Func<LibraryBook, string>> idIndexRules { get; }
|
private static bool isAuthorNarrated(Book book)
|
||||||
= new ReadOnlyDictionary<string, Func<LibraryBook, string>>(
|
|
||||||
new Dictionary<string, Func<LibraryBook, string>>
|
|
||||||
{
|
{
|
||||||
[nameof(Book.AudibleProductId)] = lb => lb.Book.AudibleProductId.ToLowerInvariant(),
|
var authors = book.Authors.Select(a => a.Name).ToArray();
|
||||||
["ProductId"] = lb => lb.Book.AudibleProductId.ToLowerInvariant(),
|
var narrators = book.Narrators.Select(a => a.Name).ToArray();
|
||||||
["Id"] = lb => lb.Book.AudibleProductId.ToLowerInvariant(),
|
|
||||||
["ASIN"] = lb => lb.Book.AudibleProductId.ToLowerInvariant()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
internal static ReadOnlyDictionary<string, Func<LibraryBook, string>> stringIndexRules { get; }
|
|
||||||
= new ReadOnlyDictionary<string, Func<LibraryBook, string>>(
|
|
||||||
new Dictionary<string, Func<LibraryBook, string>>
|
|
||||||
{
|
|
||||||
[nameof(Book.Title)] = lb => lb.Book.Title,
|
|
||||||
[ALL_AUTHOR_NAMES] = lb => lb.Book.AuthorNames(),
|
|
||||||
["Author"] = lb => lb.Book.AuthorNames(),
|
|
||||||
["Authors"] = lb => lb.Book.AuthorNames(),
|
|
||||||
[ALL_NARRATOR_NAMES] = lb => lb.Book.NarratorNames(),
|
|
||||||
["Narrator"] = lb => lb.Book.NarratorNames(),
|
|
||||||
["Narrators"] = lb => lb.Book.NarratorNames(),
|
|
||||||
[nameof(Book.Publisher)] = lb => lb.Book.Publisher,
|
|
||||||
|
|
||||||
[ALL_SERIES_NAMES] = lb => lb.Book.SeriesNames(),
|
|
||||||
["Series"] = lb => lb.Book.SeriesNames(),
|
|
||||||
["SeriesId"] = lb => string.Join(", ", lb.Book.SeriesLink.Select(s => s.Series.AudibleSeriesId)),
|
|
||||||
|
|
||||||
["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()),
|
|
||||||
["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()),
|
|
||||||
["CategoryId"] = lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()),
|
|
||||||
|
|
||||||
[TAGS.FirstCharToUpper()] = lb => lb.Book.UserDefinedItem.Tags,
|
|
||||||
|
|
||||||
["Locale"] = lb => lb.Book.Locale,
|
|
||||||
["Region"] = lb => lb.Book.Locale,
|
|
||||||
["Account"] = lb => lb.Account,
|
|
||||||
["Email"] = lb => lb.Account
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
internal static ReadOnlyDictionary<string, Func<LibraryBook, string>> numberIndexRules { get; }
|
|
||||||
= new ReadOnlyDictionary<string, Func<LibraryBook, string>>(
|
|
||||||
new Dictionary<string, Func<LibraryBook, string>>
|
|
||||||
{
|
|
||||||
// for now, all numbers are padded to 8 char.s
|
|
||||||
// This will allow a single method to auto-pad numbers. The method will match these as well as date: yyyymmdd
|
|
||||||
[nameof(Book.LengthInMinutes)] = lb => lb.Book.LengthInMinutes.ToLuceneString(),
|
|
||||||
["Length"] = lb => lb.Book.LengthInMinutes.ToLuceneString(),
|
|
||||||
["Minutes"] = lb => lb.Book.LengthInMinutes.ToLuceneString(),
|
|
||||||
["Hours"] = lb => (lb.Book.LengthInMinutes / 60).ToLuceneString(),
|
|
||||||
|
|
||||||
["ProductRating"] = lb => lb.Book.Rating.OverallRating.ToLuceneString(),
|
|
||||||
["Rating"] = lb => lb.Book.Rating.OverallRating.ToLuceneString(),
|
|
||||||
["UserRating"] = lb => userOverallRating(lb.Book),
|
|
||||||
["MyRating"] = lb => userOverallRating(lb.Book),
|
|
||||||
|
|
||||||
[nameof(LibraryBook.DateAdded)] = lb => lb.DateAdded.ToLuceneString(),
|
|
||||||
[nameof(Book.DatePublished)] = lb => lb.Book.DatePublished?.ToLuceneString() ?? "",
|
|
||||||
|
|
||||||
["LastDownload"] = lb => lb.Book.UserDefinedItem.LastDownloaded.ToLuceneString(),
|
|
||||||
["LastDownloaded"] = lb => lb.Book.UserDefinedItem.LastDownloaded.ToLuceneString()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
internal static ReadOnlyDictionary<string, Func<LibraryBook, bool>> boolIndexRules { get; }
|
|
||||||
= new ReadOnlyDictionary<string, Func<LibraryBook, bool>>(
|
|
||||||
new Dictionary<string, Func<LibraryBook, bool>>
|
|
||||||
{
|
|
||||||
["HasDownloads"] = lb => lb.Book.HasPdf(),
|
|
||||||
["HasDownload"] = lb => lb.Book.HasPdf(),
|
|
||||||
["Downloads"] = lb => lb.Book.HasPdf(),
|
|
||||||
["Download"] = lb => lb.Book.HasPdf(),
|
|
||||||
["HasPDFs"] = lb => lb.Book.HasPdf(),
|
|
||||||
["HasPDF"] = lb => lb.Book.HasPdf(),
|
|
||||||
["PDFs"] = lb => lb.Book.HasPdf(),
|
|
||||||
["PDF"] = lb => lb.Book.HasPdf(),
|
|
||||||
|
|
||||||
["IsRated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
|
|
||||||
["Rated"] = lb => lb.Book.UserDefinedItem.Rating.OverallRating > 0f,
|
|
||||||
|
|
||||||
["IsAuthorNarrated"] = isAuthorNarrated,
|
|
||||||
["AuthorNarrated"] = isAuthorNarrated,
|
|
||||||
|
|
||||||
[nameof(Book.IsAbridged)] = lb => lb.Book.IsAbridged,
|
|
||||||
["Abridged"] = lb => lb.Book.IsAbridged,
|
|
||||||
|
|
||||||
["IsLiberated"] = lb => isLiberated(lb.Book),
|
|
||||||
["Liberated"] = lb => isLiberated(lb.Book),
|
|
||||||
["LiberatedError"] = lb => liberatedError(lb.Book),
|
|
||||||
|
|
||||||
["Podcast"] = lb => lb.Book.IsEpisodeChild(),
|
|
||||||
["Podcasts"] = lb => lb.Book.IsEpisodeChild(),
|
|
||||||
["IsPodcast"] = lb => lb.Book.IsEpisodeChild(),
|
|
||||||
["Episode"] = lb => lb.Book.IsEpisodeChild(),
|
|
||||||
["Episodes"] = lb => lb.Book.IsEpisodeChild(),
|
|
||||||
["IsEpisode"] = lb => lb.Book.IsEpisodeChild(),
|
|
||||||
|
|
||||||
["Absent"] = lb => lb.AbsentFromLastScan,
|
|
||||||
["AbsentFromLastScan"] = lb => lb.AbsentFromLastScan,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
private static bool isAuthorNarrated(LibraryBook lb)
|
|
||||||
{
|
|
||||||
var authors = lb.Book.Authors.Select(a => a.Name).ToArray();
|
|
||||||
var narrators = lb.Book.Narrators.Select(a => a.Name).ToArray();
|
|
||||||
return authors.Intersect(narrators).Any();
|
return authors.Intersect(narrators).Any();
|
||||||
}
|
}
|
||||||
private static string userOverallRating(Book book) => book.UserDefinedItem.Rating.OverallRating.ToLuceneString();
|
|
||||||
private static bool isLiberated(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Liberated;
|
|
||||||
private static bool liberatedError(Book book) => book.UserDefinedItem.BookStatus == LiberatedStatus.Error;
|
|
||||||
|
|
||||||
// use these common fields in the "all" default search field
|
// use these common fields in the "all" default search field
|
||||||
private static IEnumerable<Func<LibraryBook, string>> allFieldIndexRules { get; }
|
public static IndexRuleCollection FieldIndexRules { get; } = new IndexRuleCollection
|
||||||
= new List<Func<LibraryBook, string>>
|
|
||||||
{
|
{
|
||||||
idIndexRules[ALL_AUDIBLE_PRODUCT_ID],
|
{ FieldType.ID, lb => lb.Book.AudibleProductId.ToLowerInvariant(), nameof(Book.AudibleProductId), "ProductId", "Id", "ASIN" },
|
||||||
stringIndexRules[ALL_TITLE],
|
{ FieldType.Raw, lb => lb.Book.AudibleProductId, _ID_ },
|
||||||
stringIndexRules[ALL_AUTHOR_NAMES],
|
{ FieldType.String, lb => lb.Book.Title, nameof(Book.Title), "ProductId", "Id", "ASIN" },
|
||||||
stringIndexRules[ALL_NARRATOR_NAMES],
|
{ FieldType.String, lb => lb.Book.AuthorNames(), "AuthorNames", "Author", "Authors" },
|
||||||
stringIndexRules[ALL_SERIES_NAMES]
|
{ FieldType.String, lb => lb.Book.NarratorNames(), "NarratorNames", "Narrator", "Narrators" },
|
||||||
|
{ FieldType.String, lb => lb.Book.Publisher, nameof(Book.Publisher) },
|
||||||
|
{ FieldType.String, lb => lb.Book.SeriesNames(), "SeriesNames", "Narrator", "Series" },
|
||||||
|
{ FieldType.String, lb => string.Join(", ", lb.Book.SeriesLink.Select(s => s.Series.AudibleSeriesId)), "SeriesId" },
|
||||||
|
{ FieldType.String, lb => lb.Book.CategoriesIds() is null ? null : string.Join(", ", lb.Book.CategoriesIds()), nameof(Book.Category), "Categories", "CategoriesId", "CategoryId", "CategoriesNames" },
|
||||||
|
{ FieldType.String, lb => lb.Book.UserDefinedItem.Tags, TAGS.FirstCharToUpper() },
|
||||||
|
{ FieldType.String, lb => lb.Book.Locale, "Locale", "Region" },
|
||||||
|
{ FieldType.String, lb => lb.Account, "Account", "Email" },
|
||||||
|
{ FieldType.Bool, lb => lb.Book.HasPdf().ToString(), "HasDownloads", "HasDownload", "Downloads" , "Download", "HasPDFs", "HasPDF" , "PDFs", "PDF" },
|
||||||
|
{ FieldType.Bool, lb => (lb.Book.UserDefinedItem.Rating.OverallRating > 0f).ToString(), "IsRated", "Rated" },
|
||||||
|
{ FieldType.Bool, lb => isAuthorNarrated(lb.Book).ToString(), "IsAuthorNarrated", "AuthorNarrated" },
|
||||||
|
{ FieldType.Bool, lb => lb.Book.IsAbridged.ToString(), nameof(Book.IsAbridged), "Abridged" },
|
||||||
|
{ FieldType.Bool, lb => (lb.Book.UserDefinedItem.BookStatus == LiberatedStatus.Liberated).ToString(), "IsLiberated", "Liberated" },
|
||||||
|
{ FieldType.Bool, lb => (lb.Book.UserDefinedItem.BookStatus == LiberatedStatus.Error).ToString(), "LiberatedError" },
|
||||||
|
{ FieldType.Bool, lb => lb.Book.IsEpisodeChild().ToString(), "Podcast", "Podcasts", "IsPodcast", "Episode", "Episodes", "IsEpisode" },
|
||||||
|
{ FieldType.Bool, lb => lb.AbsentFromLastScan.ToString(), "AbsentFromLastScan", "Absent" },
|
||||||
|
// all numbers are padded to 8 char.s
|
||||||
|
// This will allow a single method to auto-pad numbers. The method will match these as well as date: yyyymmdd
|
||||||
|
{ FieldType.Number, lb => lb.Book.LengthInMinutes.ToLuceneString(), nameof(Book.LengthInMinutes), "Length", "Minutes" },
|
||||||
|
{ FieldType.Number, lb => (lb.Book.LengthInMinutes / 60).ToLuceneString(), "Hours" },
|
||||||
|
{ FieldType.Number, lb => lb.Book.Rating.OverallRating.ToLuceneString(), "ProductRating", "Rating" },
|
||||||
|
{ FieldType.Number, lb => lb.Book.UserDefinedItem.Rating.OverallRating.ToLuceneString(), "UserRating", "MyRating" },
|
||||||
|
{ FieldType.Number, lb => lb.Book.DatePublished?.ToLuceneString() ?? "", nameof(Book.DatePublished) },
|
||||||
|
{ FieldType.Number, lb => lb.Book.UserDefinedItem.LastDownloaded.ToLuceneString(), nameof(UserDefinedItem.LastDownloaded), "LastDownload" },
|
||||||
|
{ FieldType.Number, lb => lb.DateAdded.ToLuceneString(), nameof(LibraryBook.DateAdded) }
|
||||||
};
|
};
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region get search fields. used for display in help
|
|
||||||
public static IEnumerable<string> GetSearchIdFields()
|
|
||||||
{
|
|
||||||
foreach (var key in idIndexRules.Keys)
|
|
||||||
yield return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<string> GetSearchStringFields()
|
|
||||||
{
|
|
||||||
foreach (var key in stringIndexRules.Keys)
|
|
||||||
yield return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<string> GetSearchBoolFields()
|
|
||||||
{
|
|
||||||
foreach (var key in boolIndexRules.Keys)
|
|
||||||
yield return key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<string> GetSearchNumberFields()
|
|
||||||
{
|
|
||||||
foreach (var key in numberIndexRules.Keys)
|
|
||||||
yield return key;
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region create and update index
|
#region create and update index
|
||||||
/// <summary>create new. ie: full re-index</summary>
|
/// <summary>create new. ie: full re-index</summary>
|
||||||
public void CreateNewIndex(IEnumerable<LibraryBook> library, bool overwrite = true)
|
public void CreateNewIndex(IEnumerable<LibraryBook> library, bool overwrite = true)
|
||||||
@ -224,35 +104,15 @@ namespace LibationSearchEngine
|
|||||||
{
|
{
|
||||||
var doc = new Document();
|
var doc = new Document();
|
||||||
|
|
||||||
// refine with
|
|
||||||
// http://codeclimber.net.nz/archive/2009/09/10/how-subtext-lucenenet-index-is-structured/
|
|
||||||
|
|
||||||
// fields are key value pairs and MULTIPLE FIELDS CAN HAVE THE SAME KEY.
|
|
||||||
// splitting authors and narrators and/or tags into multiple fields could be interesting research.
|
|
||||||
// it could allow for more advanced searches, or maybe it could break broad searches.
|
|
||||||
|
|
||||||
// all searching should be lowercase
|
|
||||||
// external callers have the reasonable expectation that product id will be returned CASE SPECIFIC
|
|
||||||
doc.AddRaw(_ID_, libraryBook.Book.AudibleProductId);
|
|
||||||
|
|
||||||
// concat all common fields for the default 'all' field
|
// concat all common fields for the default 'all' field
|
||||||
var allConcat =
|
var allConcat =
|
||||||
allFieldIndexRules
|
FieldIndexRules
|
||||||
.Select(rule => rule(libraryBook))
|
.Select(rule => rule.GetValue(libraryBook))
|
||||||
.Aggregate((a, b) => $"{a} {b}");
|
.Aggregate((a, b) => $"{a} {b}");
|
||||||
doc.AddAnalyzed(ALL, allConcat);
|
doc.AddAnalyzed(ALL, allConcat);
|
||||||
|
|
||||||
foreach (var kvp in idIndexRules)
|
foreach (var rule in FieldIndexRules)
|
||||||
doc.AddNotAnalyzed(kvp.Key, kvp.Value(libraryBook));
|
doc.AddIndexRule(rule, libraryBook);
|
||||||
|
|
||||||
foreach (var kvp in stringIndexRules)
|
|
||||||
doc.AddAnalyzed(kvp.Key, kvp.Value(libraryBook));
|
|
||||||
|
|
||||||
foreach (var kvp in boolIndexRules)
|
|
||||||
doc.AddBool(kvp.Key, kvp.Value(libraryBook));
|
|
||||||
|
|
||||||
foreach (var kvp in numberIndexRules)
|
|
||||||
doc.AddNotAnalyzed(kvp.Key, kvp.Value(libraryBook));
|
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
@ -267,57 +127,38 @@ namespace LibationSearchEngine
|
|||||||
productId,
|
productId,
|
||||||
d =>
|
d =>
|
||||||
{
|
{
|
||||||
// fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY.
|
|
||||||
// ie: must remove old before adding new else will create unwanted duplicates.
|
|
||||||
d.RemoveField(fieldName.ToLower());
|
d.RemoveField(fieldName.ToLower());
|
||||||
d.AddAnalyzed(fieldName, newValue);
|
d.AddAnalyzed(fieldName, newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
// update single document entry
|
// update single document entry
|
||||||
public void UpdateLiberatedStatus(Book book)
|
public void UpdateLiberatedStatus(LibraryBook book)
|
||||||
=> updateDocument(
|
=> updateDocument(
|
||||||
book.AudibleProductId,
|
book.Book.AudibleProductId,
|
||||||
d =>
|
d =>
|
||||||
{
|
{
|
||||||
//
|
var lib = FieldIndexRules.GetRuleByFieldName("IsLiberated");
|
||||||
// TODO: better synonym handling. This is too easy to mess up
|
var libError = FieldIndexRules.GetRuleByFieldName("LiberatedError");
|
||||||
//
|
var lastDl = FieldIndexRules.GetRuleByFieldName(nameof(UserDefinedItem.LastDownloaded));
|
||||||
|
|
||||||
// fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY.
|
d.RemoveRule(lib);
|
||||||
// ie: must remove old before adding new else will create unwanted duplicates.
|
d.RemoveRule(libError);
|
||||||
var v1 = isLiberated(book);
|
d.RemoveRule(lastDl);
|
||||||
d.RemoveField("isliberated");
|
|
||||||
d.AddBool("IsLiberated", v1);
|
|
||||||
d.RemoveField("liberated");
|
|
||||||
d.AddBool("Liberated", v1);
|
|
||||||
|
|
||||||
var v2 = liberatedError(book);
|
d.AddIndexRule(lib, book);
|
||||||
d.RemoveField("liberatederror");
|
d.AddIndexRule(libError, book);
|
||||||
d.AddBool("LiberatedError", v2);
|
d.AddIndexRule(lastDl, book);
|
||||||
|
|
||||||
var v3 = book.UserDefinedItem.LastDownloaded?.ToLuceneString() ?? "";
|
|
||||||
d.RemoveField("LastDownload");
|
|
||||||
d.AddNotAnalyzed("LastDownload", v3);
|
|
||||||
d.RemoveField("LastDownloaded");
|
|
||||||
d.AddNotAnalyzed("LastDownloaded", v3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
public void UpdateUserRatings(Book book)
|
public void UpdateUserRatings(LibraryBook book)
|
||||||
=>updateDocument(
|
=>updateDocument(
|
||||||
book.AudibleProductId,
|
book.Book.AudibleProductId,
|
||||||
d =>
|
d =>
|
||||||
{
|
{
|
||||||
//
|
var rating = FieldIndexRules.GetRuleByFieldName("UserRating");
|
||||||
// TODO: better synonym handling. This is too easy to mess up
|
|
||||||
//
|
|
||||||
|
|
||||||
// fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY.
|
d.RemoveRule(rating);
|
||||||
// ie: must remove old before adding new else will create unwanted duplicates.
|
d.AddIndexRule(rating, book);
|
||||||
var v1 = userOverallRating(book);
|
|
||||||
d.RemoveField("userrating");
|
|
||||||
d.AddNotAnalyzed("UserRating", v1);
|
|
||||||
d.RemoveField("myrating");
|
|
||||||
d.AddNotAnalyzed("MyRating", v1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
private static void updateDocument(string productId, Action<Document> action)
|
private static void updateDocument(string productId, Action<Document> action)
|
||||||
@ -335,11 +176,9 @@ namespace LibationSearchEngine
|
|||||||
return;
|
return;
|
||||||
var document = searcher.Doc(scoreDoc.Doc);
|
var document = searcher.Doc(scoreDoc.Doc);
|
||||||
|
|
||||||
|
|
||||||
// perform update
|
// perform update
|
||||||
action(document);
|
action(document);
|
||||||
|
|
||||||
|
|
||||||
// update index
|
// update index
|
||||||
var createNewIndex = false;
|
var createNewIndex = false;
|
||||||
using var analyzer = new StandardAnalyzer(Version);
|
using var analyzer = new StandardAnalyzer(Version);
|
||||||
|
|||||||
@ -88,7 +88,7 @@ namespace LibationUiBase.GridView
|
|||||||
var api = await LibraryBook.GetApiAsync();
|
var api = await LibraryBook.GetApiAsync();
|
||||||
|
|
||||||
if (await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating))
|
if (await api.ReviewAsync(Book.AudibleProductId, (int)rating.OverallRating, (int)rating.PerformanceRating, (int)rating.StoryRating))
|
||||||
LibraryBook.Book.UpdateUserDefinedItem(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, rating);
|
LibraryBook.UpdateUserDefinedItem(Book.UserDefinedItem.Tags, Book.UserDefinedItem.BookStatus, Book.UserDefinedItem.PdfStatus, rating);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using System;
|
using LibationSearchEngine;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
@ -10,10 +11,10 @@ namespace LibationWinForms.Dialogs
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
label2.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchStringFields());
|
label2.Text += "\r\n\r\n" + string.Join("\r\n", SearchEngine.FieldIndexRules.StringFieldNames);
|
||||||
label3.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchNumberFields());
|
label3.Text += "\r\n\r\n" + string.Join("\r\n", SearchEngine.FieldIndexRules.NumberFieldNames);
|
||||||
label4.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchBoolFields());
|
label4.Text += "\r\n\r\n" + string.Join("\r\n", SearchEngine.FieldIndexRules.BoolFieldNames);
|
||||||
label5.Text += "\r\n\r\n" + string.Join("\r\n", LibationSearchEngine.SearchEngine.GetSearchIdFields());
|
label5.Text += "\r\n\r\n" + string.Join("\r\n", SearchEngine.FieldIndexRules.IdFieldNames);
|
||||||
this.SetLibationIcon();
|
this.SetLibationIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -93,7 +93,7 @@ namespace LibationWinForms.GridView
|
|||||||
{
|
{
|
||||||
var bookDetailsForm = new BookDetailsDialog(liveGridEntry.LibraryBook);
|
var bookDetailsForm = new BookDetailsDialog(liveGridEntry.LibraryBook);
|
||||||
if (bookDetailsForm.ShowDialog() == DialogResult.OK)
|
if (bookDetailsForm.ShowDialog() == DialogResult.OK)
|
||||||
liveGridEntry.Book.UpdateUserDefinedItem(bookDetailsForm.NewTags, bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
liveGridEntry.LibraryBook.UpdateUserDefinedItem(bookDetailsForm.NewTags, bookDetailsForm.BookLiberatedStatus, bookDetailsForm.PdfLiberatedStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -131,7 +131,7 @@ namespace LibationWinForms.GridView
|
|||||||
if (entry.Liberate.IsSeries)
|
if (entry.Liberate.IsSeries)
|
||||||
setDownloadMenuItem.Click += (_, _) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
|
setDownloadMenuItem.Click += (_, _) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.Liberated);
|
||||||
else
|
else
|
||||||
setDownloadMenuItem.Click += (_, _) => entry.Book.UpdateBookStatus(LiberatedStatus.Liberated);
|
setDownloadMenuItem.Click += (_, _) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.Liberated);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Set Download status to Not Downloaded
|
#region Set Download status to Not Downloaded
|
||||||
@ -147,7 +147,7 @@ namespace LibationWinForms.GridView
|
|||||||
if (entry.Liberate.IsSeries)
|
if (entry.Liberate.IsSeries)
|
||||||
setNotDownloadMenuItem.Click += (_, _) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
|
setNotDownloadMenuItem.Click += (_, _) => ((ISeriesEntry)entry).Children.Select(c => c.LibraryBook).UpdateBookStatus(LiberatedStatus.NotLiberated);
|
||||||
else
|
else
|
||||||
setNotDownloadMenuItem.Click += (_, _) => entry.Book.UpdateBookStatus(LiberatedStatus.NotLiberated);
|
setNotDownloadMenuItem.Click += (_, _) => entry.LibraryBook.UpdateBookStatus(LiberatedStatus.NotLiberated);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
#region Remove from library
|
#region Remove from library
|
||||||
|
|||||||
@ -377,7 +377,7 @@ $@" Title: {libraryBook.Book.Title}
|
|||||||
|
|
||||||
if (dialogResult == SkipResult)
|
if (dialogResult == SkipResult)
|
||||||
{
|
{
|
||||||
libraryBook.Book.UpdateBookStatus(LiberatedStatus.Error);
|
libraryBook.UpdateBookStatus(LiberatedStatus.Error);
|
||||||
|
|
||||||
Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}");
|
||||||
|
|
||||||
|
|||||||
@ -184,7 +184,7 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
else if (result == ProcessBookResult.FailedAbort)
|
else if (result == ProcessBookResult.FailedAbort)
|
||||||
Queue.ClearQueue();
|
Queue.ClearQueue();
|
||||||
else if (result == ProcessBookResult.FailedSkip)
|
else if (result == ProcessBookResult.FailedSkip)
|
||||||
nextBook.LibraryBook.Book.UpdateBookStatus(DataLayer.LiberatedStatus.Error);
|
nextBook.LibraryBook.UpdateBookStatus(DataLayer.LiberatedStatus.Error);
|
||||||
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
||||||
{
|
{
|
||||||
MessageBox.Show(@$"
|
MessageBox.Show(@$"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user