Implemented RebuildIndex plus UpdateIndex signal to backgroung tasks. Adapted UI.

--HG--
branch : dev
This commit is contained in:
Sebastien Ros
2010-06-04 17:42:20 -07:00
parent d93c9274c8
commit 9eebb483ae
12 changed files with 193 additions and 74 deletions

View File

@@ -58,7 +58,11 @@ namespace Orchard.Tests.Indexing {
[Test] [Test]
public void IndexProviderShouldOverwriteAlreadyExistingIndex() { public void IndexProviderShouldOverwriteAlreadyExistingIndex() {
_provider.CreateIndex("default"); _provider.CreateIndex("default");
_provider.CreateIndex("default"); _provider.Store("default", _provider.New(1).Add("body", null));
Assert.That(_provider.IsEmpty("default"), Is.False);
_provider.CreateIndex("default");
Assert.That(_provider.IsEmpty("default"), Is.True);
} }
[Test] [Test]
@@ -193,5 +197,24 @@ namespace Orchard.Tests.Indexing {
_provider.SetLastIndexUtc("default", new DateTime(1901, 1, 1, 1, 1, 1, 1)); _provider.SetLastIndexUtc("default", new DateTime(1901, 1, 1, 1, 1, 1, 1));
Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime)); Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime));
} }
[Test]
public void IsEmptyShouldBeTrueForNoneExistingIndexes() {
_provider.IsEmpty("dummy");
Assert.That(_provider.IsEmpty("default"), Is.True);
}
[Test]
public void IsEmptyShouldBeTrueForJustNewIndexes() {
_provider.CreateIndex("default");
Assert.That(_provider.IsEmpty("default"), Is.True);
}
[Test]
public void IsEmptyShouldBeFalseWhenThereIsADocument() {
_provider.CreateIndex("default");
_provider.Store("default", _provider.New(1).Add("body", null));
Assert.That(_provider.IsEmpty("default"), Is.False);
}
} }
} }

View File

@@ -70,6 +70,21 @@ namespace Orchard.Core.Indexing.Lucene {
return new DirectoryInfo(_appDataFolder.MapPath(Path.Combine(_basePath, indexName))).Exists; return new DirectoryInfo(_appDataFolder.MapPath(Path.Combine(_basePath, indexName))).Exists;
} }
public bool IsEmpty(string indexName) {
if(!Exists(indexName)) {
return true;
}
var reader = IndexReader.Open(GetDirectory(indexName), true);
try {
return reader.NumDocs() == 0;
}
finally {
reader.Close();
}
}
public void CreateIndex(string indexName) { public void CreateIndex(string indexName) {
var writer = new IndexWriter(GetDirectory(indexName), _analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED); var writer = new IndexWriter(GetDirectory(indexName), _analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED);
writer.Close(); writer.Close();
@@ -118,7 +133,6 @@ namespace Orchard.Core.Indexing.Lucene {
writer.Optimize(); writer.Optimize();
writer.Close(); writer.Close();
} }
} }
public void Delete(string indexName, int documentId) { public void Delete(string indexName, int documentId) {

View File

@@ -10,105 +10,164 @@ using Orchard.Logging;
using Orchard.Services; using Orchard.Services;
using Orchard.Tasks; using Orchard.Tasks;
using Orchard.Core.Indexing.Models; using Orchard.Core.Indexing.Models;
using Orchard.Tasks.Indexing;
using Orchard.Indexing;
namespace Orchard.Core.Indexing.Services { namespace Orchard.Core.Indexing.Services {
/// <summary> /// <summary>
/// Contains the logic which is regularly executed to retrieve index information from multiple content handlers. /// Contains the logic which is regularly executed to retrieve index information from multiple content handlers.
/// </summary> /// </summary>
[UsedImplicitly] [UsedImplicitly]
public class IndexingTaskExecutor : IBackgroundTask { public class IndexingTaskExecutor : IBackgroundTask, IIndexNotifierHandler {
private readonly IClock _clock; private readonly IClock _clock;
private readonly IRepository<IndexingTaskRecord> _repository; private readonly IRepository<IndexingTaskRecord> _repository;
private readonly IEnumerable<IContentHandler> _handlers; private readonly IEnumerable<IContentHandler> _handlers;
private IIndexProvider _indexProvider; private IIndexProvider _indexProvider;
private readonly IIndexManager _indexManager; private readonly IIndexManager _indexManager;
private readonly IIndexingTaskManager _indexingTaskManager;
private readonly IContentManager _contentManager; private readonly IContentManager _contentManager;
private const string SearchIndexName = "Search"; private const string SearchIndexName = "Search";
private readonly object _synLock = new object();
public IndexingTaskExecutor( public IndexingTaskExecutor(
IClock clock, IClock clock,
IRepository<IndexingTaskRecord> repository, IRepository<IndexingTaskRecord> repository,
IEnumerable<IContentHandler> handlers, IEnumerable<IContentHandler> handlers,
IIndexManager indexManager, IIndexManager indexManager,
IIndexingTaskManager indexingTaskManager,
IContentManager contentManager) { IContentManager contentManager) {
_clock = clock; _clock = clock;
_repository = repository; _repository = repository;
_indexManager = indexManager; _indexManager = indexManager;
_handlers = handlers; _handlers = handlers;
_indexingTaskManager = indexingTaskManager;
_contentManager = contentManager; _contentManager = contentManager;
Logger = NullLogger.Instance; Logger = NullLogger.Instance;
} }
public ILogger Logger { get; set; } public ILogger Logger { get; set; }
public void UpdateIndex(string indexName) {
if (indexName == SearchIndexName) {
Sweep();
}
}
public void Sweep() { public void Sweep() {
if (!_indexManager.HasIndexProvider()) { if ( !System.Threading.Monitor.TryEnter(_synLock) ) {
Logger.Information("Index was requested but was already running");
return; return;
} }
_indexProvider = _indexManager.GetSearchIndexProvider();
// retrieve last processed index time
var lastIndexing = _indexProvider.GetLastIndexUtc(SearchIndexName);
_indexProvider.SetLastIndexUtc(SearchIndexName, _clock.UtcNow);
// retrieve not yet processed tasks
var taskRecords = _repository.Fetch(x => x.CreatedUtc >= lastIndexing)
.ToArray();
if (taskRecords.Length == 0)
return;
Logger.Information("Processing {0} indexing tasks", taskRecords.Length);
if (!_indexProvider.Exists(SearchIndexName)) {
_indexProvider.CreateIndex(SearchIndexName);
}
var updateIndexDocuments = new List<IIndexDocument>();
// process Delete tasks
try { try {
_indexProvider.Delete(SearchIndexName, taskRecords.Where(t => t.Action == IndexingTaskRecord.Delete).Select(t => t.Id));
}
catch (Exception ex) {
Logger.Warning(ex, "An error occured while removing a document from the index");
}
// process Update tasks if (!_indexManager.HasIndexProvider()) {
foreach (var taskRecord in taskRecords.Where(t => t.Action == IndexingTaskRecord.Update)) { return;
var task = new IndexingTask(_contentManager, taskRecord); }
try { _indexProvider = _indexManager.GetSearchIndexProvider();
var context = new IndexContentContext { var updateIndexDocuments = new List<IIndexDocument>();
ContentItem = task.ContentItem, var lastIndexing = DateTime.UtcNow;
IndexDocument = _indexProvider.New(task.ContentItem.Id)
};
// dispatch to handlers to retrieve index information // Do we need to rebuild the full index (first time module is used, or rebuild index requested) ?
foreach (var handler in _handlers) { if (_indexProvider.IsEmpty(SearchIndexName)) {
handler.Indexing(context); Logger.Information("Rebuild index started");
// mark current last task, as we should process older ones (in case of rebuild index only)
lastIndexing = _indexingTaskManager.GetLastTaskDateTime();
// get every existing content item to index it
foreach (var contentItem in _contentManager.Query(VersionOptions.Published).List()) {
try {
var context = new IndexContentContext {
ContentItem = contentItem,
IndexDocument = _indexProvider.New(contentItem.Id)
};
// dispatch to handlers to retrieve index information
foreach (var handler in _handlers) {
handler.Indexing(context);
}
updateIndexDocuments.Add(context.IndexDocument);
foreach (var handler in _handlers) {
handler.Indexed(context);
}
}
catch (Exception ex) {
Logger.Warning(ex, "Unable to index content item #{0} during rebuild", contentItem.Id);
}
} }
updateIndexDocuments.Add(context.IndexDocument); }
else {
// retrieve last processed index time
lastIndexing = _indexProvider.GetLastIndexUtc(SearchIndexName);
}
foreach (var handler in _handlers) { _indexProvider.SetLastIndexUtc(SearchIndexName, _clock.UtcNow);
handler.Indexed(context);
// retrieve not yet processed tasks
var taskRecords = _repository.Fetch(x => x.CreatedUtc >= lastIndexing)
.ToArray();
if (taskRecords.Length == 0)
return;
Logger.Information("Processing {0} indexing tasks", taskRecords.Length);
if (!_indexProvider.Exists(SearchIndexName)) {
_indexProvider.CreateIndex(SearchIndexName);
}
// process Delete tasks
try {
_indexProvider.Delete(SearchIndexName, taskRecords.Where(t => t.Action == IndexingTaskRecord.Delete).Select(t => t.Id));
}
catch (Exception ex) {
Logger.Warning(ex, "An error occured while removing a document from the index");
}
// process Update tasks
foreach (var taskRecord in taskRecords.Where(t => t.Action == IndexingTaskRecord.Update)) {
var task = new IndexingTask(_contentManager, taskRecord);
try {
var context = new IndexContentContext {
ContentItem = task.ContentItem,
IndexDocument = _indexProvider.New(task.ContentItem.Id)
};
// dispatch to handlers to retrieve index information
foreach (var handler in _handlers) {
handler.Indexing(context);
}
updateIndexDocuments.Add(context.IndexDocument);
foreach (var handler in _handlers) {
handler.Indexed(context);
}
}
catch (Exception ex) {
Logger.Warning(ex, "Unable to process indexing task #{0}", taskRecord.Id);
} }
} }
catch (Exception ex) {
Logger.Warning(ex, "Unable to process indexing task #{0}", taskRecord.Id); if (updateIndexDocuments.Count > 0) {
try {
_indexProvider.Store(SearchIndexName, updateIndexDocuments);
}
catch (Exception ex) {
Logger.Warning(ex, "An error occured while adding a document to the index");
}
} }
} }
finally {
if (updateIndexDocuments.Count > 0) { System.Threading.Monitor.Exit(_synLock);
try {
_indexProvider.Store(SearchIndexName, updateIndexDocuments);
}
catch (Exception ex) {
Logger.Warning(ex, "An error occured while adding a document to the index");
}
} }
} }
} }

View File

@@ -59,12 +59,8 @@ namespace Orchard.Core.Indexing.Services {
Logger.Information("Deleting index task created for [{0}:{1}]", contentItem.ContentType, contentItem.Id); Logger.Information("Deleting index task created for [{0}:{1}]", contentItem.ContentType, contentItem.Id);
} }
public IEnumerable<IIndexingTask> GetTasks(DateTime? createdAfter) { public DateTime GetLastTaskDateTime() {
return _repository return _repository.Table.Max(t => t.CreatedUtc) ?? DateTime.MinValue;
.Fetch(x => x.CreatedUtc > createdAfter)
.Select(x => new IndexingTask(_contentManager, x))
.Cast<IIndexingTask>()
.ToReadOnlyCollection();
} }
/// <summary> /// <summary>

View File

@@ -1,4 +1,5 @@
using System.Web.Mvc; using System;
using System.Web.Mvc;
using Orchard.Localization; using Orchard.Localization;
using Orchard.Search.Services; using Orchard.Search.Services;
using Orchard.Search.ViewModels; using Orchard.Search.ViewModels;
@@ -18,7 +19,7 @@ namespace Orchard.Search.Controllers {
public Localizer T { get; set; } public Localizer T { get; set; }
public ActionResult Index() { public ActionResult Index() {
var viewModel = new SearchIndexViewModel {HasIndexToManage = _searchService.HasIndexToManage}; var viewModel = new SearchIndexViewModel {HasIndexToManage = _searchService.HasIndexToManage, IndexUpdatedUtc = _searchService.GetIndexUpdatedUtc()};
if (!viewModel.HasIndexToManage) if (!viewModel.HasIndexToManage)
Services.Notifier.Information(T("There is not search index to manage for this site.")); Services.Notifier.Information(T("There is not search index to manage for this site."));

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using Orchard.Indexing; using Orchard.Indexing;
namespace Orchard.Search.Services { namespace Orchard.Search.Services {
@@ -7,5 +8,6 @@ namespace Orchard.Search.Services {
IEnumerable<ISearchHit> Query(string term); IEnumerable<ISearchHit> Query(string term);
void RebuildIndex(); void RebuildIndex();
void UpdateIndex(); void UpdateIndex();
DateTime GetIndexUpdatedUtc();
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Orchard.Indexing; using Orchard.Indexing;
using Orchard.Localization; using Orchard.Localization;
@@ -8,12 +9,14 @@ namespace Orchard.Search.Services
{ {
public class SearchService : ISearchService public class SearchService : ISearchService
{ {
private const string SearchIndexName = "search"; private const string SearchIndexName = "Search";
private readonly IIndexManager _indexManager; private readonly IIndexManager _indexManager;
private readonly IEnumerable<IIndexNotifierHandler> _indexNotifierHandlers;
public SearchService(IOrchardServices services, IIndexManager indexManager) { public SearchService(IOrchardServices services, IIndexManager indexManager, IEnumerable<IIndexNotifierHandler> indexNotifierHandlers) {
Services = services; Services = services;
_indexManager = indexManager; _indexManager = indexManager;
_indexNotifierHandlers = indexNotifierHandlers;
T = NullLocalizer.Instance; T = NullLocalizer.Instance;
} }
@@ -49,10 +52,20 @@ namespace Orchard.Search.Services
} }
public void UpdateIndex() { public void UpdateIndex() {
//todo: this
//if (_indexManager.HasIndexProvider()) foreach(var handler in _indexNotifierHandlers) {
// _indexManager.GetSearchIndexProvider().UpdateIndex(SearchIndexName); handler.UpdateIndex(SearchIndexName);
}
Services.Notifier.Information(T("The search index has been updated.")); Services.Notifier.Information(T("The search index has been updated."));
} }
public DateTime GetIndexUpdatedUtc() {
if(!HasIndexToManage) {
return DateTime.MinValue;
}
return _indexManager.GetSearchIndexProvider().GetLastIndexUtc(SearchIndexName);
}
} }
} }

View File

@@ -10,7 +10,7 @@ using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.S
} }
using (Html.BeginForm("rebuild", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %> using (Html.BeginForm("rebuild", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %>
<fieldset> <fieldset>
<p><%=T("Rebuild the search index for a fresh start. <button type=\"submit\" title=\"Rebuild the search index.\">Rebuld</button>") %></p> <p><%=T("Rebuild the search index for a fresh start. <button type=\"submit\" title=\"Rebuild the search index.\">Rebuild</button>") %></p>
<%=Html.AntiForgeryTokenOrchard() %> <%=Html.AntiForgeryTokenOrchard() %>
</fieldset><% </fieldset><%
} %> } %>

View File

@@ -0,0 +1,5 @@
namespace Orchard.Indexing {
public interface IIndexNotifierHandler : IEvents {
void UpdateIndex(string indexName);
}
}

View File

@@ -18,6 +18,11 @@ namespace Orchard.Indexing {
/// </summary> /// </summary>
void DeleteIndex(string name); void DeleteIndex(string name);
/// <summary>
/// Whether an index is empty or not
/// </summary>
bool IsEmpty(string indexName);
/// <summary> /// <summary>
/// Creates an empty document /// Creates an empty document
/// </summary> /// </summary>

View File

@@ -349,6 +349,7 @@
<Compile Include="Environment\State\IShellStateManager.cs" /> <Compile Include="Environment\State\IShellStateManager.cs" />
<Compile Include="Environment\State\ShellStateCoordinator.cs" /> <Compile Include="Environment\State\ShellStateCoordinator.cs" />
<Compile Include="IDependency.cs" /> <Compile Include="IDependency.cs" />
<Compile Include="Indexing\IIndexNotifierHandler.cs" />
<Compile Include="Localization\Services\DefaultCultureManager.cs" /> <Compile Include="Localization\Services\DefaultCultureManager.cs" />
<Compile Include="Localization\Services\DefaultResourceManager.cs" /> <Compile Include="Localization\Services\DefaultResourceManager.cs" />
<Compile Include="Indexing\DefaultIndexManager.cs" /> <Compile Include="Indexing\DefaultIndexManager.cs" />

View File

@@ -15,10 +15,10 @@ namespace Orchard.Tasks.Indexing {
void CreateDeleteIndexTask(ContentItem contentItem); void CreateDeleteIndexTask(ContentItem contentItem);
/// <summary> /// <summary>
/// Loads all indexing tasks created after to a specific date and time /// Returns the Date Time of the last task created
/// </summary> /// </summary>
IEnumerable<IIndexingTask> GetTasks(DateTime? createdAfter); DateTime GetLastTaskDateTime();
/// <summary> /// <summary>
/// Deletes all indexing tasks assigned to a specific content item /// Deletes all indexing tasks assigned to a specific content item
/// </summary> /// </summary>