using System; using System.Collections.Generic; using System.IO; using System.Linq; using Lucene.Net.Analysis; using Lucene.Net.Analysis.Standard; using Lucene.Net.Documents; 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.Logging; using System.Xml.Linq; namespace Orchard.Core.Indexing.Lucene { /// /// Represents the default implementation of an IIndexProvider, based on Lucene /// public class DefaultIndexProvider : IIndexProvider { private readonly IAppDataFolder _appDataFolder; private readonly ShellSettings _shellSettings; public static readonly Version LuceneVersion = Version.LUCENE_29; private readonly Analyzer _analyzer = new StandardAnalyzer(LuceneVersion); private readonly string _basePath; public static readonly DateTime DefaultMinDateTime = new DateTime(1980, 1, 1); public static readonly string Settings = "Settings"; public static readonly string LastIndexUtc = "LastIndexedUtc"; public ILogger Logger { get; set; } public DefaultIndexProvider(IAppDataFolder appDataFolder, ShellSettings shellSettings) { _appDataFolder = appDataFolder; _shellSettings = shellSettings; // TODO: (sebros) Find a common way to get where tenant's specific files should go. "Sites/Tenant" is hard coded in multiple places _basePath = Path.Combine("Sites", _shellSettings.Name, "Indexes"); Logger = NullLogger.Instance; // Ensures the directory exists EnsureDirectoryExists(); } private void EnsureDirectoryExists() { var directory = new DirectoryInfo(_appDataFolder.MapPath(_basePath)); if(!directory.Exists) { directory.Create(); } } protected virtual Directory GetDirectory(string indexName) { var directoryInfo = new DirectoryInfo(_appDataFolder.MapPath(Path.Combine(_basePath, indexName))); return FSDirectory.Open(directoryInfo); } private static Document CreateDocument(DefaultIndexDocument indexDocument) { var doc = new Document(); indexDocument.PrepareForIndexing(); foreach(var field in indexDocument.Fields) { doc.Add(field); } return doc; } public bool Exists(string indexName) { 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 int NumDocs(string indexName) { if ( !Exists(indexName) ) { return 0; } var reader = IndexReader.Open(GetDirectory(indexName), true); try { return reader.NumDocs(); } finally { reader.Close(); } } public void CreateIndex(string indexName) { var writer = new IndexWriter(GetDirectory(indexName), _analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED); writer.Close(); Logger.Information("Index [{0}] created", indexName); } public void DeleteIndex(string indexName) { new DirectoryInfo(Path.Combine(_appDataFolder.MapPath(Path.Combine(_basePath, indexName)))) .Delete(true); var settingsFileName = GetSettingsFileName(indexName); if(File.Exists(settingsFileName)) { File.Delete(settingsFileName); } } public void Store(string indexName, IIndexDocument indexDocument) { Store(indexName, new [] { (DefaultIndexDocument)indexDocument }); } public void Store(string indexName, IEnumerable indexDocuments) { Store(indexName, indexDocuments.Cast()); } public void Store(string indexName, IEnumerable indexDocuments) { if(indexDocuments.AsQueryable().Count() == 0) { return; } var writer = new IndexWriter(GetDirectory(indexName), _analyzer, false, IndexWriter.MaxFieldLength.UNLIMITED); DefaultIndexDocument current = null; try { foreach ( var indexDocument in indexDocuments ) { current = indexDocument; var doc = CreateDocument(indexDocument); writer.AddDocument(doc); Logger.Debug("Document [{0}] indexed", indexDocument.Id); } } catch ( Exception ex ) { Logger.Error(ex, "An unexpected error occured while add the document [{0}] from the index [{1}].", current.Id, indexName); } finally { writer.Optimize(); writer.Close(); } } public void Delete(string indexName, int documentId) { Delete(indexName, new[] { documentId }); } public void Delete(string indexName, IEnumerable documentIds) { if ( documentIds.AsQueryable().Count() == 0 ) { return; } var reader = IndexReader.Open(GetDirectory(indexName), false); try { foreach (var id in documentIds) { try { var term = new Term("id", id.ToString()); if (reader.DeleteDocuments(term) != 0) { Logger.Error("The document [{0}] could not be removed from the index [{1}]", id, indexName); } else { Logger.Debug("Document [{0}] removed from index", id); } } catch (Exception ex) { Logger.Error(ex, "An unexpected error occured while removing the document [{0}] from the index [{1}].", id, indexName); } } } finally { reader.Close(); } } public IIndexDocument New(int documentId) { return new DefaultIndexDocument(documentId); } public ISearchBuilder CreateSearchBuilder(string indexName) { return new DefaultSearchBuilder(GetDirectory(indexName)); } private string GetSettingsFileName(string indexName) { return Path.Combine(_appDataFolder.MapPath(_basePath), indexName + ".settings.xml"); } public DateTime GetLastIndexUtc(string indexName) { var settingsFileName = GetSettingsFileName(indexName); return File.Exists(settingsFileName) ? DateTime.Parse(XDocument.Load(settingsFileName).Descendants(LastIndexUtc).First().Value) : DefaultMinDateTime; } public void SetLastIndexUtc(string indexName, DateTime lastIndexUtc) { if ( lastIndexUtc < DefaultMinDateTime ) { lastIndexUtc = DefaultMinDateTime; } XDocument doc; var settingsFileName = GetSettingsFileName(indexName); if ( !File.Exists(settingsFileName) ) { EnsureDirectoryExists(); doc = new XDocument( new XElement(Settings, new XElement(LastIndexUtc, lastIndexUtc.ToString("s")))); } else { doc = XDocument.Load(settingsFileName); doc.Element(Settings).Element(LastIndexUtc).Value = lastIndexUtc.ToString("s"); } doc.Save(settingsFileName); } } }