mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2025-10-15 19:54:57 +08:00
Complete refactoring on Indexing module
Changed Parse() to use lucene syntax Moved everything to Orchard.Indexing New filters for search --HG-- branch : dev
This commit is contained in:
@@ -104,8 +104,6 @@
|
||||
<Compile Include="Common\Providers\CommonAspectProviderTests.cs" />
|
||||
<Compile Include="Common\Services\RoutableServiceTests.cs" />
|
||||
<Compile Include="Feeds\Controllers\FeedControllerTests.cs" />
|
||||
<Compile Include="Indexing\DefaultIndexProviderTests.cs" />
|
||||
<Compile Include="Indexing\DefaultSearchBuilderTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Scheduling\ScheduledTaskManagerTests.cs" />
|
||||
<Compile Include="Scheduling\ScheduledTaskExecutorTests.cs" />
|
||||
|
@@ -3,18 +3,14 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using Autofac;
|
||||
using NUnit.Framework;
|
||||
using Orchard.Environment;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.FileSystems.VirtualPath;
|
||||
using Orchard.Indexing;
|
||||
using Orchard.Core.Indexing.Lucene;
|
||||
using Orchard.Services;
|
||||
using Orchard.Tests.Environment.Configuration;
|
||||
using Orchard.Indexing.Services;
|
||||
using Orchard.Tests.FileSystems.AppData;
|
||||
|
||||
namespace Orchard.Tests.Indexing {
|
||||
public class DefaultIndexProviderTests {
|
||||
namespace Orchard.Tests.Modules.Indexing {
|
||||
public class LuceneIndexProviderTests {
|
||||
private IContainer _container;
|
||||
private IIndexProvider _provider;
|
||||
private IAppDataFolder _appDataFolder;
|
||||
@@ -36,7 +32,7 @@ namespace Orchard.Tests.Indexing {
|
||||
_appDataFolder = AppDataFolderTests.CreateAppDataFolder(_basePath);
|
||||
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterType<DefaultIndexProvider>().As<IIndexProvider>();
|
||||
builder.RegisterType<LuceneIndexProvider>().As<IIndexProvider>();
|
||||
builder.RegisterInstance(_appDataFolder).As<IAppDataFolder>();
|
||||
|
||||
// setting up a ShellSettings instance
|
||||
@@ -111,7 +107,7 @@ namespace Orchard.Tests.Indexing {
|
||||
|
||||
var hit = searchBuilder.Get(42);
|
||||
Assert.IsNotNull(hit);
|
||||
Assert.That(hit.Id, Is.EqualTo(42));
|
||||
Assert.That(hit.ContentItemId, Is.EqualTo(42));
|
||||
|
||||
hit = searchBuilder.Get(1);
|
||||
Assert.IsNull(hit);
|
||||
@@ -120,12 +116,12 @@ namespace Orchard.Tests.Indexing {
|
||||
[Test]
|
||||
public void PropertiesShouldNotBeLost() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(42).Add("prop1", "value1"));
|
||||
_provider.Store("default", _provider.New(42).Add("prop1", "value1").Store());
|
||||
|
||||
var hit = _provider.CreateSearchBuilder("default").Get(42);
|
||||
|
||||
Assert.IsNotNull(hit);
|
||||
Assert.That(hit.Id, Is.EqualTo(42));
|
||||
Assert.That(hit.ContentItemId, Is.EqualTo(42));
|
||||
Assert.That(hit.GetString("prop1"), Is.EqualTo("value1"));
|
||||
|
||||
}
|
||||
@@ -160,21 +156,21 @@ namespace Orchard.Tests.Indexing {
|
||||
|
||||
var searchBuilder = _provider.CreateSearchBuilder("default");
|
||||
|
||||
Assert.That(searchBuilder.Get(1).Id, Is.EqualTo(1));
|
||||
Assert.That(searchBuilder.Get(11).Id, Is.EqualTo(11));
|
||||
Assert.That(searchBuilder.Get(111).Id, Is.EqualTo(111));
|
||||
Assert.That(searchBuilder.Get(1).ContentItemId, Is.EqualTo(1));
|
||||
Assert.That(searchBuilder.Get(11).ContentItemId, Is.EqualTo(11));
|
||||
Assert.That(searchBuilder.Get(111).ContentItemId, Is.EqualTo(111));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TagsShouldBeRemoved() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("body", "<hr>some content</hr>"));
|
||||
_provider.Store("default", _provider.New(2).Add("body", "<hr>some content</hr>", true));
|
||||
_provider.Store("default", _provider.New(1).Add("body", "<hr>some content</hr>").Analyze());
|
||||
_provider.Store("default", _provider.New(2).Add("body", "<hr>some content</hr>", true).Analyze());
|
||||
|
||||
var searchBuilder = _provider.CreateSearchBuilder("default");
|
||||
|
||||
Assert.That(searchBuilder.WithField("body", "hr").Search().Count(), Is.EqualTo(1));
|
||||
Assert.That(searchBuilder.WithField("body", "hr").Search().First().Id, Is.EqualTo(1));
|
||||
Assert.That(searchBuilder.WithField("body", "hr").Search().First().ContentItemId, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test] public void ShouldAllowNullOrEmptyStrings() {
|
||||
@@ -185,21 +181,21 @@ namespace Orchard.Tests.Indexing {
|
||||
|
||||
var searchBuilder = _provider.CreateSearchBuilder("default");
|
||||
|
||||
Assert.That(searchBuilder.Get(1).Id, Is.EqualTo(1));
|
||||
Assert.That(searchBuilder.Get(2).Id, Is.EqualTo(2));
|
||||
Assert.That(searchBuilder.Get(3).Id, Is.EqualTo(3));
|
||||
Assert.That(searchBuilder.Get(1).ContentItemId, Is.EqualTo(1));
|
||||
Assert.That(searchBuilder.Get(2).ContentItemId, Is.EqualTo(2));
|
||||
Assert.That(searchBuilder.Get(3).ContentItemId, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProviderShouldStoreSettings() {
|
||||
_provider.CreateIndex("default");
|
||||
Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(DefaultIndexProvider.DefaultMinDateTime));
|
||||
Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(LuceneIndexProvider.DefaultMinDateTime));
|
||||
|
||||
_provider.SetLastIndexUtc("default", new DateTime(2010, 1, 1, 1, 1, 1, 1));
|
||||
Assert.That(_provider.GetLastIndexUtc("default"), Is.EqualTo(new DateTime(2010, 1, 1, 1, 1, 1, 0)));
|
||||
|
||||
_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(LuceneIndexProvider.DefaultMinDateTime));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -223,14 +219,14 @@ namespace Orchard.Tests.Indexing {
|
||||
|
||||
[Test]
|
||||
public void IsDirtyShouldBeFalseForNewDocuments() {
|
||||
IIndexDocument doc = _provider.New(1);
|
||||
IDocumentIndex doc = _provider.New(1);
|
||||
Assert.That(doc.IsDirty, Is.False);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void IsDirtyShouldBeTrueWhenIndexIsModified() {
|
||||
IIndexDocument doc;
|
||||
IDocumentIndex doc;
|
||||
|
||||
doc = _provider.New(1);
|
||||
doc.Add("foo", "value");
|
@@ -6,11 +6,11 @@ using NUnit.Framework;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.Indexing;
|
||||
using Orchard.Core.Indexing.Lucene;
|
||||
using Orchard.Indexing.Services;
|
||||
using Orchard.Tests.FileSystems.AppData;
|
||||
|
||||
namespace Orchard.Core.Tests.Indexing {
|
||||
public class DefaultSearchBuilderTests {
|
||||
namespace Orchard.Tests.Modules.Indexing {
|
||||
public class LuceneSearchBuilderTests {
|
||||
private IContainer _container;
|
||||
private IIndexProvider _provider;
|
||||
private IAppDataFolder _appDataFolder;
|
||||
@@ -32,7 +32,7 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
_appDataFolder = AppDataFolderTests.CreateAppDataFolder(_basePath);
|
||||
|
||||
var builder = new ContainerBuilder();
|
||||
builder.RegisterType<DefaultIndexProvider>().As<IIndexProvider>();
|
||||
builder.RegisterType<LuceneIndexProvider>().As<IIndexProvider>();
|
||||
builder.RegisterInstance(_appDataFolder).As<IAppDataFolder>();
|
||||
|
||||
// setting up a ShellSettings instance
|
||||
@@ -50,7 +50,7 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default",
|
||||
_provider.New(42)
|
||||
.Add("title", "title1 title2 title3")
|
||||
.Add("title", "title1 title2 title3").Analyze()
|
||||
.Add("date", new DateTime(2010, 05, 28, 14, 13, 56, 123))
|
||||
);
|
||||
|
||||
@@ -60,7 +60,6 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
Assert.IsNotNull(_provider.CreateSearchBuilder("default").WithField("title", "title2").Search().FirstOrDefault());
|
||||
Assert.IsNotNull(_provider.CreateSearchBuilder("default").WithField("title", "title3").Search().FirstOrDefault());
|
||||
Assert.IsNull(_provider.CreateSearchBuilder("default").WithField("title", "title4").Search().FirstOrDefault());
|
||||
Assert.IsNotNull(_provider.CreateSearchBuilder("default").WithField("title", "title").Search().FirstOrDefault());
|
||||
|
||||
}
|
||||
|
||||
@@ -72,9 +71,9 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
_provider.Store("default", _provider.New(3));
|
||||
|
||||
|
||||
Assert.That(_searchBuilder.Get(1).Id, Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.Get(2).Id, Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.Get(3).Id, Is.EqualTo(3));
|
||||
Assert.That(_searchBuilder.Get(1).ContentItemId, Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.Get(2).ContentItemId, Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.Get(3).ContentItemId, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -86,7 +85,7 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
|
||||
|
||||
Assert.That(_searchBuilder.WithField("title", "cat").Search().Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("title", "cat").Search().Any(hit => new[] { 1, 3 }.Contains(hit.Id)), Is.True);
|
||||
Assert.That(_searchBuilder.WithField("title", "cat").Search().Any(hit => new[] { 1, 3 }.Contains(hit.ContentItemId)), Is.True);
|
||||
|
||||
}
|
||||
|
||||
@@ -99,8 +98,6 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
|
||||
Assert.That(_searchBuilder.WithField("title", "dog").Count(), Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithField("title", "cat").Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("title", "c").Count(), Is.EqualTo(2));
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -110,12 +107,12 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
_provider.Store("default", _provider.New(2).Add("date", new DateTime(2010, 05, 28, 12, 30, 30)));
|
||||
_provider.Store("default", _provider.New(3).Add("date", new DateTime(2010, 05, 28, 12, 30, 45)));
|
||||
|
||||
Assert.That(_searchBuilder.After("date", new DateTime(2010, 05, 28, 12, 30, 15)).Count(), Is.EqualTo(3));
|
||||
Assert.That(_searchBuilder.Before("date", new DateTime(2010, 05, 28, 12, 30, 45)).Count(), Is.EqualTo(3));
|
||||
Assert.That(_searchBuilder.After("date", new DateTime(2010, 05, 28, 12, 30, 15)).Before("date", new DateTime(2010, 05, 28, 12, 30, 45)).Count(), Is.EqualTo(3));
|
||||
Assert.That(_searchBuilder.After("date", new DateTime(2010, 05, 28, 12, 30, 16)).Before("date", new DateTime(2010, 05, 28, 12, 30, 44)).Count(), Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.After("date", new DateTime(2010, 05, 28, 12, 30, 46)).Count(), Is.EqualTo(0));
|
||||
Assert.That(_searchBuilder.Before("date", new DateTime(2010, 05, 28, 12, 30, 1)).Count(), Is.EqualTo(0));
|
||||
Assert.That(_searchBuilder.WithinRange("date", new DateTime(2010, 05, 28, 12, 30, 15), DateTime.MaxValue).Count(), Is.EqualTo(3));
|
||||
Assert.That(_searchBuilder.WithinRange("date", DateTime.MinValue, new DateTime(2010, 05, 28, 12, 30, 45)).Count(), Is.EqualTo(3));
|
||||
Assert.That(_searchBuilder.WithinRange("date", new DateTime(2010, 05, 28, 12, 30, 15), new DateTime(2010, 05, 28, 12, 30, 45)).Count(), Is.EqualTo(3));
|
||||
Assert.That(_searchBuilder.WithinRange("date", new DateTime(2010, 05, 28, 12, 30, 16), new DateTime(2010, 05, 28, 12, 30, 44)).Count(), Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithinRange("date", new DateTime(2010, 05, 28, 12, 30, 46), DateTime.MaxValue).Count(), Is.EqualTo(0));
|
||||
Assert.That(_searchBuilder.WithinRange("date", DateTime.MinValue, new DateTime(2010, 05, 28, 12, 30, 1)).Count(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -143,28 +140,28 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
[Test]
|
||||
public void ShouldSortByRelevance() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("body", "michaelson is in the kitchen"));
|
||||
_provider.Store("default", _provider.New(2).Add("body", "michael as a cousin named michael"));
|
||||
_provider.Store("default", _provider.New(3).Add("body", "speak inside the mic"));
|
||||
_provider.Store("default", _provider.New(4).Add("body", "a dog is pursuing a cat"));
|
||||
_provider.Store("default", _provider.New(5).Add("body", "the elephant can't catch up the dog"));
|
||||
_provider.Store("default", _provider.New(1).Add("body", "michael is in the kitchen").Analyze());
|
||||
_provider.Store("default", _provider.New(2).Add("body", "michael as a cousin named michel").Analyze());
|
||||
_provider.Store("default", _provider.New(3).Add("body", "speak inside the mic").Analyze());
|
||||
_provider.Store("default", _provider.New(4).Add("body", "a dog is pursuing a cat").Analyze());
|
||||
_provider.Store("default", _provider.New(5).Add("body", "the elephant can't catch up the dog").Analyze());
|
||||
|
||||
var michael = _searchBuilder.WithField("body", "mic").Search().ToList();
|
||||
Assert.That(michael.Count(), Is.EqualTo(3));
|
||||
var michael = _searchBuilder.WithField("body", "michael").Search().ToList();
|
||||
Assert.That(michael.Count(), Is.EqualTo(2));
|
||||
Assert.That(michael[0].Score >= michael[1].Score, Is.True);
|
||||
|
||||
// Sorting on score is always descending
|
||||
michael = _searchBuilder.WithField("body", "mic").Ascending().Search().ToList();
|
||||
Assert.That(michael.Count(), Is.EqualTo(3));
|
||||
michael = _searchBuilder.WithField("body", "michael").Ascending().Search().ToList();
|
||||
Assert.That(michael.Count(), Is.EqualTo(2));
|
||||
Assert.That(michael[0].Score >= michael[1].Score, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldSortByDate() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("date", new DateTime(2010, 05, 28, 12, 30, 15)));
|
||||
_provider.Store("default", _provider.New(2).Add("date", new DateTime(2010, 05, 28, 12, 30, 30)));
|
||||
_provider.Store("default", _provider.New(3).Add("date", new DateTime(2010, 05, 28, 12, 30, 45)));
|
||||
_provider.Store("default", _provider.New(1).Add("date", new DateTime(2010, 05, 28, 12, 30, 15)).Store());
|
||||
_provider.Store("default", _provider.New(2).Add("date", new DateTime(2010, 05, 28, 12, 30, 30)).Store());
|
||||
_provider.Store("default", _provider.New(3).Add("date", new DateTime(2010, 05, 28, 12, 30, 45)).Store());
|
||||
|
||||
var date = _searchBuilder.SortBy("date").Search().ToList();
|
||||
Assert.That(date.Count(), Is.EqualTo(3));
|
||||
@@ -180,13 +177,13 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
[Test]
|
||||
public void ShouldEscapeSpecialChars() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#"));
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++"));
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#").Analyze());
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++").Analyze());
|
||||
|
||||
var cs = _searchBuilder.WithField("body", "C#").Search().ToList();
|
||||
var cs = _searchBuilder.Parse("body", "C#").Search().ToList();
|
||||
Assert.That(cs.Count(), Is.EqualTo(2));
|
||||
|
||||
var cpp = _searchBuilder.WithField("body", "C++").Search().ToList();
|
||||
var cpp = _searchBuilder.Parse("body", "C++").Search().ToList();
|
||||
Assert.That(cpp.Count(), Is.EqualTo(2));
|
||||
|
||||
}
|
||||
@@ -194,42 +191,42 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
[Test]
|
||||
public void ShouldHandleMandatoryFields() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#"));
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++"));
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#").Analyze());
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++").Analyze());
|
||||
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").Search().ToList().Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Search().ToList().Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Mandatory().Search().ToList().Count(), Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Mandatory().Search().First().Id, Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Mandatory().Search().First().ContentItemId, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldHandleForbiddenFields() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#"));
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++"));
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#").Analyze());
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++").Analyze());
|
||||
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").Search().ToList().Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Search().ToList().Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Forbidden().Search().ToList().Count(), Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Forbidden().Search().First().Id, Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("body", "developped").Search().ToList().Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("body", "developped").WithField("body", "Orchard").Search().ToList().Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.WithField("body", "developped").WithField("body", "Orchard").Forbidden().Search().ToList().Count(), Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithField("body", "developped").WithField("body", "Orchard").Forbidden().Search().First().ContentItemId, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldHandleWeight() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#"));
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++"));
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Orchard has been developped in C#").Analyze());
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Windows has been developped in C++").Analyze());
|
||||
|
||||
Assert.That(_searchBuilder.WithField("body", "develop").WithField("body", "Orchard").Weighted(2).Search().First().Id, Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithField("body", "developped").WithField("body", "Orchard").Weighted(2).Search().First().ContentItemId, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldParseLuceneQueries() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Bradley is in the kitchen.").Add("title", "Beer and takos"));
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Renaud is also in the kitchen.").Add("title", "A love affair"));
|
||||
_provider.Store("default", _provider.New(3).Add("body", "Bertrand is a little bit jealous.").Add("title", "Soap opera"));
|
||||
_provider.Store("default", _provider.New(1).Add("body", "Bradley is in the kitchen.").Analyze().Add("title", "Beer and takos").Analyze());
|
||||
_provider.Store("default", _provider.New(2).Add("body", "Renaud is also in the kitchen.").Analyze().Add("title", "A love affair").Analyze());
|
||||
_provider.Store("default", _provider.New(3).Add("body", "Bertrand is a little bit jealous.").Analyze().Add("title", "Soap opera").Analyze());
|
||||
|
||||
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kitchen").Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kitchen bertrand").Count(), Is.EqualTo(3));
|
||||
@@ -238,9 +235,24 @@ namespace Orchard.Core.Tests.Indexing {
|
||||
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kit").Count(), Is.EqualTo(0));
|
||||
Assert.That(_searchBuilder.Parse(new[] { "body" }, "kit*").Count(), Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.Parse(new[] { "body", "title" }, "bradley love^3 soap").Count(), Is.EqualTo(3));
|
||||
Assert.That(_searchBuilder.Parse(new[] { "body", "title" }, "bradley love^3 soap").Search().First().Id, Is.EqualTo(2));
|
||||
Assert.That(_searchBuilder.Parse(new[] { "body", "title" }, "bradley love^3 soap").Search().First().ContentItemId, Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShouldFilterIntValues() {
|
||||
_provider.CreateIndex("default");
|
||||
_provider.Store("default", _provider.New(1).Add("field", 1));
|
||||
_provider.Store("default", _provider.New(2).Add("field", 22));
|
||||
_provider.Store("default", _provider.New(3).Add("field", 333));
|
||||
|
||||
Assert.That(_searchBuilder.WithField("field", 1).ExactMatch().Count(), Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithField("field", 22).ExactMatch().Count(), Is.EqualTo(1));
|
||||
Assert.That(_searchBuilder.WithField("field", 333).ExactMatch().Count(), Is.EqualTo(1));
|
||||
|
||||
Assert.That(_searchBuilder.WithField("field", 0).ExactMatch().Count(), Is.EqualTo(0));
|
||||
Assert.That(_searchBuilder.WithField("field", 2).ExactMatch().Count(), Is.EqualTo(0));
|
||||
Assert.That(_searchBuilder.WithField("field", 3).ExactMatch().Count(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -108,6 +108,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DatabaseEnabledTestsBase.cs" />
|
||||
<Compile Include="Indexing\LuceneIndexProviderTests.cs" />
|
||||
<Compile Include="Indexing\LuceneSearchBuilderTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Roles\Controllers\AdminControllerTests.cs" />
|
||||
<Compile Include="Roles\Services\RoleServiceTests.cs" />
|
||||
@@ -128,6 +130,10 @@
|
||||
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
|
||||
<Name>Orchard.Core</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.Indexing\Orchard.Indexing.csproj">
|
||||
<Project>{EA2B9121-EF54-40A6-A53E-6593C86EE696}</Project>
|
||||
<Name>Orchard.Indexing</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\Orchard.Web\Modules\Orchard.Pages\Orchard.Pages.csproj">
|
||||
<Project>{4A9C04A6-0986-4A92-A610-5F59FF273FB9}</Project>
|
||||
<Name>Orchard.Pages</Name>
|
||||
|
@@ -9,9 +9,9 @@ namespace Orchard.Core.Common.Handlers {
|
||||
public BodyAspectHandler(IRepository<BodyRecord> bodyRepository) {
|
||||
Filters.Add(StorageFilter.For(bodyRepository));
|
||||
|
||||
OnIndexing<BodyAspect>((context, bodyAspect) => context.IndexDocument
|
||||
.Add("body", bodyAspect.Record.Text, true).Store(false)
|
||||
.Add("format", bodyAspect.Record.Format).Analyze(false));
|
||||
OnIndexing<BodyAspect>((context, bodyAspect) => context.DocumentIndex
|
||||
.Add("body", bodyAspect.Record.Text, true).Analyze()
|
||||
.Add("format", bodyAspect.Record.Format).Store());
|
||||
}
|
||||
}
|
||||
}
|
@@ -53,12 +53,12 @@ namespace Orchard.Core.Common.Handlers {
|
||||
//OnGetEditorViewModel<CommonAspect>(GetEditor);
|
||||
//OnUpdateEditorViewModel<CommonAspect>(UpdateEditor);
|
||||
|
||||
OnIndexing<CommonAspect>((context, commonAspect) => context.IndexDocument
|
||||
.Add("type", commonAspect.ContentItem.ContentType).Analyze(false)
|
||||
.Add("author", commonAspect.Owner.UserName).Analyze(false)
|
||||
.Add("created", commonAspect.CreatedUtc ?? _clock.UtcNow).Analyze(false)
|
||||
.Add("published", commonAspect.PublishedUtc ?? _clock.UtcNow).Analyze(false)
|
||||
.Add("modified", commonAspect.ModifiedUtc ?? _clock.UtcNow).Analyze(false)
|
||||
OnIndexing<CommonAspect>((context, commonAspect) => context.DocumentIndex
|
||||
.Add("type", commonAspect.ContentItem.ContentType).Store()
|
||||
.Add("author", commonAspect.Owner.UserName).Store()
|
||||
.Add("created", commonAspect.CreatedUtc ?? _clock.UtcNow).Store()
|
||||
.Add("published", commonAspect.PublishedUtc ?? _clock.UtcNow).Store()
|
||||
.Add("modified", commonAspect.ModifiedUtc ?? _clock.UtcNow).Store()
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -36,8 +36,8 @@ namespace Orchard.Core.Common.Handlers {
|
||||
|
||||
OnCreated<RoutableAspect>((context, ra) => routableService.ProcessSlug(ra));
|
||||
|
||||
OnIndexing<RoutableAspect>((context, part) => context.IndexDocument
|
||||
.Add("slug", part.Slug).Analyze(false)
|
||||
OnIndexing<RoutableAspect>((context, part) => context.DocumentIndex
|
||||
.Add("slug", part.Slug)
|
||||
.Add("title", part.Title)
|
||||
);
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using JetBrains.Annotations;
|
||||
using System.Globalization;
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.Core.Localization.Models;
|
||||
using Orchard.Data;
|
||||
using Orchard.Localization;
|
||||
@@ -23,7 +24,10 @@ namespace Orchard.Core.Localization.Handlers {
|
||||
|
||||
OnLoaded<Localized>(LazyLoadHandlers);
|
||||
|
||||
OnIndexed<Localized>((context, localized) => context.IndexDocument.Add("culture", localized.Culture != null ? localized.Culture.Culture : _cultureManager.GetSiteCulture()).Store(false).Analyze(false));
|
||||
OnIndexed<Localized>((context, localized) => context.DocumentIndex
|
||||
.Add("culture", CultureInfo.GetCultureInfo(localized.Culture != null ? localized.Culture.Culture : _cultureManager.GetSiteCulture()).LCID)
|
||||
.Store()
|
||||
);
|
||||
}
|
||||
|
||||
public Localizer T { get; set; }
|
||||
|
@@ -39,10 +39,6 @@
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Lucene.Net, Version=2.9.2.2, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\lib\lucene.net\Lucene.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.ComponentModel.DataAnnotations">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
@@ -136,16 +132,6 @@
|
||||
<Compile Include="Feeds\Rss\RssResult.cs" />
|
||||
<Compile Include="HomePage\Controllers\HomeController.cs" />
|
||||
<Compile Include="HomePage\Routes.cs" />
|
||||
<Compile Include="Indexing\Commands\IndexingCommands.cs" />
|
||||
<Compile Include="Indexing\Lucene\DefaultIndexDocument.cs" />
|
||||
<Compile Include="Indexing\Lucene\DefaultIndexProvider.cs" />
|
||||
<Compile Include="Indexing\Lucene\DefaultSearchBuilder.cs" />
|
||||
<Compile Include="Indexing\Lucene\DefaultSearchHit.cs" />
|
||||
<Compile Include="Indexing\Models\IndexingTask.cs" />
|
||||
<Compile Include="Indexing\Models\IndexingTaskRecord.cs" />
|
||||
<Compile Include="Indexing\Services\CreateIndexingTaskHandler.cs" />
|
||||
<Compile Include="Indexing\Services\IndexingTaskExecutor.cs" />
|
||||
<Compile Include="Indexing\Services\IndexingTaskManager.cs" />
|
||||
<Compile Include="Localization\Handlers\LocalizedHandler.cs" />
|
||||
<Compile Include="Localization\Models\Localized.cs" />
|
||||
<Compile Include="Localization\Models\LocalizedRecord.cs" />
|
||||
@@ -232,7 +218,6 @@
|
||||
<Content Include="Contents\Views\EditorTemplates\Items\Contents.Item.ascx" />
|
||||
<Content Include="Contents\Views\Item\Preview.aspx" />
|
||||
<Content Include="Contents\Views\Item\Display.aspx" />
|
||||
<Content Include="Indexing\Module.txt" />
|
||||
<Content Include="Localization\Module.txt" />
|
||||
<Content Include="Routable\Module.txt" />
|
||||
<Content Include="Routable\Scripts\jquery.slugify.js" />
|
||||
|
@@ -1,7 +1,7 @@
|
||||
using Orchard.Localization;
|
||||
using Orchard.UI.Navigation;
|
||||
|
||||
namespace Orchard.Search {
|
||||
namespace Orchard.Indexing {
|
||||
public class AdminMenu : INavigationProvider {
|
||||
public Localizer T { get; set; }
|
||||
public string MenuName { get { return "admin"; } }
|
||||
@@ -9,7 +9,7 @@ namespace Orchard.Search {
|
||||
public void GetNavigation(NavigationBuilder builder) {
|
||||
builder.Add(T("Site Configuration"), "11",
|
||||
menu => menu
|
||||
.Add(T("Search Index"), "10.0", item => item.Action("Index", "Admin", new {area = "Orchard.Search"})
|
||||
.Add(T("Search Index"), "10.0", item => item.Action("Index", "Admin", new {area = "Orchard.Indexing"})
|
||||
.Permission(Permissions.ManageSearchIndex)));
|
||||
}
|
||||
}
|
@@ -7,7 +7,7 @@ using Orchard.Indexing;
|
||||
using Orchard.Security;
|
||||
using Orchard.Tasks.Indexing;
|
||||
|
||||
namespace Orchard.Core.Indexing.Commands {
|
||||
namespace Orchard.Indexing.Commands {
|
||||
public class IndexingCommands : DefaultOrchardCommandHandler {
|
||||
private readonly IEnumerable<IIndexNotifierHandler> _indexNotifierHandlers;
|
||||
private readonly IIndexManager _indexManager;
|
@@ -0,0 +1,50 @@
|
||||
using System.Web.Mvc;
|
||||
using Orchard.Indexing.Services;
|
||||
using Orchard.Localization;
|
||||
using Orchard.UI.Notify;
|
||||
using Orchard.Indexing.ViewModels;
|
||||
|
||||
namespace Orchard.Indexing.Controllers {
|
||||
public class AdminController : Controller {
|
||||
private readonly IIndexingService _indexingService;
|
||||
|
||||
public AdminController(IIndexingService indexingService, IOrchardServices services) {
|
||||
_indexingService = indexingService;
|
||||
Services = services;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public IOrchardServices Services { get; private set; }
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public ActionResult Index() {
|
||||
var viewModel = new IndexViewModel {HasIndexToManage = _indexingService.HasIndexToManage, IndexUpdatedUtc = _indexingService.GetIndexUpdatedUtc()};
|
||||
|
||||
if (!viewModel.HasIndexToManage)
|
||||
Services.Notifier.Information(T("There is no search index to manage for this site."));
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult Update() {
|
||||
if ( !Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index.")) )
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
_indexingService.UpdateIndex();
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult Rebuild() {
|
||||
if ( !Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index.")) )
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
_indexingService.RebuildIndex();
|
||||
_indexingService.UpdateIndex();
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ using Orchard.ContentManagement;
|
||||
using Orchard.Core.Common.Models;
|
||||
using Orchard.Tasks.Indexing;
|
||||
|
||||
namespace Orchard.Core.Indexing.Services {
|
||||
namespace Orchard.Indexing.Handlers {
|
||||
/// <summary>
|
||||
/// Intercepts the ContentHandler events to create indexing tasks when a content item
|
||||
/// is published, and to delete them when the content item is unpublished.
|
@@ -2,7 +2,7 @@ using System;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Tasks.Indexing;
|
||||
|
||||
namespace Orchard.Core.Indexing.Models {
|
||||
namespace Orchard.Indexing.Models {
|
||||
public class IndexingTask : IIndexingTask {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IndexingTaskRecord _record;
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Orchard.ContentManagement.Records;
|
||||
|
||||
namespace Orchard.Core.Indexing.Models {
|
||||
namespace Orchard.Indexing.Models {
|
||||
public class IndexingTaskRecord {
|
||||
|
||||
public const int Update = 0;
|
@@ -1,21 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Mvc;
|
||||
using Lucene.Net.Documents;
|
||||
using Orchard.Indexing;
|
||||
using Orchard.Mvc.Html;
|
||||
using Orchard.Utility.Extensions;
|
||||
|
||||
namespace Orchard.Core.Indexing.Lucene {
|
||||
namespace Orchard.Indexing.Models {
|
||||
|
||||
public class DefaultIndexDocument : IIndexDocument {
|
||||
public class LuceneDocumentIndex : IDocumentIndex {
|
||||
|
||||
public List<AbstractField> Fields { get; private set; }
|
||||
private AbstractField _previousField;
|
||||
|
||||
public int Id { get; private set; }
|
||||
|
||||
public DefaultIndexDocument(int documentId) {
|
||||
public LuceneDocumentIndex(int documentId) {
|
||||
Fields = new List<AbstractField>();
|
||||
SetContentItemId(documentId);
|
||||
IsDirty = false;
|
||||
@@ -23,11 +20,11 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
|
||||
public bool IsDirty { get; private set; }
|
||||
|
||||
public IIndexDocument Add(string name, string value) {
|
||||
public IDocumentIndex Add(string name, string value) {
|
||||
return Add(name, value, false);
|
||||
}
|
||||
|
||||
public IIndexDocument Add(string name, string value, bool removeTags) {
|
||||
public IDocumentIndex Add(string name, string value, bool removeTags) {
|
||||
AppendPreviousField();
|
||||
|
||||
if(value == null) {
|
||||
@@ -38,68 +35,54 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
value = value.RemoveTags();
|
||||
}
|
||||
|
||||
_previousField = new Field(name, value, Field.Store.YES, Field.Index.ANALYZED);
|
||||
_previousField = new Field(name, value, Field.Store.NO, Field.Index.NOT_ANALYZED);
|
||||
IsDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIndexDocument Add(string name, DateTime value) {
|
||||
public IDocumentIndex Add(string name, DateTime value) {
|
||||
return Add(name, DateTools.DateToString(value, DateTools.Resolution.SECOND));
|
||||
}
|
||||
|
||||
public IDocumentIndex Add(string name, int value) {
|
||||
AppendPreviousField();
|
||||
_previousField = new Field(name, DateTools.DateToString(value, DateTools.Resolution.SECOND), Field.Store.YES, Field.Index.NOT_ANALYZED);
|
||||
_previousField = new NumericField(name, Field.Store.NO, true).SetIntValue(value);
|
||||
IsDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIndexDocument Add(string name, int value) {
|
||||
public IDocumentIndex Add(string name, bool value) {
|
||||
return Add(name, value.ToString());
|
||||
}
|
||||
|
||||
public IDocumentIndex Add(string name, float value) {
|
||||
AppendPreviousField();
|
||||
_previousField = new NumericField(name, Field.Store.YES, true).SetIntValue(value);
|
||||
_previousField = new NumericField(name, Field.Store.NO, true).SetFloatValue(value);
|
||||
IsDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIndexDocument Add(string name, bool value) {
|
||||
AppendPreviousField();
|
||||
_previousField = new Field(name, value.ToString().ToLower(), Field.Store.YES, Field.Index.NOT_ANALYZED);
|
||||
IsDirty = true;
|
||||
return this;
|
||||
public IDocumentIndex Add(string name, object value) {
|
||||
return Add(name, value.ToString());
|
||||
}
|
||||
|
||||
public IIndexDocument Add(string name, float value) {
|
||||
AppendPreviousField();
|
||||
_previousField = new NumericField(name, Field.Store.YES, true).SetFloatValue(value);
|
||||
IsDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIndexDocument Add(string name, object value) {
|
||||
AppendPreviousField();
|
||||
_previousField = new Field(name, value.ToString(), Field.Store.NO, Field.Index.NOT_ANALYZED);
|
||||
IsDirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIndexDocument Store(bool store) {
|
||||
public IDocumentIndex Store() {
|
||||
EnsurePreviousField();
|
||||
if(store != _previousField.IsStored()) {
|
||||
var index = _previousField.IsTokenized() ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED;
|
||||
_previousField = new Field(_previousField.Name(), _previousField.StringValue(), store ? Field.Store.YES : Field.Store.NO, index);
|
||||
}
|
||||
_previousField = new Field(_previousField.Name(), _previousField.StringValue(), Field.Store.YES, index);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIndexDocument Analyze(bool analyze) {
|
||||
public IDocumentIndex Analyze() {
|
||||
EnsurePreviousField();
|
||||
if (_previousField.IsTokenized() == analyze) {
|
||||
return this;
|
||||
}
|
||||
|
||||
var index = analyze ? Field.Index.ANALYZED : Field.Index.NOT_ANALYZED;
|
||||
var index = Field.Index.ANALYZED;
|
||||
var store = _previousField.IsStored() ? Field.Store.YES : Field.Store.NO;
|
||||
_previousField = new Field(_previousField.Name(), _previousField.StringValue(), store, index);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIndexDocument SetContentItemId(int id) {
|
||||
public IDocumentIndex SetContentItemId(int id) {
|
||||
Id = id;
|
||||
Fields.Add(new Field("id", id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
|
||||
return this;
|
@@ -1,21 +1,20 @@
|
||||
using Lucene.Net.Documents;
|
||||
using System.Globalization;
|
||||
using Lucene.Net.Util;
|
||||
using Orchard.Indexing;
|
||||
|
||||
namespace Orchard.Core.Indexing.Lucene {
|
||||
public class DefaultSearchHit : ISearchHit {
|
||||
namespace Orchard.Indexing.Models {
|
||||
public class LuceneSearchHit : ISearchHit {
|
||||
private readonly Document _doc;
|
||||
private readonly float _score;
|
||||
|
||||
public float Score { get { return _score; } }
|
||||
|
||||
public DefaultSearchHit(Document document, float score) {
|
||||
public LuceneSearchHit(Document document, float score) {
|
||||
_doc = document;
|
||||
_score = score;
|
||||
}
|
||||
|
||||
public int Id { get { return int.Parse(GetString("id")); } }
|
||||
public int ContentItemId { get { return int.Parse(GetString("id")); } }
|
||||
|
||||
public int GetInt(string name) {
|
||||
return NumericUtils.PrefixCodedToInt(_doc.GetField(name).StringValue());
|
@@ -6,6 +6,6 @@ version: 0.1
|
||||
orchardversion: 0.1.2010.0312
|
||||
description: The Indexing module enables the site to be indexed. The index generated by this module can then be used by the search module to provide an integrated full-text search experience to a web site.
|
||||
features:
|
||||
Indexing:
|
||||
Orchard.Indexing:
|
||||
Description: Indexing services based on Lucene.
|
||||
Category: Core
|
||||
Category: Search
|
123
src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj
Normal file
123
src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj
Normal file
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProductVersion>
|
||||
</ProductVersion>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
<ProjectGuid>{EA2B9121-EF54-40A6-A53E-6593C86EE696}</ProjectGuid>
|
||||
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Orchard.Indexing</RootNamespace>
|
||||
<AssemblyName>Orchard.Indexing</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Lucene.Net">
|
||||
<HintPath>..\..\..\..\lib\lucene.net\Lucene.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Web.DynamicData" />
|
||||
<Reference Include="System.Web.Entity" />
|
||||
<Reference Include="System.Web.ApplicationServices" />
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Web.Services" />
|
||||
<Reference Include="System.EnterpriseServices" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Module.txt" />
|
||||
<Content Include="Views\Admin\Index.ascx" />
|
||||
<Content Include="Web.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="Commands\IndexingCommands.cs" />
|
||||
<Compile Include="Controllers\AdminController.cs" />
|
||||
<Compile Include="Handlers\CreateIndexingTaskHandler.cs" />
|
||||
<Compile Include="Models\IndexingTask.cs" />
|
||||
<Compile Include="Models\IndexingTaskRecord.cs" />
|
||||
<Compile Include="Models\LuceneDocumentIndex.cs" />
|
||||
<Compile Include="Permissions.cs" />
|
||||
<Compile Include="Services\IndexingBackgroundTask.cs" />
|
||||
<Compile Include="Services\IndexingTaskExecutor.cs" />
|
||||
<Compile Include="Services\IndexingTaskManager.cs" />
|
||||
<Compile Include="Services\IIndexService.cs" />
|
||||
<Compile Include="Services\IndexSynLock.cs" />
|
||||
<Compile Include="Services\LuceneIndexProvider.cs" />
|
||||
<Compile Include="Services\LuceneSearchBuilder.cs" />
|
||||
<Compile Include="Models\LuceneSearchHit.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Services\IndexService.cs" />
|
||||
<Compile Include="ViewModels\IndexViewModel.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
|
||||
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
|
||||
<Name>Orchard.Framework</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
|
||||
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
|
||||
<Name>Orchard.Core</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Views\Web.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" />
|
||||
<ProjectExtensions>
|
||||
<VisualStudio>
|
||||
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
|
||||
<WebProjectProperties>
|
||||
<UseIIS>False</UseIIS>
|
||||
<AutoAssignPort>True</AutoAssignPort>
|
||||
<DevelopmentServerPort>35644</DevelopmentServerPort>
|
||||
<DevelopmentServerVPath>/</DevelopmentServerVPath>
|
||||
<IISUrl>
|
||||
</IISUrl>
|
||||
<NTLMAuthentication>False</NTLMAuthentication>
|
||||
<UseCustomServer>False</UseCustomServer>
|
||||
<CustomServerUrl>
|
||||
</CustomServerUrl>
|
||||
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
|
||||
</WebProjectProperties>
|
||||
</FlavorProperties>
|
||||
</VisualStudio>
|
||||
</ProjectExtensions>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
@@ -1,27 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Security.Permissions;
|
||||
|
||||
namespace Orchard.Search {
|
||||
namespace Orchard.Indexing {
|
||||
public class Permissions : IPermissionProvider {
|
||||
public static readonly Permission ManageSearchIndex = new Permission { Description = "Manage Search Index", Name = "ManageSearchIndex" };
|
||||
|
||||
public string ModuleName {
|
||||
get {
|
||||
return "Search";
|
||||
return "Indexing";
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Permission> GetPermissions() {
|
||||
return new Permission[] {
|
||||
ManageSearchIndex,
|
||||
};
|
||||
return new[] { ManageSearchIndex };
|
||||
}
|
||||
|
||||
public IEnumerable<PermissionStereotype> GetDefaultStereotypes() {
|
||||
return new[] {
|
||||
new PermissionStereotype {
|
||||
Name = "Administrator",
|
||||
Permissions = new[] {ManageSearchIndex}
|
||||
Permissions = new [] { ManageSearchIndex }
|
||||
},
|
||||
};
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Orchard.Indexing")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Microsoft")]
|
||||
[assembly: AssemblyProduct("Orchard.Indexing")]
|
||||
[assembly: AssemblyCopyright("Copyright © Microsoft 2010")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("ab8e1272-1749-4e27-a123-83c8d47e321a")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Revision and Build Numbers
|
||||
// by using the '*' as shown below:
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Orchard.Indexing.Services {
|
||||
public interface IIndexingService : IDependency {
|
||||
bool HasIndexToManage { get; }
|
||||
void RebuildIndex();
|
||||
void UpdateIndex();
|
||||
DateTime GetIndexUpdatedUtc();
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Localization.Services;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Indexing.Services
|
||||
{
|
||||
public class IndexingService : IIndexingService
|
||||
{
|
||||
private const string SearchIndexName = "Search";
|
||||
private readonly IIndexManager _indexManager;
|
||||
private readonly IEnumerable<IIndexNotifierHandler> _indexNotifierHandlers;
|
||||
|
||||
public IndexingService(IOrchardServices services, IIndexManager indexManager, IEnumerable<IIndexNotifierHandler> indexNotifierHandlers, ICultureManager cultureManager) {
|
||||
Services = services;
|
||||
_indexManager = indexManager;
|
||||
_indexNotifierHandlers = indexNotifierHandlers;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public IOrchardServices Services { get; set; }
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public bool HasIndexToManage {
|
||||
get { return _indexManager.HasIndexProvider(); }
|
||||
}
|
||||
|
||||
void IIndexingService.RebuildIndex() {
|
||||
if (!_indexManager.HasIndexProvider()) {
|
||||
Services.Notifier.Warning(T("There is no search index to rebuild."));
|
||||
return;
|
||||
}
|
||||
|
||||
var searchProvider = _indexManager.GetSearchIndexProvider();
|
||||
if (searchProvider.Exists(SearchIndexName))
|
||||
searchProvider.DeleteIndex(SearchIndexName);
|
||||
|
||||
searchProvider.CreateIndex(SearchIndexName); // or just reset the updated date and let the background process recreate the index
|
||||
|
||||
Services.Notifier.Information(T("The search index has been rebuilt."));
|
||||
}
|
||||
|
||||
void IIndexingService.UpdateIndex() {
|
||||
|
||||
foreach(var handler in _indexNotifierHandlers) {
|
||||
handler.UpdateIndex(SearchIndexName);
|
||||
}
|
||||
|
||||
Services.Notifier.Information(T("The search index has been updated."));
|
||||
}
|
||||
|
||||
DateTime IIndexingService.GetIndexUpdatedUtc() {
|
||||
return !HasIndexToManage
|
||||
? DateTime.MinValue
|
||||
: _indexManager.GetSearchIndexProvider().GetLastIndexUtc(SearchIndexName);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
|
||||
namespace Orchard.Indexing.Services {
|
||||
public interface IIndexSynLock : ISingletonDependency {
|
||||
object GetSynLock(string indexName);
|
||||
}
|
||||
|
||||
public class IndexSynLock : IIndexSynLock {
|
||||
private readonly Dictionary<string, object> _synLocks;
|
||||
private readonly object _synLock = new object();
|
||||
|
||||
public IndexSynLock() {
|
||||
_synLocks =new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
public object GetSynLock(string indexName) {
|
||||
lock(_synLock) {
|
||||
if(!_synLocks.ContainsKey(indexName)) {
|
||||
_synLocks[indexName] = new object();
|
||||
}
|
||||
return _synLocks[indexName];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Tasks;
|
||||
|
||||
namespace Orchard.Indexing.Services {
|
||||
/// <summary>
|
||||
/// Regularly fires IIndexNotifierHandler events
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class IndexingBackgroundTask : IBackgroundTask {
|
||||
private readonly IIndexNotifierHandler _indexNotifierHandler;
|
||||
private const string SearchIndexName = "Search";
|
||||
|
||||
public IndexingBackgroundTask(
|
||||
IIndexNotifierHandler indexNotifierHandler) {
|
||||
_indexNotifierHandler = indexNotifierHandler;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public void Sweep() {
|
||||
_indexNotifierHandler.UpdateIndex(SearchIndexName);
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,60 +3,49 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.ContentManagement.Handlers;
|
||||
using Orchard.Data;
|
||||
using Orchard.Indexing;
|
||||
using Orchard.Indexing.Models;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Services;
|
||||
using Orchard.Tasks;
|
||||
using Orchard.Core.Indexing.Models;
|
||||
using Orchard.Tasks.Indexing;
|
||||
using Orchard.Indexing;
|
||||
|
||||
namespace Orchard.Core.Indexing.Services {
|
||||
namespace Orchard.Indexing.Services {
|
||||
/// <summary>
|
||||
/// Contains the logic which is regularly executed to retrieve index information from multiple content handlers.
|
||||
/// </summary>
|
||||
[UsedImplicitly]
|
||||
public class IndexingTaskExecutor : IBackgroundTask, IIndexNotifierHandler {
|
||||
public class IndexingTaskExecutor : IIndexNotifierHandler {
|
||||
private readonly IClock _clock;
|
||||
private readonly IRepository<IndexingTaskRecord> _repository;
|
||||
private readonly IEnumerable<IContentHandler> _handlers;
|
||||
private IIndexProvider _indexProvider;
|
||||
private readonly IIndexManager _indexManager;
|
||||
private readonly IIndexingTaskManager _indexingTaskManager;
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IIndexSynLock _indexSynLock;
|
||||
private const string SearchIndexName = "Search";
|
||||
|
||||
private readonly object _synLock = new object();
|
||||
|
||||
public IndexingTaskExecutor(
|
||||
IClock clock,
|
||||
IRepository<IndexingTaskRecord> repository,
|
||||
IEnumerable<IContentHandler> handlers,
|
||||
IIndexManager indexManager,
|
||||
IIndexingTaskManager indexingTaskManager,
|
||||
IContentManager contentManager) {
|
||||
IContentManager contentManager,
|
||||
IIndexSynLock indexSynLock) {
|
||||
_clock = clock;
|
||||
_repository = repository;
|
||||
_indexManager = indexManager;
|
||||
_handlers = handlers;
|
||||
_indexingTaskManager = indexingTaskManager;
|
||||
_contentManager = contentManager;
|
||||
_indexSynLock = indexSynLock;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public void UpdateIndex(string indexName) {
|
||||
if (indexName == SearchIndexName) {
|
||||
Sweep();
|
||||
}
|
||||
}
|
||||
var synLock = _indexSynLock.GetSynLock(SearchIndexName);
|
||||
|
||||
public void Sweep() {
|
||||
|
||||
if ( !System.Threading.Monitor.TryEnter(_synLock) ) {
|
||||
if ( !System.Threading.Monitor.TryEnter(synLock) ) {
|
||||
Logger.Information("Index was requested but was already running");
|
||||
return;
|
||||
}
|
||||
@@ -68,8 +57,8 @@ namespace Orchard.Core.Indexing.Services {
|
||||
}
|
||||
|
||||
_indexProvider = _indexManager.GetSearchIndexProvider();
|
||||
var updateIndexDocuments = new List<IIndexDocument>();
|
||||
var lastIndexing = DateTime.UtcNow;
|
||||
var updateIndexDocuments = new List<IDocumentIndex>();
|
||||
DateTime lastIndexing;
|
||||
|
||||
// Do we need to rebuild the full index (first time module is used, or rebuild index requested) ?
|
||||
if (_indexProvider.IsEmpty(SearchIndexName)) {
|
||||
@@ -81,22 +70,11 @@ namespace Orchard.Core.Indexing.Services {
|
||||
// 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)
|
||||
};
|
||||
var documentIndex = _indexProvider.New(contentItem.Id);
|
||||
|
||||
// dispatch to handlers to retrieve index information
|
||||
foreach (var handler in _handlers) {
|
||||
handler.Indexing(context);
|
||||
}
|
||||
|
||||
if ( context.IndexDocument.IsDirty ) {
|
||||
updateIndexDocuments.Add(context.IndexDocument);
|
||||
|
||||
foreach ( var handler in _handlers ) {
|
||||
handler.Indexed(context);
|
||||
}
|
||||
_contentManager.Index(contentItem, documentIndex);
|
||||
if(documentIndex.IsDirty) {
|
||||
updateIndexDocuments.Add(documentIndex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex) {
|
||||
@@ -139,22 +117,11 @@ namespace Orchard.Core.Indexing.Services {
|
||||
var task = new IndexingTask(_contentManager, taskRecord);
|
||||
|
||||
try {
|
||||
var context = new IndexContentContext {
|
||||
ContentItem = task.ContentItem,
|
||||
IndexDocument = _indexProvider.New(task.ContentItem.Id)
|
||||
};
|
||||
var documentIndex = _indexProvider.New(task.ContentItem.Id);
|
||||
|
||||
// dispatch to handlers to retrieve index information
|
||||
foreach (var handler in _handlers) {
|
||||
handler.Indexing(context);
|
||||
}
|
||||
|
||||
if ( context.IndexDocument.IsDirty ) {
|
||||
updateIndexDocuments.Add(context.IndexDocument);
|
||||
|
||||
foreach (var handler in _handlers) {
|
||||
handler.Indexed(context);
|
||||
}
|
||||
_contentManager.Index(task.ContentItem, documentIndex);
|
||||
if ( documentIndex.IsDirty ) {
|
||||
updateIndexDocuments.Add(documentIndex);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -173,7 +140,7 @@ namespace Orchard.Core.Indexing.Services {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
System.Threading.Monitor.Exit(_synLock);
|
||||
System.Threading.Monitor.Exit(synLock);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Orchard.ContentManagement;
|
||||
using Orchard.Data;
|
||||
using Orchard.Indexing.Models;
|
||||
using Orchard.Logging;
|
||||
using Orchard.Tasks.Scheduling;
|
||||
using Orchard.Utility.Extensions;
|
||||
using Orchard.Tasks.Indexing;
|
||||
using Orchard.Core.Indexing.Models;
|
||||
using Orchard.Services;
|
||||
|
||||
namespace Orchard.Core.Indexing.Services {
|
||||
namespace Orchard.Indexing.Services {
|
||||
[UsedImplicitly]
|
||||
public class IndexingTaskManager : IIndexingTaskManager {
|
||||
private readonly IContentManager _contentManager;
|
||||
private readonly IRepository<IndexingTaskRecord> _repository;
|
||||
private readonly IClock _clock;
|
||||
|
||||
@@ -24,7 +20,6 @@ namespace Orchard.Core.Indexing.Services {
|
||||
IClock clock) {
|
||||
_clock = clock;
|
||||
_repository = repository;
|
||||
_contentManager = contentManager;
|
||||
Logger = NullLogger.Instance;
|
||||
}
|
||||
|
@@ -9,17 +9,17 @@ using Lucene.Net.Index;
|
||||
using Lucene.Net.Store;
|
||||
using Orchard.Environment.Configuration;
|
||||
using Orchard.FileSystems.AppData;
|
||||
using Orchard.Indexing;
|
||||
using Directory = Lucene.Net.Store.Directory;
|
||||
using Version = Lucene.Net.Util.Version;
|
||||
using Orchard.Indexing.Models;
|
||||
using Orchard.Logging;
|
||||
using System.Xml.Linq;
|
||||
using Directory = Lucene.Net.Store.Directory;
|
||||
using Version = Lucene.Net.Util.Version;
|
||||
|
||||
namespace Orchard.Core.Indexing.Lucene {
|
||||
namespace Orchard.Indexing.Services {
|
||||
/// <summary>
|
||||
/// Represents the default implementation of an IIndexProvider, based on Lucene
|
||||
/// </summary>
|
||||
public class DefaultIndexProvider : IIndexProvider {
|
||||
public class LuceneIndexProvider : IIndexProvider {
|
||||
private readonly IAppDataFolder _appDataFolder;
|
||||
private readonly ShellSettings _shellSettings;
|
||||
public static readonly Version LuceneVersion = Version.LUCENE_29;
|
||||
@@ -31,7 +31,7 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public DefaultIndexProvider(IAppDataFolder appDataFolder, ShellSettings shellSettings) {
|
||||
public LuceneIndexProvider(IAppDataFolder appDataFolder, ShellSettings shellSettings) {
|
||||
_appDataFolder = appDataFolder;
|
||||
_shellSettings = shellSettings;
|
||||
_analyzer = CreateAnalyzer();
|
||||
@@ -62,7 +62,7 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
return FSDirectory.Open(directoryInfo);
|
||||
}
|
||||
|
||||
private static Document CreateDocument(DefaultIndexDocument indexDocument) {
|
||||
private static Document CreateDocument(LuceneDocumentIndex indexDocument) {
|
||||
var doc = new Document();
|
||||
|
||||
indexDocument.PrepareForIndexing();
|
||||
@@ -123,21 +123,21 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
}
|
||||
}
|
||||
|
||||
public void Store(string indexName, IIndexDocument indexDocument) {
|
||||
Store(indexName, new [] { (DefaultIndexDocument)indexDocument });
|
||||
public void Store(string indexName, IDocumentIndex indexDocument) {
|
||||
Store(indexName, new [] { (LuceneDocumentIndex)indexDocument });
|
||||
}
|
||||
|
||||
public void Store(string indexName, IEnumerable<IIndexDocument> indexDocuments) {
|
||||
Store(indexName, indexDocuments.Cast<DefaultIndexDocument>());
|
||||
public void Store(string indexName, IEnumerable<IDocumentIndex> indexDocuments) {
|
||||
Store(indexName, indexDocuments.Cast<LuceneDocumentIndex>());
|
||||
}
|
||||
|
||||
public void Store(string indexName, IEnumerable<DefaultIndexDocument> indexDocuments) {
|
||||
public void Store(string indexName, IEnumerable<LuceneDocumentIndex> indexDocuments) {
|
||||
if(indexDocuments.AsQueryable().Count() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var writer = new IndexWriter(GetDirectory(indexName), _analyzer, false, IndexWriter.MaxFieldLength.UNLIMITED);
|
||||
DefaultIndexDocument current = null;
|
||||
LuceneDocumentIndex current = null;
|
||||
|
||||
try {
|
||||
foreach ( var indexDocument in indexDocuments ) {
|
||||
@@ -188,12 +188,12 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
}
|
||||
}
|
||||
|
||||
public IIndexDocument New(int documentId) {
|
||||
return new DefaultIndexDocument(documentId);
|
||||
public IDocumentIndex New(int documentId) {
|
||||
return new LuceneDocumentIndex(documentId);
|
||||
}
|
||||
|
||||
public ISearchBuilder CreateSearchBuilder(string indexName) {
|
||||
return new DefaultSearchBuilder(GetDirectory(indexName));
|
||||
return new LuceneSearchBuilder(GetDirectory(indexName));
|
||||
}
|
||||
|
||||
private string GetSettingsFileName(string indexName) {
|
@@ -7,19 +7,19 @@ using Lucene.Net.Analysis.Tokenattributes;
|
||||
using Lucene.Net.Index;
|
||||
using Lucene.Net.Search;
|
||||
using Lucene.Net.Store;
|
||||
using Orchard.Indexing.Models;
|
||||
using Orchard.Logging;
|
||||
using Lucene.Net.Documents;
|
||||
using Orchard.Indexing;
|
||||
using Lucene.Net.QueryParsers;
|
||||
|
||||
namespace Orchard.Core.Indexing.Lucene {
|
||||
public class DefaultSearchBuilder : ISearchBuilder {
|
||||
namespace Orchard.Indexing.Services {
|
||||
public class LuceneSearchBuilder : ISearchBuilder {
|
||||
|
||||
private const int MaxResults = Int16.MaxValue;
|
||||
|
||||
private readonly Directory _directory;
|
||||
|
||||
private readonly Dictionary<string, BooleanClause[]> _fields;
|
||||
private readonly List<BooleanClause> _clauses;
|
||||
private int _count;
|
||||
private int _skip;
|
||||
private readonly Dictionary<string, DateTime> _before;
|
||||
@@ -31,16 +31,14 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
private string[] _defaultFields;
|
||||
|
||||
// pending clause attributes
|
||||
private string _field;
|
||||
private string _terms;
|
||||
private BooleanClause.Occur _occur;
|
||||
private bool _prefix;
|
||||
private bool _stem;
|
||||
private bool _exactMatch;
|
||||
private float _boost;
|
||||
private Query _query;
|
||||
|
||||
public ILogger Logger { get; set; }
|
||||
|
||||
public DefaultSearchBuilder(Directory directory) {
|
||||
public LuceneSearchBuilder(Directory directory) {
|
||||
_directory = directory;
|
||||
Logger = NullLogger.Instance;
|
||||
|
||||
@@ -48,14 +46,17 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
_skip = 0;
|
||||
_before = new Dictionary<string, DateTime>();
|
||||
_after = new Dictionary<string, DateTime>();
|
||||
_fields = new Dictionary<string, BooleanClause[]>();
|
||||
_clauses = new List<BooleanClause>();
|
||||
_sort = String.Empty;
|
||||
_sortDescending = true;
|
||||
_parse = String.Empty;
|
||||
_analyzer = DefaultIndexProvider.CreateAnalyzer();
|
||||
_analyzer = LuceneIndexProvider.CreateAnalyzer();
|
||||
|
||||
InitPendingClause();
|
||||
}
|
||||
public ISearchBuilder Parse(string defaultField, string query) {
|
||||
return Parse(new string[] {defaultField}, query);
|
||||
}
|
||||
|
||||
public ISearchBuilder Parse(string[] defaultFields, string query) {
|
||||
if ( defaultFields.Length == 0 ) {
|
||||
@@ -71,11 +72,60 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithField(string field, int value) {
|
||||
CreatePendingClause();
|
||||
_query = NumericRangeQuery.NewIntRange(field, value, value, true, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithinRange(string field, int min, int max) {
|
||||
CreatePendingClause();
|
||||
_query = NumericRangeQuery.NewIntRange(field, min, max, true, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithField(string field, float value) {
|
||||
CreatePendingClause();
|
||||
_query = NumericRangeQuery.NewFloatRange(field, value, value, true, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithinRange(string field, float min, float max) {
|
||||
CreatePendingClause();
|
||||
_query = NumericRangeQuery.NewFloatRange(field, min, max, true, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithField(string field, bool value) {
|
||||
CreatePendingClause();
|
||||
_query = new TermQuery(new Term(field, value.ToString()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithField(string field, DateTime value) {
|
||||
CreatePendingClause();
|
||||
_query = new TermQuery(new Term(field, DateTools.DateToString(value, DateTools.Resolution.SECOND)));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithinRange(string field, DateTime min, DateTime max) {
|
||||
CreatePendingClause();
|
||||
_query = new TermRangeQuery(field, DateTools.DateToString(min, DateTools.Resolution.SECOND), DateTools.DateToString(max, DateTools.Resolution.SECOND), true, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithinRange(string field, string min, string max) {
|
||||
CreatePendingClause();
|
||||
_query = new TermRangeQuery(field, QueryParser.Escape(min.ToLower()), QueryParser.Escape(min.ToLower()), true, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder WithField(string field, string value) {
|
||||
CreatePendingClause();
|
||||
|
||||
_field = field;
|
||||
_terms = value;
|
||||
if ( !String.IsNullOrWhiteSpace(value) ) {
|
||||
_query = new TermQuery(new Term(field, QueryParser.Escape(value.ToLower())));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -91,8 +141,7 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
}
|
||||
|
||||
public ISearchBuilder ExactMatch() {
|
||||
_prefix = false;
|
||||
_stem = false;
|
||||
_exactMatch = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -102,58 +151,29 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
}
|
||||
|
||||
private void InitPendingClause() {
|
||||
_field = String.Empty;
|
||||
_terms = String.Empty;
|
||||
_occur = BooleanClause.Occur.SHOULD;
|
||||
_prefix = true;
|
||||
_stem = true;
|
||||
_exactMatch = false;
|
||||
_query = null;
|
||||
_boost = 0;
|
||||
}
|
||||
|
||||
private void CreatePendingClause() {
|
||||
if(String.IsNullOrWhiteSpace(_field) || String.IsNullOrWhiteSpace(_terms)) {
|
||||
if(_query == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tokens = new List<string>();
|
||||
using ( var sr = new System.IO.StringReader(_terms) ) {
|
||||
var stream = _analyzer.TokenStream(_field, sr);
|
||||
|
||||
if(_stem) {
|
||||
stream = new PorterStemFilter(stream);
|
||||
if (_boost != 0) {
|
||||
_query.SetBoost(_boost);
|
||||
}
|
||||
|
||||
while ( stream.IncrementToken() ) {
|
||||
tokens.Add(( (TermAttribute)stream.GetAttribute(typeof(TermAttribute)) ).Term());
|
||||
if(!_exactMatch) {
|
||||
var termQuery = _query as TermQuery;
|
||||
if(termQuery != null) {
|
||||
_query = new PrefixQuery(termQuery.GetTerm());
|
||||
}
|
||||
}
|
||||
|
||||
var clauses = tokens
|
||||
.Where(k => !String.IsNullOrWhiteSpace(k)) // remove empty strings
|
||||
.Select(QueryParser.Escape) // escape special chars (e.g. C#)
|
||||
.Select(k => new Term(_field, k)) // creates the Term instance
|
||||
.Select(t => _prefix ? new PrefixQuery(t) as Query : new TermQuery(t) as Query) // apply the corresponding Query
|
||||
.Select(q => {
|
||||
if (_boost != 0) q.SetBoost(_boost);
|
||||
return q;
|
||||
})
|
||||
.Select(q => new BooleanClause(q, _occur)); // apply the corresponding clause
|
||||
|
||||
if ( !_fields.ContainsKey(_field) ) {
|
||||
_fields[_field] = new BooleanClause[0];
|
||||
}
|
||||
|
||||
_fields[_field] = _fields[_field].Union(clauses).ToArray();
|
||||
}
|
||||
|
||||
public ISearchBuilder After(string name, DateTime date) {
|
||||
_after[name] = date;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ISearchBuilder Before(string name, DateTime date) {
|
||||
_before[name] = date;
|
||||
return this;
|
||||
_clauses.Add(new BooleanClause(_query, _occur));
|
||||
}
|
||||
|
||||
public ISearchBuilder SortBy(string name) {
|
||||
@@ -189,29 +209,13 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
if(!String.IsNullOrWhiteSpace(_parse)) {
|
||||
|
||||
foreach ( var defaultField in _defaultFields ) {
|
||||
var clause = new BooleanClause(new QueryParser(DefaultIndexProvider.LuceneVersion, defaultField, DefaultIndexProvider.CreateAnalyzer()).Parse(_parse), BooleanClause.Occur.SHOULD);
|
||||
var clause = new BooleanClause(new QueryParser(LuceneIndexProvider.LuceneVersion, defaultField, LuceneIndexProvider.CreateAnalyzer()).Parse(_parse), BooleanClause.Occur.SHOULD);
|
||||
query.Add(clause);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( _fields.Keys.Count > 0 ) { // apply specific filters if defined
|
||||
foreach ( var clauses in _fields.Values ) {
|
||||
foreach( var clause in clauses)
|
||||
foreach( var clause in _clauses)
|
||||
query.Add(clause);
|
||||
}
|
||||
}
|
||||
|
||||
// apply date range filter ?
|
||||
foreach(string name in _before.Keys.Concat(_after.Keys)) {
|
||||
if ((_before.ContainsKey(name) && _before[name] != DateTime.MaxValue) || (_after.ContainsKey(name) && _after[name] != DateTime.MinValue)) {
|
||||
var filter = new TermRangeQuery("date",
|
||||
DateTools.DateToString(_after.ContainsKey(name) ? _after[name] : DateTime.MinValue, DateTools.Resolution.SECOND),
|
||||
DateTools.DateToString(_before.ContainsKey(name) ? _before[name] : DateTime.MaxValue, DateTools.Resolution.SECOND),
|
||||
true, true);
|
||||
query.Add(filter, BooleanClause.Occur.MUST);
|
||||
}
|
||||
}
|
||||
|
||||
if ( query.Clauses().Count == 0 ) { // get all documents ?
|
||||
query.Add(new TermRangeQuery("id", "0", "9", true, true), BooleanClause.Occur.SHOULD);
|
||||
@@ -247,15 +251,16 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
false,
|
||||
true);
|
||||
|
||||
Logger.Debug("Searching: {0}", query.ToString());
|
||||
searcher.Search(query, collector);
|
||||
|
||||
var results = new List<DefaultSearchHit>();
|
||||
var results = collector.TopDocs().scoreDocs
|
||||
.Skip(_skip)
|
||||
.Select(scoreDoc => new LuceneSearchHit(searcher.Doc(scoreDoc.doc), scoreDoc.score))
|
||||
.ToList();
|
||||
|
||||
foreach ( var scoreDoc in collector.TopDocs().scoreDocs.Skip(_skip) ) {
|
||||
results.Add(new DefaultSearchHit(searcher.Doc(scoreDoc.doc), scoreDoc.score));
|
||||
}
|
||||
Logger.Debug("Search results: {0}", results.Count);
|
||||
|
||||
Logger.Information("Search results: {0}", results.Count);
|
||||
return results;
|
||||
}
|
||||
finally {
|
||||
@@ -297,7 +302,7 @@ namespace Orchard.Core.Indexing.Lucene {
|
||||
var hits = searcher.Search(query, 1);
|
||||
Logger.Information("Search results: {0}", hits.scoreDocs.Length);
|
||||
if ( hits.scoreDocs.Length > 0 ) {
|
||||
return new DefaultSearchHit(searcher.Doc(hits.scoreDocs[0].doc), hits.scoreDocs[0].score);
|
||||
return new LuceneSearchHit(searcher.Doc(hits.scoreDocs[0].doc), hits.scoreDocs[0].score);
|
||||
}
|
||||
else {
|
||||
return null;
|
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using Orchard.Mvc.ViewModels;
|
||||
|
||||
namespace Orchard.Search.ViewModels {
|
||||
public class SearchIndexViewModel : BaseViewModel {
|
||||
namespace Orchard.Indexing.ViewModels {
|
||||
public class IndexViewModel : BaseViewModel {
|
||||
public bool HasIndexToManage { get; set; }
|
||||
//todo: hang the index updated date off here to show in the admin UI (e.g. -> index updated: June 4, 2010 [update index])
|
||||
public DateTime IndexUpdatedUtc { get; set; }
|
@@ -1,8 +1,8 @@
|
||||
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Search.ViewModels.SearchIndexViewModel>" %>
|
||||
<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl<Orchard.Indexing.ViewModels.IndexViewModel>" %>
|
||||
<%@ Import Namespace="Orchard.Mvc.Html" %><%
|
||||
Html.RegisterStyle("admin.css"); %>
|
||||
<h1><%:Html.TitleForPage(T("Search Index Management").ToString()) %></h1><%
|
||||
using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.Search"})) { %>
|
||||
using (Html.BeginForm("update", "admin", FormMethod.Post, new {area = "Orchard.Indexing"})) { %>
|
||||
<fieldset>
|
||||
<p><%:T("The search index was last updated {0}.", Html.DateTimeRelative(Model.IndexUpdatedUtc, T))%> <button type="submit" title="<%:T("Update the search index.") %>" class="primaryAction"><%:T("Update")%></button></p>
|
||||
<%:Html.AntiForgeryTokenOrchard() %>
|
34
src/Orchard.Web/Modules/Orchard.Indexing/Views/Web.config
Normal file
34
src/Orchard.Web/Modules/Orchard.Indexing/Views/Web.config
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0"?>
|
||||
<configuration>
|
||||
<system.web>
|
||||
<httpHandlers>
|
||||
<add path="*" verb="*"
|
||||
type="System.Web.HttpNotFoundHandler"/>
|
||||
</httpHandlers>
|
||||
|
||||
<!--
|
||||
Enabling request validation in view pages would cause validation to occur
|
||||
after the input has already been processed by the controller. By default
|
||||
MVC performs request validation before a controller processes the input.
|
||||
To change this behavior apply the ValidateInputAttribute to a
|
||||
controller or action.
|
||||
-->
|
||||
<pages
|
||||
validateRequest="false"
|
||||
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
|
||||
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
|
||||
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
|
||||
<controls>
|
||||
<add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" namespace="System.Web.Mvc" tagPrefix="mvc" />
|
||||
</controls>
|
||||
</pages>
|
||||
</system.web>
|
||||
|
||||
<system.webServer>
|
||||
<validation validateIntegratedModeConfiguration="false"/>
|
||||
<handlers>
|
||||
<remove name="BlockViewHandler"/>
|
||||
<add name="BlockViewHandler" path="*" verb="*" preCondition="integratedMode" type="System.Web.HttpNotFoundHandler"/>
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
</configuration>
|
60
src/Orchard.Web/Modules/Orchard.Indexing/Web.config
Normal file
60
src/Orchard.Web/Modules/Orchard.Indexing/Web.config
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0"?>
|
||||
<!--
|
||||
Note: As an alternative to hand editing this file you can use the
|
||||
web admin tool to configure settings for your application. Use
|
||||
the Website->Asp.Net Configuration option in Visual Studio.
|
||||
A full list of settings and comments can be found in
|
||||
machine.config.comments usually located in
|
||||
\Windows\Microsoft.Net\Framework\v2.x\Config
|
||||
-->
|
||||
<configuration>
|
||||
<appSettings/>
|
||||
<connectionStrings>
|
||||
<add name="ApplicationServices" connectionString="data source=.\SQLEXPRESS;Integrated Security=SSPI;AttachDBFilename=|DataDirectory|aspnetdb.mdf;User Instance=true" providerName="System.Data.SqlClient"/>
|
||||
</connectionStrings>
|
||||
<system.web>
|
||||
<!--
|
||||
Set compilation debug="true" to insert debugging
|
||||
symbols into the compiled page. Because this
|
||||
affects performance, set this value to true only
|
||||
during development.
|
||||
-->
|
||||
<compilation debug="false" targetFramework="4.0">
|
||||
<assemblies>
|
||||
<add assembly="System.Web.Mvc, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
|
||||
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
|
||||
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
|
||||
<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/></assemblies>
|
||||
</compilation>
|
||||
<pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID">
|
||||
<namespaces>
|
||||
<add namespace="System.Web.Mvc"/>
|
||||
<add namespace="System.Web.Mvc.Ajax"/>
|
||||
<add namespace="System.Web.Mvc.Html"/>
|
||||
<add namespace="System.Web.Routing"/>
|
||||
<add namespace="System.Linq"/>
|
||||
<add namespace="System.Collections.Generic"/>
|
||||
</namespaces>
|
||||
</pages>
|
||||
</system.web>
|
||||
<system.web.extensions/>
|
||||
<!--
|
||||
The system.webServer section is required for running ASP.NET AJAX under Internet
|
||||
Information Services 7.0. It is not necessary for previous version of IIS.
|
||||
-->
|
||||
<system.webServer>
|
||||
<modules runAllManagedModulesForAllRequests="true">
|
||||
</modules>
|
||||
<handlers>
|
||||
<remove name="UrlRoutingHandler"/>
|
||||
</handlers>
|
||||
</system.webServer>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35"/>
|
||||
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"/>
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
@@ -1,50 +0,0 @@
|
||||
using System.Web.Mvc;
|
||||
using Orchard.Localization;
|
||||
using Orchard.Search.Services;
|
||||
using Orchard.Search.ViewModels;
|
||||
using Orchard.UI.Notify;
|
||||
|
||||
namespace Orchard.Search.Controllers {
|
||||
public class AdminController : Controller {
|
||||
private readonly ISearchService _searchService;
|
||||
|
||||
public AdminController(ISearchService searchService, IOrchardServices services) {
|
||||
_searchService = searchService;
|
||||
Services = services;
|
||||
T = NullLocalizer.Instance;
|
||||
}
|
||||
|
||||
public IOrchardServices Services { get; private set; }
|
||||
public Localizer T { get; set; }
|
||||
|
||||
public ActionResult Index() {
|
||||
var viewModel = new SearchIndexViewModel {HasIndexToManage = _searchService.HasIndexToManage, IndexUpdatedUtc = _searchService.GetIndexUpdatedUtc()};
|
||||
|
||||
if (!viewModel.HasIndexToManage)
|
||||
Services.Notifier.Information(T("There is no search index to manage for this site."));
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult Update() {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index.")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
_searchService.UpdateIndex();
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public ActionResult Rebuild() {
|
||||
if (!Services.Authorizer.Authorize(Permissions.ManageSearchIndex, T("Not allowed to manage the search index.")))
|
||||
return new HttpUnauthorizedResult();
|
||||
|
||||
_searchService.RebuildIndex();
|
||||
_searchService.UpdateIndex();
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,7 +18,7 @@ namespace Orchard.Search.Controllers {
|
||||
Query = q,
|
||||
DefaultPageSize = 10, // <- yeah, I know :|
|
||||
PageOfResults = _searchService.Query(q, page, pageSize, searchHit => new SearchResultViewModel {
|
||||
Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.Id), "SummaryForSearch"),
|
||||
Content = _contentManager.BuildDisplayModel(_contentManager.Get(searchHit.ContentItemId), "SummaryForSearch"),
|
||||
SearchHit = searchHit
|
||||
})
|
||||
};
|
||||
|
@@ -65,15 +65,11 @@
|
||||
<Reference Include="System.EnterpriseServices" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AdminMenu.cs" />
|
||||
<Compile Include="Controllers\AdminController.cs" />
|
||||
<Compile Include="Controllers\SearchController.cs" />
|
||||
<Compile Include="Filters\SearchFilter.cs" />
|
||||
<Compile Include="Permissions.cs" />
|
||||
<Compile Include="Routes.cs" />
|
||||
<Compile Include="Services\ISearchService.cs" />
|
||||
<Compile Include="Services\SearchService.cs" />
|
||||
<Compile Include="ViewModels\SearchIndexViewModel.cs" />
|
||||
<Compile Include="ViewModels\SearchResultViewModel.cs" />
|
||||
<Compile Include="ViewModels\SearchViewModel.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
@@ -88,7 +84,6 @@
|
||||
<Content Include="Module.txt" />
|
||||
<Content Include="Styles\admin.css" />
|
||||
<Content Include="Styles\search.css" />
|
||||
<Content Include="Views\Admin\Index.ascx" />
|
||||
<Content Include="Views\SearchForm.ascx" />
|
||||
<Content Include="Views\Search\Index.ascx" />
|
||||
<Content Include="Views\Web.config" />
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Orchard.Collections;
|
||||
using Orchard.Indexing;
|
||||
@@ -39,9 +40,11 @@ namespace Orchard.Search.Services
|
||||
var searchBuilder = _indexManager.GetSearchIndexProvider().CreateSearchBuilder(SearchIndexName)
|
||||
.Parse(new [] {"title", "body"}, query);
|
||||
|
||||
if(HttpContext.Current != null) {
|
||||
searchBuilder.WithField("culture", _cultureManager.GetCurrentCulture(HttpContext.Current)).Mandatory();
|
||||
}
|
||||
var culture = _cultureManager.GetSiteCulture();
|
||||
|
||||
// use LCID as the text representation gets analyzed by the query parser
|
||||
searchBuilder
|
||||
.WithField("culture", CultureInfo.GetCultureInfo(culture).LCID);
|
||||
|
||||
var totalCount = searchBuilder.Count();
|
||||
if (pageSize != null)
|
||||
|
@@ -131,6 +131,10 @@
|
||||
<Project>{67C1D3AF-A0EC-46B2-BAE1-DF1DA8E0B890}</Project>
|
||||
<Name>Orchard.DevTools</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="Modules\Orchard.Indexing\Orchard.Indexing.csproj">
|
||||
<Project>{EA2B9121-EF54-40A6-A53E-6593C86EE696}</Project>
|
||||
<Name>Orchard.Indexing</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="Modules\Orchard.Media\Orchard.Media.csproj">
|
||||
<Project>{D9A7B330-CD22-4DA1-A95A-8DE1982AD8EB}</Project>
|
||||
<Name>Orchard.Media</Name>
|
||||
|
@@ -67,6 +67,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.MetaData", "Orchard
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Search", "Orchard.Web\Modules\Orchard.Search\Orchard.Search.csproj", "{4BE4EB01-AC56-4048-924E-2CA77F509ABA}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Indexing", "Orchard.Web\Modules\Orchard.Indexing\Orchard.Indexing.csproj", "{EA2B9121-EF54-40A6-A53E-6593C86EE696}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -189,6 +191,10 @@ Global
|
||||
{4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4BE4EB01-AC56-4048-924E-2CA77F509ABA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EA2B9121-EF54-40A6-A53E-6593C86EE696}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EA2B9121-EF54-40A6-A53E-6593C86EE696}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EA2B9121-EF54-40A6-A53E-6593C86EE696}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EA2B9121-EF54-40A6-A53E-6593C86EE696}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -210,6 +216,7 @@ Global
|
||||
{17F86780-9A1F-4AA1-86F1-875EEC2730C7} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
|
||||
{23E04990-2A8D-41B8-9908-6DDB71EA3B23} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
|
||||
{4BE4EB01-AC56-4048-924E-2CA77F509ABA} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
|
||||
{EA2B9121-EF54-40A6-A53E-6593C86EE696} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5}
|
||||
{ABC826D4-2FA1-4F2F-87DE-E6095F653810} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
|
||||
{F112851D-B023-4746-B6B1-8D2E5AD8F7AA} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
|
||||
{6CB3EB30-F725-45C0-9742-42599BA8E8D2} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA}
|
||||
|
@@ -7,6 +7,7 @@ using Orchard.ContentManagement.MetaData;
|
||||
using Orchard.ContentManagement.MetaData.Builders;
|
||||
using Orchard.ContentManagement.Records;
|
||||
using Orchard.Data;
|
||||
using Orchard.Indexing;
|
||||
using Orchard.Mvc.ViewModels;
|
||||
|
||||
namespace Orchard.ContentManagement {
|
||||
@@ -429,5 +430,18 @@ namespace Orchard.ContentManagement {
|
||||
}
|
||||
return contentTypeRecord;
|
||||
}
|
||||
|
||||
public void Index(ContentItem contentItem, IDocumentIndex documentIndex) {
|
||||
var indexContentContext = new IndexContentContext(contentItem, documentIndex);
|
||||
|
||||
// dispatch to handlers to retrieve index information
|
||||
foreach ( var handler in Handlers ) {
|
||||
handler.Indexing(indexContentContext);
|
||||
}
|
||||
|
||||
foreach ( var handler in Handlers ) {
|
||||
handler.Indexed(indexContentContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,13 @@
|
||||
using Orchard.Indexing;
|
||||
using Orchard.Indexing;
|
||||
|
||||
namespace Orchard.ContentManagement.Handlers {
|
||||
public class IndexContentContext {
|
||||
public ContentItem ContentItem { get; set; }
|
||||
public IIndexDocument IndexDocument { get; set; }
|
||||
public class IndexContentContext : ContentContextBase {
|
||||
|
||||
public IDocumentIndex DocumentIndex { get; private set; }
|
||||
|
||||
public IndexContentContext(ContentItem contentItem, IDocumentIndex documentIndex)
|
||||
: base(contentItem) {
|
||||
DocumentIndex = documentIndex;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using Orchard.Indexing;
|
||||
using Orchard.Mvc.ViewModels;
|
||||
|
||||
namespace Orchard.ContentManagement {
|
||||
@@ -17,6 +18,7 @@ namespace Orchard.ContentManagement {
|
||||
void Publish(ContentItem contentItem);
|
||||
void Unpublish(ContentItem contentItem);
|
||||
void Remove(ContentItem contentItem);
|
||||
void Index(ContentItem contentItem, IDocumentIndex documentIndex);
|
||||
|
||||
void Flush();
|
||||
IContentQuery<ContentItem> Query();
|
||||
|
@@ -3,26 +3,26 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Orchard.Indexing {
|
||||
|
||||
public interface IIndexDocument {
|
||||
public interface IDocumentIndex {
|
||||
|
||||
IIndexDocument SetContentItemId(int documentId);
|
||||
IDocumentIndex SetContentItemId(int documentId);
|
||||
|
||||
IIndexDocument Add(string name, string value);
|
||||
IIndexDocument Add(string name, string value, bool removeTags);
|
||||
IIndexDocument Add(string name, DateTime value);
|
||||
IIndexDocument Add(string name, int value);
|
||||
IIndexDocument Add(string name, bool value);
|
||||
IIndexDocument Add(string name, float value);
|
||||
IDocumentIndex Add(string name, string value);
|
||||
IDocumentIndex Add(string name, string value, bool removeTags);
|
||||
IDocumentIndex Add(string name, DateTime value);
|
||||
IDocumentIndex Add(string name, int value);
|
||||
IDocumentIndex Add(string name, bool value);
|
||||
IDocumentIndex Add(string name, float value);
|
||||
|
||||
/// <summary>
|
||||
/// Whether to store the original value to the index.
|
||||
/// Stores the original value to the index.
|
||||
/// </summary>
|
||||
IIndexDocument Store(bool store);
|
||||
IDocumentIndex Store();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the content should be tokenized or not. If not, value will be taken as a whole.
|
||||
/// Content is analyzed and tokenized.
|
||||
/// </summary>
|
||||
IIndexDocument Analyze(bool analyze);
|
||||
IDocumentIndex Analyze();
|
||||
|
||||
/// <summary>
|
||||
/// Whether some property have been added to this document, or otherwise if it's empty
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Orchard.ContentManagement;
|
||||
|
||||
namespace Orchard.Indexing {
|
||||
public interface IIndexProvider : IDependency {
|
||||
@@ -32,17 +33,17 @@ namespace Orchard.Indexing {
|
||||
/// Creates an empty document
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IIndexDocument New(int documentId);
|
||||
IDocumentIndex New(int documentId);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new document to the index
|
||||
/// </summary>
|
||||
void Store(string indexName, IIndexDocument indexDocument);
|
||||
void Store(string indexName, IDocumentIndex indexDocument);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a set of new document to the index
|
||||
/// </summary>
|
||||
void Store(string indexName, IEnumerable<IIndexDocument> indexDocuments);
|
||||
void Store(string indexName, IEnumerable<IDocumentIndex> indexDocuments);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an existing document from the index
|
||||
|
@@ -3,16 +3,24 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Orchard.Indexing {
|
||||
public interface ISearchBuilder {
|
||||
ISearchBuilder Parse(string defaultField, string query);
|
||||
ISearchBuilder Parse(string[] defaultFields, string query);
|
||||
|
||||
ISearchBuilder WithField(string field, bool value);
|
||||
ISearchBuilder WithField(string field, DateTime value);
|
||||
ISearchBuilder WithField(string field, string value);
|
||||
ISearchBuilder WithField(string field, int value);
|
||||
ISearchBuilder WithField(string field, float value);
|
||||
ISearchBuilder WithinRange(string field, int min, int max);
|
||||
ISearchBuilder WithinRange(string field, float min, float max);
|
||||
ISearchBuilder WithinRange(string field, DateTime min, DateTime max);
|
||||
ISearchBuilder WithinRange(string field, string min, string max);
|
||||
|
||||
ISearchBuilder Mandatory();
|
||||
ISearchBuilder Forbidden();
|
||||
ISearchBuilder ExactMatch();
|
||||
ISearchBuilder Weighted(float weight);
|
||||
|
||||
ISearchBuilder After(string name, DateTime date);
|
||||
ISearchBuilder Before(string name, DateTime date);
|
||||
ISearchBuilder SortBy(string name);
|
||||
ISearchBuilder Ascending();
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
namespace Orchard.Indexing {
|
||||
public interface ISearchHit {
|
||||
int Id { get; }
|
||||
int ContentItemId { get; }
|
||||
float Score { get; }
|
||||
|
||||
int GetInt(string name);
|
||||
|
@@ -227,6 +227,7 @@
|
||||
<Compile Include="ContentManagement\Handlers\ContentItemTemplates.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Include="ContentManagement\Handlers\IndexContentContext.cs" />
|
||||
<Compile Include="ContentManagement\Handlers\CreateContentContext.cs">
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
@@ -413,7 +414,6 @@
|
||||
<Compile Include="Indexing\ISearchHit.cs" />
|
||||
<Compile Include="Tasks\Indexing\IIndexingTask.cs" />
|
||||
<Compile Include="Tasks\Indexing\IIndexingTaskManager.cs" />
|
||||
<Compile Include="ContentManagement\Handlers\IndexContentContext.cs" />
|
||||
<Compile Include="Validation\Argument.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
Reference in New Issue
Block a user