2019-06-17 18:12:34 -07:00
|
|
|
|
|
2019-06-18 13:04:06 -07:00
|
|
|
|
using System;
|
2019-06-17 18:12:34 -07:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO.Compression;
|
|
|
|
|
|
using System.Linq;
|
2019-06-18 13:04:06 -07:00
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
2019-06-17 18:12:34 -07:00
|
|
|
|
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;
|
2019-06-18 13:04:06 -07:00
|
|
|
|
using MatterHackers.Agg;
|
2019-06-17 18:12:34 -07:00
|
|
|
|
using MatterHackers.Agg.Platform;
|
2019-06-18 13:04:06 -07:00
|
|
|
|
using MatterHackers.Localizations;
|
2019-06-17 18:12:34 -07:00
|
|
|
|
using MatterHackers.MatterControl;
|
|
|
|
|
|
using MatterHackers.MatterControl.DataStorage;
|
|
|
|
|
|
using MatterHackers.MatterControl.PartPreviewWindow;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
|
|
|
|
|
|
namespace MatterControlLib
|
|
|
|
|
|
{
|
|
|
|
|
|
public class LuceneHelpSearch
|
|
|
|
|
|
{
|
|
|
|
|
|
private static IndexWriter writer;
|
|
|
|
|
|
private static StandardAnalyzer analyzer;
|
|
|
|
|
|
|
|
|
|
|
|
static LuceneHelpSearch()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Ensures index backwards compatibility
|
|
|
|
|
|
var AppLuceneVersion = LuceneVersion.LUCENE_48;
|
|
|
|
|
|
|
|
|
|
|
|
var indexLocation = System.IO.Path.Combine(ApplicationDataStorage.Instance.ApplicationTempDataPath, "LuceneIndex");
|
|
|
|
|
|
System.IO.Directory.CreateDirectory(indexLocation);
|
|
|
|
|
|
|
|
|
|
|
|
var dir = FSDirectory.Open(indexLocation);
|
|
|
|
|
|
|
|
|
|
|
|
// create an analyzer to process the text
|
|
|
|
|
|
analyzer = new StandardAnalyzer(AppLuceneVersion);
|
|
|
|
|
|
|
|
|
|
|
|
// create an index writer
|
|
|
|
|
|
var indexConfig = new IndexWriterConfig(AppLuceneVersion, analyzer);
|
|
|
|
|
|
|
|
|
|
|
|
writer = new IndexWriter(dir, indexConfig);
|
|
|
|
|
|
|
2019-06-18 13:04:06 -07:00
|
|
|
|
// If the index lacks a reasonable number of documents, rebuild it from the zip file
|
|
|
|
|
|
if (writer.MaxDoc < 10)
|
|
|
|
|
|
{
|
|
|
|
|
|
ApplicationController.Instance.Tasks.Execute(
|
|
|
|
|
|
"Preparing help index".Localize(),
|
|
|
|
|
|
null,
|
|
|
|
|
|
(progress, cancellationToken) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
string relativePath = System.IO.Path.Combine("OemSettings", "help-docs.zip");
|
|
|
|
|
|
|
|
|
|
|
|
IndexZipFile(relativePath, progress, cancellationToken);
|
|
|
|
|
|
|
|
|
|
|
|
return Task.CompletedTask;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2019-06-17 18:12:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public LuceneHelpSearch()
|
|
|
|
|
|
{
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static void ProcessHelpTree(HelpArticle context, Dictionary<string, HelpArticle> helpArticles)
|
|
|
|
|
|
{
|
|
|
|
|
|
helpArticles[context.Path] = context;
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var child in context.Children)
|
|
|
|
|
|
{
|
|
|
|
|
|
ProcessHelpTree(child, helpArticles);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-06-18 13:04:06 -07:00
|
|
|
|
private static void IndexZipFile(string filePath, IProgress<ProgressStatus> progress, CancellationToken cancellationToken)
|
2019-06-17 18:12:34 -07:00
|
|
|
|
{
|
|
|
|
|
|
Dictionary<string, HelpArticle> helpArticles;
|
|
|
|
|
|
|
|
|
|
|
|
// Clear existing
|
|
|
|
|
|
writer.DeleteAll();
|
|
|
|
|
|
|
|
|
|
|
|
// Build index from help-docs.zip
|
2019-06-18 13:04:06 -07:00
|
|
|
|
using (var file = AggContext.StaticData.OpenStream(filePath))
|
2019-06-17 18:12:34 -07:00
|
|
|
|
using (var zip = new ZipArchive(file, ZipArchiveMode.Read))
|
|
|
|
|
|
{
|
|
|
|
|
|
var tocEntry = zip.Entries.FirstOrDefault(e => e.FullName == "toc.json");
|
|
|
|
|
|
|
|
|
|
|
|
using (var docStream = tocEntry.Open())
|
|
|
|
|
|
{
|
|
|
|
|
|
var reader = new System.IO.StreamReader(docStream);
|
|
|
|
|
|
var tocText = reader.ReadToEnd();
|
|
|
|
|
|
|
|
|
|
|
|
var rootHelpArticle = JsonConvert.DeserializeObject<HelpArticle>(tocText);
|
|
|
|
|
|
|
|
|
|
|
|
helpArticles = new Dictionary<string, HelpArticle>();
|
|
|
|
|
|
|
|
|
|
|
|
// Walk the documents tree building up a dictionary of article paths to articles
|
|
|
|
|
|
ProcessHelpTree(rootHelpArticle, helpArticles);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-06-18 13:04:06 -07:00
|
|
|
|
var progressStatus = new ProgressStatus()
|
|
|
|
|
|
{
|
|
|
|
|
|
//Status = "",
|
|
|
|
|
|
Progress0To1 = 0
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var count = zip.Entries.Count;
|
|
|
|
|
|
double i = 0;
|
|
|
|
|
|
|
2019-06-17 18:12:34 -07:00
|
|
|
|
foreach (var entry in zip.Entries)
|
|
|
|
|
|
{
|
2019-06-18 13:04:06 -07:00
|
|
|
|
progressStatus.Progress0To1 = i++ / count;
|
|
|
|
|
|
progress.Report(progressStatus);
|
|
|
|
|
|
|
|
|
|
|
|
// Observe and abort on cancellationToken signal
|
|
|
|
|
|
if (cancellationToken.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
writer.DeleteAll();
|
|
|
|
|
|
writer.Commit();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-06-17 18:12:34 -07:00
|
|
|
|
if (entry.FullName.ToLower().EndsWith(".md"))
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var docStream = entry.Open())
|
|
|
|
|
|
{
|
|
|
|
|
|
var reader = new System.IO.StreamReader(docStream);
|
|
|
|
|
|
|
|
|
|
|
|
string text = reader.ReadToEnd();
|
|
|
|
|
|
|
|
|
|
|
|
var doc = new Document();
|
|
|
|
|
|
|
|
|
|
|
|
// StringField indexes but doesn't tokenise
|
|
|
|
|
|
doc.Add(new StringField("name", helpArticles[entry.FullName].Name, Field.Store.YES));
|
|
|
|
|
|
doc.Add(new StringField("path", entry.FullName, Field.Store.YES));
|
|
|
|
|
|
doc.Add(new TextField("body", text, Field.Store.NO));
|
|
|
|
|
|
|
|
|
|
|
|
writer.AddDocument(doc);
|
|
|
|
|
|
writer.Flush(triggerMerge: false, applyAllDeletes: false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
writer.Commit();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public IEnumerable<HelpSearchResult> Search(string text)
|
|
|
|
|
|
{
|
|
|
|
|
|
var parser = new QueryParser(LuceneVersion.LUCENE_48, "body", analyzer);
|
|
|
|
|
|
var query = parser.Parse(text);
|
|
|
|
|
|
|
|
|
|
|
|
// re-use the writer to get real-time updates
|
|
|
|
|
|
var searcher = new IndexSearcher(writer.GetReader(applyAllDeletes: true));
|
|
|
|
|
|
|
|
|
|
|
|
var hits = searcher.Search(query, 20 /* top 20 */).ScoreDocs;
|
|
|
|
|
|
|
|
|
|
|
|
return hits.Select(hit =>
|
|
|
|
|
|
{
|
|
|
|
|
|
var foundDoc = searcher.Doc(hit.Doc);
|
|
|
|
|
|
return new HelpSearchResult()
|
|
|
|
|
|
{
|
|
|
|
|
|
Name = foundDoc.Get("name"),
|
|
|
|
|
|
Path = foundDoc.Get("path")
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|