using System.Globalization; using ErsatzTV.Core; using Lucene.Net.Analysis; using Lucene.Net.Index; using Lucene.Net.QueryParsers.Classic; using Lucene.Net.QueryParsers.ComplexPhrase; using Lucene.Net.Search; using Lucene.Net.Util; using Query = Lucene.Net.Search.Query; namespace ErsatzTV.Infrastructure.Search; public class CustomMultiFieldQueryParser : MultiFieldQueryParser { private static readonly List NumericFields = [ LuceneSearchIndex.MinutesField, LuceneSearchIndex.SecondsField, LuceneSearchIndex.HeightField, LuceneSearchIndex.WidthField, LuceneSearchIndex.SeasonNumberField, LuceneSearchIndex.EpisodeNumberField, LuceneSearchIndex.VideoBitDepthField ]; private readonly Analyzer _analyzer; private readonly LuceneVersion _matchVersion; public CustomMultiFieldQueryParser( LuceneVersion matchVersion, string[] fields, Analyzer analyzer, IDictionary boosts) : base(matchVersion, fields, analyzer, boosts) { _matchVersion = matchVersion; _analyzer = analyzer; } public CustomMultiFieldQueryParser(LuceneVersion matchVersion, string[] fields, Analyzer analyzer) : base( matchVersion, fields, analyzer) { _matchVersion = matchVersion; _analyzer = analyzer; } protected override Query GetFieldQuery(string field, string queryText, bool quoted) { if (field == "released_onthisday") { var todayString = DateTime.Today.ToString("*MMdd", CultureInfo.InvariantCulture); return base.GetWildcardQuery(LuceneSearchIndex.ReleaseDateField, todayString); } if (NumericFields.Contains(field) && int.TryParse(queryText, out int val)) { var bytesRef = new BytesRef(); NumericUtils.Int32ToPrefixCoded(val, 0, bytesRef); return NewTermQuery(new Term(field, bytesRef)); } return base.GetFieldQuery(field, queryText, quoted); } protected override Query GetFieldQuery(string field, string queryText, int slop) { if (field == "released_inthelast" && ParseStart(queryText, out DateTime start)) { var todayString = DateTime.UtcNow.ToString("yyyyMMdd", CultureInfo.InvariantCulture); var dateString = start.ToString("yyyyMMdd", CultureInfo.InvariantCulture); return base.GetRangeQuery(LuceneSearchIndex.ReleaseDateField, dateString, todayString, true, true); } if (field == "released_notinthelast" && ParseStart(queryText, out DateTime finish)) { var dateString = finish.ToString("yyyyMMdd", CultureInfo.InvariantCulture); return base.GetRangeQuery(LuceneSearchIndex.ReleaseDateField, "00000000", dateString, false, false); } if (field == "added_inthelast" && ParseStart(queryText, out DateTime addedStart)) { var todayString = DateTime.UtcNow.ToString("yyyyMMdd", CultureInfo.InvariantCulture); var dateString = addedStart.ToString("yyyyMMdd", CultureInfo.InvariantCulture); return base.GetRangeQuery(LuceneSearchIndex.AddedDateField, dateString, todayString, true, true); } if (field == "added_notinthelast" && ParseStart(queryText, out DateTime addedFinish)) { var dateString = addedFinish.ToString("yyyyMMdd", CultureInfo.InvariantCulture); return base.GetRangeQuery(LuceneSearchIndex.AddedDateField, "00000000", dateString, false, false); } // assume asterisk always means wildcard if (queryText.Contains('*') && queryText.Contains(' ')) { var parser = new ComplexPhraseQueryParser(_matchVersion, LuceneSearchIndex.TitleField, _analyzer) { AllowLeadingWildcard = true }; string queryToParse = string.IsNullOrWhiteSpace(field) ? $"\"{queryText}\"" : $"{field}:\"{queryText}\""; return parser.Parse(queryToParse); } return base.GetFieldQuery(field, queryText, slop); } protected override Query GetRangeQuery( string field, string part1, string part2, bool startInclusive, bool endInclusive) { if (NumericFields.Contains(field)) { if (part1 is null or "*" && int.TryParse(part2, out int max1)) { return NumericRangeQuery.NewInt32Range(field, null, max1, startInclusive, endInclusive); } if (int.TryParse(part1, out int min)) { if (part2 is null or "*") { return NumericRangeQuery.NewInt32Range(field, min, null, startInclusive, endInclusive); } if (int.TryParse(part2, out int max)) { return NumericRangeQuery.NewInt32Range(field, min, max, startInclusive, endInclusive); } } } return base.GetRangeQuery(field, part1, part2, startInclusive, endInclusive); } private static bool ParseStart(string text, out DateTime start) { start = SystemTime.MinValueUtc; try { if (int.TryParse(text.Split(" ")[0], out int number)) { if (text.Contains("day")) { start = DateTime.Today.AddDays(number * -1); return true; } if (text.Contains("week")) { start = DateTime.Today.AddDays(number * -7); return true; } if (text.Contains("month")) { start = DateTime.Today.AddMonths(number * -1); return true; } if (text.Contains("year")) { start = DateTime.Today.AddYears(number * -1); return true; } } } catch { // do nothing } return false; } }