Refactor LibationSearchEngine

This commit is contained in:
Mbucari 2023-06-12 13:22:35 -06:00
parent 4ab6da132b
commit b4aa220051
9 changed files with 235 additions and 266 deletions

View File

@ -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;

View File

@ -0,0 +1,21 @@
using DataLayer;
using System;
using System.Collections.ObjectModel;
namespace LibationSearchEngine;
public class BookRule : IIndexRule
{
public FieldType FieldType { get; }
public Func<Book, string> ValueGetter { get; }
public ReadOnlyCollection<string> FieldNames { get; }
public BookRule(FieldType fieldType, Func<Book, string> valueGetter, params string[] fieldNames)
{
ValueGetter = valueGetter;
FieldType = fieldType;
FieldNames = new ReadOnlyCollection<string>(fieldNames);
}
public string GetValue(LibraryBook libraryBook) => ValueGetter(libraryBook.Book);
}

View File

@ -0,0 +1,22 @@
using DataLayer;
using System.Collections.ObjectModel;
namespace LibationSearchEngine;
public enum FieldType
{
Bool,
String,
Number,
ID,
Raw
}
public interface IIndexRule
{
/// <summary> This rule's value type. </summary>
FieldType FieldType { get; }
/// <summary> All aliases of this search index rule </summary>
ReadOnlyCollection<string> FieldNames { get; }
string GetValue(LibraryBook libraryBook);
}

View File

@ -0,0 +1,28 @@
using DataLayer;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace LibationSearchEngine;
public class IndexRuleCollection : IEnumerable<IIndexRule>
{
private readonly List<IIndexRule> 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 LibraryBookRule(fieldType, getter, fieldNames));
public void Add(FieldType fieldType, Func<Book, string> getter, params string[] fieldNames)
=> rules.Add(new BookRule(fieldType, getter, fieldNames));
public T GetRuleByFieldName<T>(string fieldName) where T : IIndexRule
=> (T)rules.SingleOrDefault(r => r.FieldNames.Any(n => n.Equals(fieldName, StringComparison.OrdinalIgnoreCase)));
public IEnumerator<IIndexRule> GetEnumerator() => rules.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@ -0,0 +1,21 @@
using DataLayer;
using System;
using System.Collections.ObjectModel;
namespace LibationSearchEngine;
public class LibraryBookRule : IIndexRule
{
public FieldType FieldType { get; }
public Func<LibraryBook, string> ValueGetter { get; }
public ReadOnlyCollection<string> FieldNames { get; }
public LibraryBookRule(FieldType fieldType, Func<LibraryBook, string> valueGetter, params string[] fieldNames)
{
ValueGetter = valueGetter;
FieldType = fieldType;
FieldNames = new ReadOnlyCollection<string>(fieldNames);
}
public string GetValue(LibraryBook libraryBook) => ValueGetter(libraryBook);
}

View File

@ -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;
@ -10,21 +10,58 @@ 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) internal static void AddAnalyzed(this Document document, string name, string value)
=> document.Add(new Field(name, value, Field.Store.YES, Field.Index.NOT_ANALYZED)); {
if (value is not null)
document.Add(new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.ANALYZED));
}
internal static void AddAnalyzed(this Document document, string name, string value) internal static void RemoveRule(this Document document, IIndexRule rule)
{ {
if (value is not null) // fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY.
document.Add(new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.ANALYZED)); // 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 AddNotAnalyzed(this Document document, string name, string value) internal static void AddIndexRule(this Document document, IIndexRule rule, LibraryBook libraryBook)
=> document.Add(new Field(name.ToLowerInvariant(), value, Field.Store.YES, Field.Index.NOT_ANALYZED)); {
string value = rule.GetValue(libraryBook);
internal static void AddBool(this Document document, string name, bool value) addIndexRule(document, rule, value);
=> document.Add(new Field(name.ToLowerInvariant(), value.ToString(), Field.Store.YES, Field.Index.ANALYZED_NO_NORMS)); }
internal static void AddIndexRule(this Document document, BookRule rule, Book libraryBook)
{
addIndexRule(document, rule, rule.ValueGetter(libraryBook));
}
private static void addIndexRule(Document document, IIndexRule rule, string value)
{
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);

View File

@ -8,22 +8,20 @@ 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) .ToHashSet();
.Union(boolTerms)
.ToHashSet();
internal static string Sanitize(string searchString, StandardAnalyzer analyzer) internal static string Sanitize(string searchString, StandardAnalyzer analyzer)
{ {

View File

@ -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,164 +22,47 @@ 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(),
["ProductId"] = lb => lb.Book.AudibleProductId.ToLowerInvariant(),
["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 authors = book.Authors.Select(a => a.Name).ToArray();
var narrators = lb.Book.Narrators.Select(a => a.Name).ToArray(); var narrators = 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],
stringIndexRules[ALL_TITLE],
stringIndexRules[ALL_AUTHOR_NAMES],
stringIndexRules[ALL_NARRATOR_NAMES],
stringIndexRules[ALL_SERIES_NAMES]
};
#endregion
#region get search fields. used for display in help
public static IEnumerable<string> GetSearchIdFields()
{ {
foreach (var key in idIndexRules.Keys) { FieldType.ID, Book => Book.AudibleProductId.ToLowerInvariant(), nameof(Book.AudibleProductId), "ProductId", "Id", "ASIN" },
yield return key; { FieldType.Raw, Book => Book.AudibleProductId, _ID_ },
} { FieldType.String, Book => Book.Title, nameof(Book.Title), "ProductId", "Id", "ASIN" },
{ FieldType.String, Book => Book.AuthorNames(), "AuthorNames", "Author", "Authors" },
public static IEnumerable<string> GetSearchStringFields() { FieldType.String, Book => Book.NarratorNames(), "NarratorNames", "Narrator", "Narrators" },
{ { FieldType.String, Book => Book.Publisher, nameof(Book.Publisher) },
foreach (var key in stringIndexRules.Keys) { FieldType.String, Book => Book.SeriesNames(), "SeriesNames", "Narrator", "Series" },
yield return key; { FieldType.String, Book => string.Join(", ", Book.SeriesLink.Select(s => s.Series.AudibleSeriesId)), "SeriesId" },
} { FieldType.String, Book => Book.CategoriesIds() is null ? null : string.Join(", ", Book.CategoriesIds()), nameof(Book.Category), "Categories", "CategoriesId", "CategoryId", "CategoriesNames" },
{ FieldType.String, Book => Book.UserDefinedItem.Tags, TAGS.FirstCharToUpper() },
public static IEnumerable<string> GetSearchBoolFields() { FieldType.String, Book => Book.Locale, "Locale", "Region" },
{ { FieldType.String, lb => lb.Account, "Account", "Email" },
foreach (var key in boolIndexRules.Keys) { FieldType.Bool, Book => Book.HasPdf().ToString(), "HasDownloads", "HasDownload", "Downloads" , "Download", "HasPDFs", "HasPDF" , "PDFs", "PDF" },
yield return key; { FieldType.Bool, Book => (Book.UserDefinedItem.Rating.OverallRating > 0f).ToString(), "IsRated", "Rated" },
} { FieldType.Bool, Book => isAuthorNarrated(Book).ToString(), "IsAuthorNarrated", "AuthorNarrated" },
{ FieldType.Bool, Book => Book.IsAbridged.ToString(), nameof(Book.IsAbridged), "Abridged" },
public static IEnumerable<string> GetSearchNumberFields() { FieldType.Bool, Book => (Book.UserDefinedItem.BookStatus == LiberatedStatus.Liberated).ToString(), "IsLiberated", "Liberated" },
{ { FieldType.Bool, Book => (Book.UserDefinedItem.BookStatus == LiberatedStatus.Error).ToString(), "LiberatedError" },
foreach (var key in numberIndexRules.Keys) { FieldType.Bool, Book => Book.IsEpisodeChild().ToString(), "Podcast", "Podcasts", "IsPodcast", "Episode", "Episodes", "IsEpisode" },
yield return key; { 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, Book => Book.LengthInMinutes.ToLuceneString(), nameof(Book.LengthInMinutes), "Length", "Minutes" },
{ FieldType.Number, Book => (Book.LengthInMinutes / 60).ToLuceneString(), "Hours" },
{ FieldType.Number, Book => Book.Rating.OverallRating.ToLuceneString(), "ProductRating", "Rating" },
{ FieldType.Number, Book => Book.UserDefinedItem.Rating.OverallRating.ToLuceneString(), "UserRating", "MyRating" },
{ FieldType.Number, Book => Book.DatePublished?.ToLuceneString() ?? "", nameof(Book.DatePublished) },
{ FieldType.Number, Book => Book.UserDefinedItem.LastDownloaded.ToLuceneString(), nameof(UserDefinedItem.LastDownloaded), "LastDownload" },
{ FieldType.Number, lb => lb.DateAdded.ToLuceneString(), nameof(LibraryBook.DateAdded) }
};
#endregion #endregion
#region create and update index #region create and update index
@ -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,58 +127,39 @@ namespace LibationSearchEngine
productId, productId,
d => d =>
{ {
// fields are key value pairs. MULTIPLE FIELDS CAN POTENTIALLY HAVE THE SAME KEY. d.RemoveField(fieldName.ToLower());
// ie: must remove old before adding new else will create unwanted duplicates.
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(Book book)
=> updateDocument( => updateDocument(
book.AudibleProductId, book.AudibleProductId,
d => d =>
{ {
// var lib = FieldIndexRules.GetRuleByFieldName<BookRule>("IsLiberated");
// TODO: better synonym handling. This is too easy to mess up var libError = FieldIndexRules.GetRuleByFieldName<BookRule>("LiberatedError");
// var lastDl = FieldIndexRules.GetRuleByFieldName<BookRule>(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(Book book)
=>updateDocument( =>updateDocument(
book.AudibleProductId, book.AudibleProductId,
d => d =>
{ {
// var rating = FieldIndexRules.GetRuleByFieldName<BookRule>("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);
@ -412,24 +251,24 @@ namespace LibationSearchEngine
return returnList; return returnList;
} }
private void displayResults(SearchResultSet docs) private void displayResults(SearchResultSet docs)
{ {
//for (int i = 0; i < docs.Docs.Count(); i++) //for (int i = 0; i < docs.Docs.Count(); i++)
//{ //{
// var sde = docs.Docs.First(); // var sde = docs.Docs.First();
// Document doc = sde.Doc; // Document doc = sde.Doc;
// float score = sde.Score; // float score = sde.Score;
// Serilog.Log.Logger.Debug($"{(i + 1)}) score={score}. Fields:"); // Serilog.Log.Logger.Debug($"{(i + 1)}) score={score}. Fields:");
// var allFields = doc.GetFields(); // var allFields = doc.GetFields();
// foreach (var f in allFields) // foreach (var f in allFields)
// Serilog.Log.Logger.Debug($" [{f.Name}]={f.StringValue}"); // Serilog.Log.Logger.Debug($" [{f.Name}]={f.StringValue}");
//} //}
} }
#endregion #endregion
private static Directory getIndex() => FSDirectory.Open(SearchEngineDirectory); private static Directory getIndex() => FSDirectory.Open(SearchEngineDirectory);
// not customizable. don't move to config // not customizable. don't move to config
private static string SearchEngineDirectory { get; } private static string SearchEngineDirectory { get; }

View File

@ -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();
} }