blob: 29106ba4c78cac6cc97b7d3daccc63783b9533ea [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Add NuGet References:
// Lucene.Net.Analysis.Common
// Lucene.Net.QueryParser
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers.Classic;
using Lucene.Net.Search;
using Lucene.Net.Store;
using Lucene.Net.Util;
using System;
using System.Globalization;
using System.IO;
using System.Text;
namespace Lucene.Net.Demo
{
/// <summary>
/// Simple command-line based search demo.
/// </summary>
public class SearchFiles
{
private SearchFiles() { }
/// <summary>Simple command-line based search demo.</summary>
public static void Main(string[] args)
{
// The <CONSOLE_APP_NAME> should be the assembly name of the application
// this code is compiled into. In .NET Framework, it is the name of the EXE file.
// In .NET Core, you have the option of compiling this into either a DLL or an EXE
// (see https://docs.microsoft.com/en-us/dotnet/core/deploying/index).
// In the first case, the <CONSOLE_APP_NAME> will be "dotnet <DLL_NAME>.dll".
string usage = "Usage: <CONSOLE_APP_NAME> <INDEX_DIRECTORY> [-f|--field <FIELD>] " +
"[-r|--repeat <NUMBER>] [-qf|--queries-file <PATH>] [-q|--query <QUERY>] " +
"[--raw] [-p|--page-size <NUMBER>]\n\n" +
"Use no --query or --queries-file option for interactive mode.\n\n" +
"See http://lucene.apache.org/core/4_8_0/demo/ for details.";
if (args.Length < 1 || args.Length > 0 &&
("?".Equals(args[0], StringComparison.Ordinal) || "-h".Equals(args[0], StringComparison.Ordinal) || "--help".Equals(args[0], StringComparison.Ordinal)))
{
Console.WriteLine(usage);
Environment.Exit(0);
}
string index = args[0];
string field = "contents";
string queries = null;
int repeat = 0;
bool raw = false;
string queryString = null;
int hitsPerPage = 10;
for (int i = 0; i < args.Length; i++)
{
if ("-f".Equals(args[i], StringComparison.Ordinal) || "-field".Equals(args[i], StringComparison.Ordinal))
{
field = args[i + 1];
i++;
}
else if ("-qf".Equals(args[i], StringComparison.Ordinal) || "--queries-file".Equals(args[i], StringComparison.Ordinal))
{
queries = args[i + 1];
i++;
}
else if ("-q".Equals(args[i], StringComparison.Ordinal) || "--query".Equals(args[i], StringComparison.Ordinal))
{
queryString = args[i + 1];
i++;
}
else if ("-r".Equals(args[i], StringComparison.Ordinal) || "--repeat".Equals(args[i], StringComparison.Ordinal))
{
repeat = int.Parse(args[i + 1], CultureInfo.InvariantCulture);
i++;
}
else if ("--raw".Equals(args[i], StringComparison.Ordinal))
{
raw = true;
}
else if ("-p".Equals(args[i], StringComparison.Ordinal) || "--paging".Equals(args[i], StringComparison.Ordinal))
{
hitsPerPage = int.Parse(args[i + 1], CultureInfo.InvariantCulture);
if (hitsPerPage <= 0)
{
Console.WriteLine("There must be at least 1 hit per page.");
Environment.Exit(1);
}
i++;
}
}
using (IndexReader reader = DirectoryReader.Open(FSDirectory.Open(index)))
{
IndexSearcher searcher = new IndexSearcher(reader);
// :Post-Release-Update-Version.LUCENE_XY:
Analyzer analyzer = new StandardAnalyzer(LuceneVersion.LUCENE_48);
TextReader input = null;
if (queries != null)
{
input = new StreamReader(new FileStream(queries, FileMode.Open, FileAccess.Read), Encoding.UTF8);
}
else
{
input = Console.In;
}
// :Post-Release-Update-Version.LUCENE_XY:
QueryParser parser = new QueryParser(LuceneVersion.LUCENE_48, field, analyzer);
while (true)
{
if (queries == null && queryString == null)
{
// prompt the user
Console.WriteLine("Enter query (or press Enter to exit): ");
}
string line = queryString != null ? queryString : input.ReadLine();
if (line == null || line.Length == 0)
{
break;
}
line = line.Trim();
if (line.Length == 0)
{
break;
}
Query query = parser.Parse(line);
Console.WriteLine("Searching for: " + query.ToString(field));
if (repeat > 0) // repeat & time as benchmark
{
DateTime start = DateTime.UtcNow;
for (int i = 0; i < repeat; i++)
{
searcher.Search(query, null, 100);
}
DateTime end = DateTime.UtcNow;
Console.WriteLine("Time: " + (end - start).TotalMilliseconds + "ms");
}
DoPagingSearch(searcher, query, hitsPerPage, raw, queries == null && queryString == null);
if (queryString != null)
{
break;
}
}
} // Disposes reader
}
/// <summary>
/// This demonstrates a typical paging search scenario, where the search engine presents
/// pages of size n to the user. The user can then go to the next page if interested in
/// the next hits.
/// <para/>
/// When the query is executed for the first time, then only enough results are collected
/// to fill 5 result pages. If the user wants to page beyond this limit, then the query
/// is executed another time and all hits are collected.
/// </summary>
public static void DoPagingSearch(IndexSearcher searcher, Query query,
int hitsPerPage, bool raw, bool interactive)
{
// Collect enough docs to show 5 pages
TopDocs results = searcher.Search(query, 5 * hitsPerPage);
ScoreDoc[]
hits = results.ScoreDocs;
int numTotalHits = results.TotalHits;
Console.WriteLine(numTotalHits + " total matching documents");
int start = 0;
int end = Math.Min(numTotalHits, hitsPerPage);
while (true)
{
if (end > hits.Length)
{
Console.WriteLine("Only results 1 - " + hits.Length + " of " + numTotalHits + " total matching documents collected.");
Console.WriteLine("Collect more (y/n) ?");
var key = Console.ReadKey().KeyChar;
if (key == 'n')
{
break;
}
hits = searcher.Search(query, numTotalHits).ScoreDocs;
}
end = Math.Min(hits.Length, start + hitsPerPage);
for (int i = start; i < end; i++)
{
if (raw) // output raw format
{
Console.WriteLine("doc=" + hits[i].Doc + " score=" + hits[i].Score);
continue;
}
Document doc = searcher.Doc(hits[i].Doc);
string path = doc.Get("path");
if (path != null)
{
Console.WriteLine((i + 1) + ". " + path);
string title = doc.Get("title");
if (title != null)
{
Console.WriteLine(" Title: " + doc.Get("title"));
}
}
else
{
Console.WriteLine((i + 1) + ". " + "No path for this document");
}
}
if (!interactive || end == 0)
{
break;
}
if (numTotalHits >= end)
{
var pageNumberBuilder = new StringBuilder();
bool quit = false;
while (true)
{
if (pageNumberBuilder.Length == 0)
{
Console.WriteLine("Press ");
if (start - hitsPerPage >= 0)
{
Console.WriteLine("(p)revious page, ");
}
if (start + hitsPerPage < numTotalHits)
{
Console.WriteLine("(n)ext page, ");
}
Console.WriteLine("(q)uit or enter number to jump to a page.");
}
var key = Console.ReadKey(intercept: true).KeyChar;
if (key == 'q')
{
quit = true;
break;
}
if (key == 'p')
{
start = Math.Max(0, start - hitsPerPage);
break;
}
else if (key == 'n')
{
if (start + hitsPerPage < numTotalHits)
{
start += hitsPerPage;
}
break;
}
else if (key == (char)13) // enter key
{
Console.WriteLine();
int page = int.Parse(pageNumberBuilder.ToString(), CultureInfo.InvariantCulture);
pageNumberBuilder.Clear();
if ((page - 1) * hitsPerPage < numTotalHits)
{
start = (page - 1) * hitsPerPage;
break;
}
else
{
Console.WriteLine("No such page");
}
}
else if (char.IsDigit(key))
{
Console.Write(key);
pageNumberBuilder.Append(key);
}
else
{
Console.WriteLine("No such command");
}
}
if (quit) break;
end = Math.Min(numTotalHits, start + hitsPerPage);
}
}
}
}
}