├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ ├── docfx-gh-pages.yml │ └── test-report.yml ├── .gitignore ├── .nuget └── Nuget.config ├── GitVersion.yml.bak ├── README.md ├── assets ├── ExamineLogo.pdn ├── ExamineLogo.png ├── License free.txt ├── License premium.txt ├── logo-round-small.png ├── logo-round.png ├── logo-round.xcf ├── logo.ai ├── logo.eps ├── logo.png ├── logo.svg ├── logo_transparent_tiny.png ├── twitter-card.png └── twitter-card.xcf ├── build └── Build-Release.ps1 ├── docs ├── .gitignore ├── 404.html ├── 404.md ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── _includes │ ├── site-header.html │ └── social-metatags.html ├── _layouts │ ├── default.html │ ├── home.html │ ├── page.html │ └── post.html ├── _sass │ ├── blog.scss │ ├── blog │ │ ├── _base.scss │ │ └── _layout.scss │ ├── jekyll-theme-cayman-blog.scss │ ├── normalize.scss │ ├── rouge-github.scss │ └── variables.scss ├── android-chrome-192x192.png ├── android-chrome-384x384.png ├── apple-touch-icon.png ├── assets │ └── css │ │ └── style.scss ├── browserconfig.xml ├── configuration.md ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── index.md ├── indexing.md ├── mstile-150x150.png ├── safari-pinned-tab.svg ├── searching.md ├── site.webmanifest ├── sorting.md └── v2 │ ├── .gitignore │ ├── 404.html │ ├── 404.md │ ├── android-chrome-192x192.png │ ├── android-chrome-384x384.png │ ├── api │ ├── .gitignore │ └── index.md │ ├── articles │ ├── configuration.md │ ├── indexing.md │ ├── searching.md │ ├── sorting.md │ └── toc.yml │ ├── docfx.json │ ├── docs-v1-v2 │ ├── configuration.md │ ├── indexing.md │ ├── quickstart.md │ ├── searching.md │ ├── sorting.md │ └── toc.yml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── images │ ├── favicon.ico │ └── headerlogo.png │ ├── index.md │ ├── mstile-150x150.png │ ├── safari-pinned-tab.svg │ ├── site.webmanifest │ ├── templates │ └── material │ │ ├── partials │ │ └── head.tmpl.partial │ │ └── styles │ │ └── main.css │ └── toc.yml ├── nuget.config └── src ├── .editorconfig ├── Directory.Build.props ├── Examine.Benchmarks ├── ConcurrentAcquireBenchmarks.cs ├── ConcurrentSearchBenchmarks.cs ├── Examine.Benchmarks.csproj ├── ExamineBaseTest.cs ├── IndexVersionComparison.cs ├── InitTools.cs ├── NugetConfig.cs ├── Program.cs ├── SearchVersionComparison.cs └── TestIndex.cs ├── Examine.Core ├── BaseIndexProvider.cs ├── BaseSearchProvider.cs ├── DisposableObjectSlim.cs ├── EmptySearchResults.cs ├── Examine.Core.csproj ├── ExamineExtensions.cs ├── ExamineFieldNames.cs ├── ExamineManager.cs ├── FieldDefinition.cs ├── FieldDefinitionCollection.cs ├── FieldDefinitionTypes.cs ├── IExamineManager.cs ├── IIndex.cs ├── IIndexStats.cs ├── ISearchResult.cs ├── ISearchResults.cs ├── ISearcher.cs ├── IValueSetValidator.cs ├── IndexOperation.cs ├── IndexOperationEventArgs.cs ├── IndexOperationType.cs ├── IndexOptions.cs ├── IndexingErrorEventArgs.cs ├── IndexingItemEventArgs.cs ├── ObjectExtensions.cs ├── OrderedDictionary.cs ├── PublicAPI.Shipped.txt ├── PublicAPI.Unshipped.txt ├── ReadOnlyFieldDefinitionCollection.cs ├── Search │ ├── BooleanOperation.cs │ ├── ExamineValue.cs │ ├── Examineness.cs │ ├── IBooleanOperation.cs │ ├── IExamineValue.cs │ ├── INestedBooleanOperation.cs │ ├── INestedQuery.cs │ ├── IOrdering.cs │ ├── IQuery.cs │ ├── IQueryExecutor.cs │ ├── QueryOptions.cs │ ├── SortType.cs │ └── SortableField.cs ├── SearchExtensions.cs ├── SearchResult.cs ├── ValueSet.cs ├── ValueSetValidationResult.cs └── ValueSetValidationStatus.cs ├── Examine.Host ├── AspNetCoreApplicationIdentifier.cs ├── CurrentEnvironmentApplicationRoot.cs ├── Examine.csproj ├── IApplicationRoot.cs ├── PublicAPI.Shipped.txt ├── PublicAPI.Unshipped.txt └── ServicesCollectionExtensions.cs ├── Examine.Lucene ├── Analyzers │ ├── CultureInvariantStandardAnalyzer.cs │ ├── CultureInvariantWhitespaceAnalyzer.cs │ ├── EmailAddressAnalyzer.cs │ └── PatternAnalyzer.cs ├── App.config ├── AspExamineManager.cs.bak ├── DelegateFieldValueTypeFactory.cs ├── Directories │ ├── DefaultLockFactory.cs │ ├── DirectoryFactory.cs │ ├── DirectoryFactoryBase.cs │ ├── FakeLuceneDirectoryIndexOptionsOptionsMonitor.cs │ ├── FileSystemDirectoryFactory.cs │ ├── IApplicationIdentifier.cs │ ├── IDirectoryFactory.cs │ ├── ILockFactory.cs │ ├── MultiIndexLock.cs │ ├── MultiIndexLockFactory.cs │ ├── SyncedFileSystemDirectory.cs │ ├── SyncedFileSystemDirectoryFactory.cs │ └── TempEnvFileSystemDirectoryFactory.cs ├── DocumentWritingEventArgs.cs ├── Examine.Lucene.csproj ├── Examine.csproj.vspscc ├── ExamineReplicator.cs ├── FieldValueTypeCollection.cs ├── IFieldValueTypeFactory.cs ├── Indexing │ ├── DateTimeType.cs │ ├── DoubleType.cs │ ├── FullTextType.cs │ ├── GenericAnalyzerFieldValueType.cs │ ├── IIndexFieldValueType.cs │ ├── IIndexRangeValueType.cs │ ├── IndexFieldRangeValueType.cs │ ├── IndexFieldValueTypeBase.cs │ ├── Int32Type.cs │ ├── Int64Type.cs │ ├── RawStringType.cs │ └── SingleType.cs ├── LoggingInfoStream.cs ├── LoggingReplicationClient.cs ├── LuceneDirectoryIndexOptions.cs ├── LuceneExtensions.cs ├── LuceneIndexOptions.cs ├── LuceneInfo.cs ├── Providers │ ├── BaseLuceneSearcher.cs │ ├── ErrorCheckingScoringBooleanQueryRewrite.cs │ ├── ErrorLoggingConcurrentMergeScheduler.cs │ ├── IndexThreadingMode.cs │ ├── LuceneIndex.cs │ ├── LuceneSearcher.cs │ ├── MultiIndexSearcher.cs │ └── ValueSetValidatorDelegate.cs ├── PublicAPI.Shipped.txt ├── PublicAPI.Unshipped.txt ├── ReaderStatus.cs ├── Search │ ├── CustomMultiFieldQueryParser.cs │ ├── ExamineMultiFieldQueryParser.cs │ ├── ILuceneSearchResults.cs │ ├── ISearchContext.cs │ ├── ISearcherReference.cs │ ├── LateBoundQuery.cs │ ├── LuceneBooleanOperation.cs │ ├── LuceneBooleanOperationBase.cs │ ├── LuceneQuery.cs │ ├── LuceneQueryOptions.cs │ ├── LuceneSearchExecutor.cs │ ├── LuceneSearchExtensions.cs │ ├── LuceneSearchOptions.cs │ ├── LuceneSearchQuery.cs │ ├── LuceneSearchQueryBase.cs │ ├── LuceneSearchResult.cs │ ├── LuceneSearchResults.cs │ ├── MultiSearchContext.cs │ ├── MultiSearchSearcherReference.cs │ ├── SearchAfterOptions.cs │ ├── SearchContext.cs │ └── SearcherReference.cs ├── StringExtensions.cs └── ValueTypeFactoryCollection.cs ├── Examine.Test ├── App_Data │ ├── PDFStandards.PDF │ ├── StringTheory.pdf │ ├── TemplateIndex │ │ ├── _0.cfs │ │ ├── segments.gen │ │ └── segments_2 │ ├── UmbracoContour.pdf │ ├── VS2010CSharp.pdf │ ├── media.xml │ └── umbraco.config ├── Examine.Core │ └── Options │ │ └── ConfigureOptionsTests.cs ├── Examine.Lucene │ ├── Analyzers │ │ ├── PatternAnalyzerTests.cs │ │ └── TokenStreamExtensions.cs │ ├── Directories │ │ └── SyncedFileSystemDirectoryFactoryTests.cs │ ├── ExamineReplicatorTests.cs │ ├── Extensions │ │ └── SpatialSearch.cs │ ├── Index │ │ ├── AnalyzerTests.cs │ │ └── LuceneIndexTests.cs │ └── Search │ │ ├── AnalyzerTests.cs │ │ ├── ConcurrentSearchBenchmarks.cs │ │ ├── FluentApiTests.cs │ │ ├── LuceneSearchResultsReaderTrackerTests.cs │ │ ├── MultiIndexSearch.cs │ │ ├── MultiIndexSearchTests.cs │ │ └── StringTests.cs ├── Examine.Test.csproj ├── Examine.Test.csproj.vspscc ├── ExamineBaseTest.cs ├── ExamineExtensions.cs ├── IndexInitializer.cs ├── IndexTypes.cs ├── OrderedDictionaryTests.cs ├── RandomIdRAMDirectory.cs ├── SimpleDataProviderTest.cs ├── TestContentService.cs └── TestIndex.cs ├── Examine.Web.Demo ├── App.razor ├── ConfigureIndexOptions.cs ├── Data │ ├── BogusDataService.cs │ ├── IndexService.cs │ └── Models │ │ └── IndexInformation.cs ├── Examine.Web.Demo.csproj ├── IndexFactoryExtensions.cs ├── Pages │ ├── Error.cshtml │ ├── Error.cshtml.cs │ ├── Index.razor │ ├── Indexes.razor │ ├── Search.razor │ ├── _Host.cshtml │ └── _Layout.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Shared │ ├── Components │ │ └── IndexData.razor │ ├── MainLayout.razor │ ├── MainLayout.razor.css │ ├── NavMenu.razor │ └── NavMenu.razor.css ├── _Imports.razor ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ ├── css │ ├── bootstrap │ │ ├── bootstrap-grid.css │ │ ├── bootstrap-grid.css.map │ │ ├── bootstrap-grid.min.css │ │ ├── bootstrap-grid.min.css.map │ │ ├── bootstrap-grid.rtl.css │ │ ├── bootstrap-grid.rtl.css.map │ │ ├── bootstrap-grid.rtl.min.css │ │ ├── bootstrap-grid.rtl.min.css.map │ │ ├── bootstrap-icons.css │ │ ├── bootstrap-icons.json │ │ ├── bootstrap-icons.scss │ │ ├── bootstrap-reboot.css │ │ ├── bootstrap-reboot.css.map │ │ ├── bootstrap-reboot.min.css │ │ ├── bootstrap-reboot.min.css.map │ │ ├── bootstrap-reboot.rtl.css │ │ ├── bootstrap-reboot.rtl.css.map │ │ ├── bootstrap-reboot.rtl.min.css │ │ ├── bootstrap-reboot.rtl.min.css.map │ │ ├── bootstrap-utilities.css │ │ ├── bootstrap-utilities.css.map │ │ ├── bootstrap-utilities.min.css │ │ ├── bootstrap-utilities.min.css.map │ │ ├── bootstrap-utilities.rtl.css │ │ ├── bootstrap-utilities.rtl.css.map │ │ ├── bootstrap-utilities.rtl.min.css │ │ ├── bootstrap-utilities.rtl.min.css.map │ │ ├── bootstrap.css │ │ ├── bootstrap.css.map │ │ ├── bootstrap.min.css │ │ ├── bootstrap.min.css.map │ │ ├── bootstrap.rtl.css │ │ ├── bootstrap.rtl.css.map │ │ ├── bootstrap.rtl.min.css │ │ ├── bootstrap.rtl.min.css.map │ │ ├── fonts │ │ │ ├── bootstrap-icons.woff │ │ │ └── bootstrap-icons.woff2 │ │ └── index.html │ └── site.css │ └── favicon.ico └── Examine.sln /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [shazwazza] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/docfx-gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: Deploy DocFX with GitHub Pages dependencies preinstalled 2 | 3 | on: 4 | # Runs on pushes targeting the default branch 5 | push: 6 | branches: ["feature/docfx"] 7 | pull_request: 8 | branches: 9 | - 'feature/docfx' 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 15 | permissions: 16 | contents: read 17 | pages: write 18 | id-token: write 19 | 20 | # Allow one concurrent deployment 21 | concurrency: 22 | group: "pages" 23 | cancel-in-progress: true 24 | 25 | jobs: 26 | # Build job 27 | build: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v3 32 | - name: Setup Github Pages 33 | uses: actions/configure-pages@v3 34 | 35 | - name: Build DocFX Site 36 | uses: VakuWare/docfx-pdf-action@v1.4.0 37 | with: 38 | args: docs/v2/docfx.json 39 | - name: Upload docfx built site artifact 40 | uses: actions/upload-pages-artifact@v1 41 | with: 42 | path: docs/v2/_site 43 | 44 | # Deployment job 45 | deploy: 46 | environment: 47 | name: github-pages 48 | url: ${{ steps.deployment.outputs.page_url }} 49 | runs-on: ubuntu-latest 50 | needs: build 51 | steps: 52 | - name: Deploy to GitHub Pages 53 | id: deployment 54 | uses: actions/deploy-pages@v1 55 | -------------------------------------------------------------------------------- /.github/workflows/test-report.yml: -------------------------------------------------------------------------------- 1 | name: 'Test Report' 2 | on: 3 | workflow_run: 4 | workflows: ['Examine Build'] # runs after CI workflow 5 | types: 6 | - completed 7 | jobs: 8 | report: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: dorny/test-reporter@v1 12 | with: 13 | artifact: examine-test-results # artifact name 14 | name: Publish Tests # Name of the check run which will be created 15 | path: '*.trx' # Path to test results (inside artifact .zip) 16 | reporter: dotnet-trx # Format of test results -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.add 2 | _ReSharper* 3 | *.suo 4 | [Dd]ebug/ 5 | [Rr]elease/ 6 | x64/ 7 | build/ 8 | [Bb]in/ 9 | [Oo]bj/ 10 | *ReSharper.user 11 | TestResults/* 12 | Examine.Web.Demo/App_Data/* 13 | *.user 14 | build/Releases/* 15 | packages/* 16 | Projects/Examine.Web.Demo/App_Data/*.sdf 17 | Projects/Examine.Web.Demo/App_Data/SimpleIndexSet2/Index/* 18 | NDepend/* 19 | *.orig 20 | .Examine.boltdata/* 21 | src/Examine.Web.Demo/App_Data/SimpleIndexSet2/* 22 | src/Examine.Web.Demo/App_Data/Database1.sdf 23 | src/Examine.vsmdi 24 | src/Local.testsettings 25 | src/packages/* 26 | /src/.vs/* 27 | /src/Examine.Web.Demo/_bin_deployableAssemblies/* 28 | /src/Examine.Web.Demo/App_Data/* 29 | .idea/ 30 | /src/Examine.Web.Demo/Examine/* 31 | -------------------------------------------------------------------------------- /.nuget/Nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /GitVersion.yml.bak: -------------------------------------------------------------------------------- 1 | next-version: 1.0 2 | assembly-versioning-scheme: MajorMinorPatch 3 | assembly-file-versioning-scheme: MajorMinorPatchTag 4 | assembly-informational-format: '{InformationalVersion}' 5 | mode: ContinuousDelivery 6 | increment: Inherit 7 | continuous-delivery-fallback-tag: ci 8 | tag-prefix: '[vV]|release-[vV]' 9 | major-version-bump-message: '\+semver:\s?(breaking|major)' 10 | minor-version-bump-message: '\+semver:\s?(feature|minor)' 11 | patch-version-bump-message: '\+semver:\s?(fix|patch)' 12 | no-bump-message: '\+semver:\s?(none|skip)' 13 | legacy-semver-padding: 4 14 | build-metadata-padding: 4 15 | commits-since-version-source-padding: 4 16 | commit-message-incrementing: Enabled 17 | commit-date-format: 'yyyy-MM-dd' 18 | ignore: 19 | sha: [] 20 | commits-before: 2021-01-01T00:00:00 21 | merge-message-formats: {} -------------------------------------------------------------------------------- /assets/ExamineLogo.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/ExamineLogo.pdn -------------------------------------------------------------------------------- /assets/ExamineLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/ExamineLogo.png -------------------------------------------------------------------------------- /assets/License free.txt: -------------------------------------------------------------------------------- 1 | IMPORTANT NOTICE: This license only applies if you downloaded this content as 2 | an unsubscribed user. If you are a premium user (ie, you pay a subscription) 3 | you are bound to the license terms described in the accompanying file 4 | "License premium.txt". 5 | 6 | --------------------- 7 | 8 | You must attribute the image to its author: 9 | 10 | In order to use a content or a part of it, you must attribute it to pikisuperstar / Freepik, 11 | so we will be able to continue creating new graphic resources every day. 12 | 13 | 14 | How to attribute it? 15 | 16 | For websites: 17 | 18 | Please, copy this code on your website to accredit the author: 19 | Designed by pikisuperstar / Freepik 20 | 21 | For printing: 22 | 23 | Paste this text on the final work so the authorship is known. 24 | - For example, in the acknowledgements chapter of a book: 25 | "Designed by pikisuperstar / Freepik" 26 | 27 | 28 | You are free to use this image: 29 | 30 | - For both personal and commercial projects and to modify it. 31 | - In a website or presentation template or application or as part of your design. 32 | 33 | You are not allowed to: 34 | 35 | - Sub-license, resell or rent it. 36 | - Include it in any online or offline archive or database. 37 | 38 | The full terms of the license are described in section 7 of the Freepik 39 | terms of use, available online in the following link: 40 | 41 | http://www.freepik.com/terms_of_use 42 | 43 | The terms described in the above link have precedence over the terms described 44 | in the present document. In case of disagreement, the Freepik Terms of Use 45 | will prevail. 46 | -------------------------------------------------------------------------------- /assets/License premium.txt: -------------------------------------------------------------------------------- 1 | IMPORTANT NOTICE: This license only applies if you downloaded this content as 2 | a subscribed (or "premium") user. If you are an unsubscribed user (or "free" 3 | user) you are bound to the license terms described in the accompanying file 4 | "License free.txt". 5 | 6 | --------------------- 7 | 8 | You can download from your profile in Freepik a personalized license stating 9 | your right to use this content as a "premium" user: 10 | 11 | https://profile.freepik.com/my_downloads 12 | 13 | You are free to use this image: 14 | 15 | - For both personal and commercial projects and to modify it. 16 | - In a website or presentation template or application or as part of your design. 17 | 18 | You are not allowed to: 19 | 20 | - Sub-license, resell or rent it. 21 | - Include it in any online or offline archive or database. 22 | 23 | The full terms of the license are described in sections 7 and 8 of the Freepik 24 | terms of use, available online in the following link: 25 | 26 | http://www.freepik.com/terms_of_use 27 | 28 | The terms described in the above link have precedence over the terms described 29 | in the present document. In case of disagreement, the Freepik Terms of Use 30 | will prevail. 31 | -------------------------------------------------------------------------------- /assets/logo-round-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/logo-round-small.png -------------------------------------------------------------------------------- /assets/logo-round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/logo-round.png -------------------------------------------------------------------------------- /assets/logo-round.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/logo-round.xcf -------------------------------------------------------------------------------- /assets/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/logo.ai -------------------------------------------------------------------------------- /assets/logo.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/logo.eps -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/logo.png -------------------------------------------------------------------------------- /assets/logo_transparent_tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/logo_transparent_tiny.png -------------------------------------------------------------------------------- /assets/twitter-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/twitter-card.png -------------------------------------------------------------------------------- /assets/twitter-card.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/assets/twitter-card.xcf -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-cache 4 | .jekyll-metadata 5 | vendor 6 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: 404 Error 4 | tagline: Ah snap! We could not find what you are looking for! 5 | permalink: /404.html 6 | --- 7 | 8 | Try got get back to the Home Page and start over! 9 | 10 | [Go to the Home Page]({{ '/' | absolute_url }}) 11 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | # gem "jekyll", "~> 4.0.0" 11 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 12 | gem "minima", "~> 2.5" 13 | gem "jekyll-theme-cayman-blog" 14 | # gem "jekyll-theme-minimal" 15 | # gem "jekyll-theme-cayman" 16 | 17 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 18 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 19 | # gem "github-pages", "~> 201", group: :jekyll_plugins 20 | # gem 'github-pages', '>=104', group: :jekyll_plugins 21 | # gem "github-pages", group: :jekyll_plugins 22 | 23 | # If you have any plugins, put them here! 24 | group :jekyll_plugins do 25 | gem "jekyll-feed", "~> 0.11" 26 | gem "jekyll-readme-index", "~> 0.2.0" 27 | end 28 | 29 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem 30 | # and associated library. 31 | install_if -> { RUBY_PLATFORM =~ %r!mingw|mswin|java! } do 32 | gem "tzinfo", "~> 1.2" 33 | gem "tzinfo-data" 34 | end 35 | 36 | # Performance-booster for watching directories on Windows 37 | gem "wdm", "~> 0.1.1", :install_if => Gem.win_platform? 38 | 39 | gem "kramdown-parser-gfm" 40 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | # 11 | # If you need help with YAML syntax, here are some quick references for you: 12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml 13 | # https://learnxinyminutes.com/docs/yaml/ 14 | # 15 | # Site settings 16 | # These are used to personalize your new site. If you look in the HTML files, 17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 18 | # You can create any custom variable you would like, and they will be accessible 19 | # in the templates via {{ site.myvariable }}. 20 | 21 | title: Examine 22 | # email: your-email@example.com 23 | description: >- # this means to ignore newlines until "baseurl:" 24 | A .NET indexing and search engine powered by Lucene.Net 25 | baseurl: "/Examine" # the subpath of your site, e.g. /blog 26 | url: "https://shazwazza.github.io" # the base hostname & protocol for your site, e.g. http://example.com 27 | twitter_username: shazwazza 28 | github_username: shazwazza 29 | 30 | image: https://github.com/Shazwazza/Examine/raw/master/assets/twitter-card.png?raw=true 31 | 32 | twitter: 33 | username: shazwazza 34 | 35 | github: 36 | owner_url: https://github.com/Shazwazza/Examine 37 | owner_name: shazwazza 38 | 39 | # Build settings 40 | # theme: minima 41 | # theme: jekyll-theme-cayman 42 | theme: 43 | 44 | plugins: 45 | - jekyll-feed 46 | - jekyll-readme-index 47 | 48 | # Exclude from processing. 49 | # The following items will not be processed, by default. 50 | # Any item listed under the `exclude:` key here will be automatically added to 51 | # the internal "default list". 52 | # 53 | # Excluded items can be processed by explicitly listing the directories or 54 | # their entries' file path in the `include:` list. 55 | # 56 | # exclude: 57 | # - .sass-cache/ 58 | # - .jekyll-cache/ 59 | # - gemfiles/ 60 | # - Gemfile 61 | # - Gemfile.lock 62 | # - node_modules/ 63 | # - vendor/bundle/ 64 | # - vendor/cache/ 65 | # - vendor/gems/ 66 | # - vendor/ruby/ 67 | 68 | header_page_refs: ['configuration', 'indexing', 'searching', 'sorting'] -------------------------------------------------------------------------------- /docs/_includes/site-header.html: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /docs/_layouts/home.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |
6 | 7 | {{ content }} 8 | 9 |

Latest Posts

10 | 11 |
 
12 | 13 | 29 | 30 |
31 | -------------------------------------------------------------------------------- /docs/_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | 6 |
7 | {{ content }} 8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /docs/_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | 6 | 17 | 18 |
19 | {{ content }} 20 |
21 | 22 | {% if site.disqus.shortname %} 23 | {% include disqus_comments.html %} 24 | {% endif %} 25 |
26 | -------------------------------------------------------------------------------- /docs/_sass/blog.scss: -------------------------------------------------------------------------------- 1 | @charset "utf-8"; 2 | 3 | // Define defaults for each variable. 4 | 5 | $base-font-size: 16px !default; 6 | $base-font-weight: 400 !default; 7 | $small-font-size: $base-font-size * 0.875 !default; 8 | $base-line-height: 1.5 !default; 9 | 10 | $spacing-unit: 30px !default; 11 | 12 | $text-color: $section-headings-color !default; 13 | $background-color: #fdfdfd !default; 14 | $brand-color: #2a7ae2 !default; 15 | 16 | $grey-color: #828282 !default; 17 | $grey-color-light: lighten($grey-color, 40%) !default; 18 | $grey-color-dark: darken($grey-color, 25%) !default; 19 | 20 | // Width of the content area 21 | //$content-width: 800px !default; 22 | $navbar-width: 54em !default; 23 | 24 | $on-palm: 38em !default; 25 | $on-laptop: 54em !default; 26 | 27 | // Use media queries like this: 28 | // @include media-query($on-palm) { 29 | // .wrapper { 30 | // padding-right: $spacing-unit / 2; 31 | // padding-left: $spacing-unit / 2; 32 | // } 33 | // } 34 | @mixin media-query($device) { 35 | @media screen and (max-width: $device) { 36 | @content; 37 | } 38 | } 39 | 40 | @mixin relative-font-size($ratio) { 41 | font-size: $base-font-size * $ratio; 42 | } 43 | 44 | // Import partials. 45 | @import 46 | "blog/base", 47 | "blog/layout" 48 | ; 49 | -------------------------------------------------------------------------------- /docs/_sass/blog/_base.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper 3 | */ 4 | .wrapper { 5 | max-width: -webkit-calc(#{$navbar-width} - (#{$spacing-unit} * 2)); 6 | max-width: calc(#{$navbar-width} - (#{$spacing-unit} * 2)); 7 | margin-right: auto; 8 | margin-left: auto; 9 | padding-right: $spacing-unit; 10 | padding-left: $spacing-unit; 11 | @extend %clearfix; 12 | 13 | @include media-query($on-laptop) { 14 | max-width: -webkit-calc(#{$navbar-width} - (#{$spacing-unit})); 15 | max-width: calc(#{$navbar-width} - (#{$spacing-unit})); 16 | padding-right: $spacing-unit / 2; 17 | padding-left: $spacing-unit / 2; 18 | } 19 | } 20 | 21 | 22 | 23 | /** 24 | * Clearfix 25 | */ 26 | %clearfix:after { 27 | content: ""; 28 | display: table; 29 | clear: both; 30 | } 31 | -------------------------------------------------------------------------------- /docs/_sass/variables.scss: -------------------------------------------------------------------------------- 1 | // Breakpoints 2 | $large-breakpoint: 64em !default; 3 | $medium-breakpoint: 42em !default; 4 | 5 | // Headers 6 | $header-heading-color: #fff !default; 7 | $header-bg-color: #159957 !default; 8 | $header-bg-color-secondary: #155799 !default; 9 | 10 | // Text 11 | $section-headings-color: #159957 !default; 12 | $body-text-color: #606c71 !default; 13 | $body-link-color: #1e6bb8 !default; 14 | $blockquote-text-color: #819198 !default; 15 | 16 | // Code 17 | $code-bg-color: #f3f6fa !default; 18 | $code-text-color: #567482 !default; 19 | 20 | // Borders 21 | $border-color: #dce6f0 !default; 22 | $table-border-color: #e9ebec !default; 23 | $hr-border-color: #eff0f1 !default; 24 | -------------------------------------------------------------------------------- /docs/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/android-chrome-384x384.png -------------------------------------------------------------------------------- /docs/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/assets/css/style.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | @import 'jekyll-theme-cayman-blog'; 5 | 6 | /* CSS Tabs https://www.cssscript.com/css-only-tabs-ui/ */ 7 | 8 | .container > input[type="radio"] { 9 | position: absolute; 10 | left: -200vw; 11 | } 12 | 13 | .container > label { 14 | position: relative; 15 | display: inline-block; 16 | padding: 15px 15px 25px; 17 | border: 1px solid transparent; 18 | border-bottom: 0; 19 | cursor: pointer; 20 | font-weight: 600; 21 | } 22 | 23 | .container > label::after { 24 | content: ""; 25 | position: absolute; 26 | left: 15px; 27 | bottom: 10px; 28 | width: 22px; 29 | height: 4px; 30 | background: $background-color; 31 | } 32 | 33 | .container > label:hover, .container > input:focus + label { 34 | color: $brand-color; 35 | } 36 | 37 | .container > label:hover::after, .container > input:focus + label::after, .container > input:checked + label::after { 38 | background: $brand-color; 39 | } 40 | 41 | .container > input:checked + label { 42 | border-color: $grey-color; 43 | border-bottom: 1px solid #fff; 44 | margin-bottom: -1px; 45 | } 46 | 47 | .tab-content { 48 | border-top: 1px solid $grey-color; 49 | } 50 | 51 | .tab-content section.tab-panel { 52 | padding:30px; 53 | 54 | border-right: 1px $grey-color solid; 55 | border-bottom: 1px $grey-color solid; 56 | border-left: 1px $grey-color solid; 57 | } 58 | 59 | .container section.tab-panel { 60 | display: none; 61 | } 62 | 63 | .container > input:first-child:checked ~ .tab-content > section.tab-panel:first-child, 64 | .container > input:nth-child(3):checked ~ .tab-content > section.tab-panel:nth-child(2), 65 | .container > input:nth-child(5):checked ~ .tab-content > section.tab-panel:nth-child(3) { 66 | display: block; 67 | } -------------------------------------------------------------------------------- /docs/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #00aba9 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/favicon-16x16.png -------------------------------------------------------------------------------- /docs/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/favicon-32x32.png -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/favicon.ico -------------------------------------------------------------------------------- /docs/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/mstile-150x150.png -------------------------------------------------------------------------------- /docs/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-384x384.png", 12 | "sizes": "384x384", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /docs/v2/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # folder # 3 | ############### 4 | /**/DROP/ 5 | /**/TEMP/ 6 | /**/packages/ 7 | /**/bin/ 8 | /**/obj/ 9 | _site 10 | -------------------------------------------------------------------------------- /docs/v2/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs/v2/404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: 404 Error 4 | tagline: Ah snap! We could not find what you are looking for! 5 | permalink: /404.html 6 | --- 7 | 8 | Try got get back to the Home Page and start over! 9 | 10 | [Go to the Home Page]({{ '/' | absolute_url }}) 11 | -------------------------------------------------------------------------------- /docs/v2/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/v2/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/v2/android-chrome-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/v2/android-chrome-384x384.png -------------------------------------------------------------------------------- /docs/v2/api/.gitignore: -------------------------------------------------------------------------------- 1 | ############### 2 | # temp file # 3 | ############### 4 | *.yml 5 | .manifest 6 | -------------------------------------------------------------------------------- /docs/v2/api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | uid: apidocsindex 3 | --- 4 | # Examine V3 API Documentation 5 | 6 | API documentation is automatically generated. 7 | 8 | _**Tip**: There are many unit tests in the source code that can be used as Examples of how to do things. There is also a test web project that has plenty of examples of how to configure indexes and search them._ -------------------------------------------------------------------------------- /docs/v2/articles/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Configuration 2 | href: configuration.md 3 | - name: Indexing 4 | href: indexing.md 5 | - name: Searching 6 | href: searching.md 7 | - name: Sorting 8 | href: sorting.md 9 | - name: Paging 10 | href: sorting.md#paging-and-limiting-results -------------------------------------------------------------------------------- /docs/v2/docfx.json: -------------------------------------------------------------------------------- 1 | { 2 | "metadata": [ 3 | { 4 | "src": [ 5 | { 6 | "files": [ 7 | "src/**.csproj" 8 | ], 9 | "exclude":[ 10 | "**/bin/**", 11 | "**/obj/**", 12 | "**/Examine.Test**", 13 | "**/Examine.Web**" 14 | ], 15 | "src": "../../" 16 | } 17 | ], 18 | "dest": "api", 19 | "disableGitFeatures": false, 20 | "disableDefaultFilter": false 21 | } 22 | ], 23 | "build": { 24 | "content": [ 25 | { 26 | "files": [ 27 | "api/**.yml", 28 | "api/index.md" 29 | ] 30 | }, 31 | { 32 | "files": [ 33 | "articles/**.md", 34 | "articles/**/toc.yml", 35 | "toc.yml", 36 | "*.md", 37 | "docs-v1-v2/**.md", 38 | "docs-v1-v2/**/toc.yml" 39 | ] 40 | } 41 | ], 42 | "resource": [ 43 | { 44 | "files": [ 45 | "images/**" 46 | ] 47 | } 48 | ], 49 | "overwrite": [ 50 | { 51 | "files": [ 52 | "apidoc/**.md" 53 | ], 54 | "exclude": [ 55 | "obj/**", 56 | "_site/**" 57 | ] 58 | } 59 | ], 60 | "dest": "_site", 61 | "globalMetadataFiles": [], 62 | "fileMetadataFiles": [], 63 | "template": [ 64 | "default", 65 | "templates/material" 66 | ], 67 | "postProcessors": ["ExtractSearchIndex"], 68 | "markdownEngineName": "markdig", 69 | "noLangKeyword": false, 70 | "keepFileLink": false, 71 | "cleanupCacheHistory": false, 72 | "disableGitFeatures": false, 73 | "globalMetadata": { 74 | "_appTitle": "Examine", 75 | "_appFooter": "Examine", 76 | "_enableSearch": true, 77 | "_gitContribute": { 78 | "repo": "https://github.com/Shazwazza/Examine", 79 | "branch": "release/v3.0" 80 | }, 81 | "_appLogoPath": "/images/headerlogo.png", 82 | "_appFaviconPath": "/images/favicon.ico" 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /docs/v2/docs-v1-v2/toc.yml: -------------------------------------------------------------------------------- 1 | - name: V1 / V2 Articles 2 | href: quickstart.md 3 | - name: Configuration 4 | href: configuration.md 5 | - name: Indexing 6 | href: indexing.md 7 | - name: Searching 8 | href: searching.md 9 | - name: Sorting 10 | href: sorting.md 11 | -------------------------------------------------------------------------------- /docs/v2/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/v2/favicon-16x16.png -------------------------------------------------------------------------------- /docs/v2/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/v2/favicon-32x32.png -------------------------------------------------------------------------------- /docs/v2/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/v2/images/favicon.ico -------------------------------------------------------------------------------- /docs/v2/images/headerlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/v2/images/headerlogo.png -------------------------------------------------------------------------------- /docs/v2/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/docs/v2/mstile-150x150.png -------------------------------------------------------------------------------- /docs/v2/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /docs/v2/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-384x384.png", 12 | "sizes": "384x384", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /docs/v2/templates/material/partials/head.tmpl.partial: -------------------------------------------------------------------------------- 1 | {{!Copyright (c) Oscar Vasquez. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} 2 | 3 | 4 | 5 | 6 | {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} 7 | 8 | 9 | 10 | {{#_description}}{{/_description}} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{#_noindex}}{{/_noindex}} 19 | {{#_enableSearch}}{{/_enableSearch}} 20 | {{#_enableNewTab}}{{/_enableNewTab}} 21 | -------------------------------------------------------------------------------- /docs/v2/toc.yml: -------------------------------------------------------------------------------- 1 | - name: Articles 2 | href: articles/ 3 | - name: Api Documentation 4 | href: api/ 5 | homepage: api/index.md 6 | - name: Source Code 7 | href: https://github.com/Shazwazza/Examine 8 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | https://github.com/Shazwazza/Examine 6 | true 7 | true 8 | snupkg 9 | true 10 | NU5104 11 | 12 | 13 | 14 | 15 | 16 | 17 | true 18 | 19 | https://github.com/Shazwazza/Examine 20 | https://github.com/Shazwazza/Examine 21 | git 22 | $(Copyright) 23 | Shannon Deminick 24 | shandem 25 | logo-round-small.png 26 | MS-PL 27 | 2.0.0 28 | 29 | 30 | net6.0;net8.0; 31 | latest 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Examine.Benchmarks/Examine.Benchmarks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | false 8 | false 9 | Exe 10 | 11 | true 12 | $(DefineConstants);LocalBuild 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Examine.Benchmarks/ExamineBaseTest.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene; 2 | using Examine.Lucene.Directories; 3 | using Lucene.Net.Analysis; 4 | using Lucene.Net.Index; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | using Moq; 8 | using Directory = Lucene.Net.Store.Directory; 9 | 10 | namespace Examine.Benchmarks 11 | { 12 | public abstract class ExamineBaseTest 13 | { 14 | protected ILoggerFactory? LoggerFactory { get; private set; } 15 | 16 | public virtual void Setup() 17 | { 18 | LoggerFactory = CreateLoggerFactory(); 19 | LoggerFactory.CreateLogger(typeof(ExamineBaseTest)).LogDebug("Initializing test"); 20 | } 21 | 22 | public virtual void TearDown() => LoggerFactory!.Dispose(); 23 | 24 | public TestIndex GetTestIndex( 25 | Directory d, 26 | Analyzer analyzer, 27 | FieldDefinitionCollection? fieldDefinitions = null, 28 | IndexDeletionPolicy? indexDeletionPolicy = null, 29 | IReadOnlyDictionary? indexValueTypesFactory = null, 30 | double nrtTargetMaxStaleSec = 60, 31 | double nrtTargetMinStaleSec = 1, 32 | bool nrtEnabled = true) 33 | => new TestIndex( 34 | LoggerFactory!, 35 | Mock.Of>(x => x.Get(TestIndex.TestIndexName) == new LuceneDirectoryIndexOptions 36 | { 37 | FieldDefinitions = fieldDefinitions, 38 | DirectoryFactory = new GenericDirectoryFactory(_ => d, true), 39 | Analyzer = analyzer, 40 | IndexDeletionPolicy = indexDeletionPolicy, 41 | IndexValueTypesFactory = indexValueTypesFactory, 42 | #if LocalBuild 43 | NrtTargetMaxStaleSec = nrtTargetMaxStaleSec, 44 | NrtTargetMinStaleSec = nrtTargetMinStaleSec, 45 | NrtEnabled = nrtEnabled 46 | #endif 47 | })); 48 | 49 | //public TestIndex GetTestIndex( 50 | // IndexWriter writer, 51 | // double nrtTargetMaxStaleSec = 60, 52 | // double nrtTargetMinStaleSec = 1) 53 | // => new TestIndex( 54 | // LoggerFactory, 55 | // Mock.Of>(x => x.Get(TestIndex.TestIndexName) == new LuceneIndexOptions 56 | // { 57 | // NrtTargetMaxStaleSec = nrtTargetMaxStaleSec, 58 | // NrtTargetMinStaleSec = nrtTargetMinStaleSec 59 | // }), 60 | // writer); 61 | 62 | protected virtual ILoggerFactory CreateLoggerFactory() 63 | => Microsoft.Extensions.Logging.LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Debug)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Examine.Benchmarks/IndexVersionComparison.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using Examine.Lucene.Providers; 3 | using Lucene.Net.Analysis.Standard; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Examine.Benchmarks 7 | { 8 | [Config(typeof(NugetConfig))] 9 | [ThreadingDiagnoser] 10 | [MemoryDiagnoser] 11 | public class IndexVersionComparison : ExamineBaseTest 12 | { 13 | private readonly List _valueSets = InitTools.CreateValueSet(100); 14 | private readonly StandardAnalyzer _analyzer = new StandardAnalyzer(LuceneInfo.CurrentVersion); 15 | private ILogger? _logger; 16 | private string? _tempBasePath; 17 | private LuceneIndex? _indexer; 18 | 19 | [GlobalSetup] 20 | public override void Setup() 21 | { 22 | base.Setup(); 23 | 24 | _logger = LoggerFactory!.CreateLogger(); 25 | _tempBasePath = Path.Combine(Path.GetTempPath(), "ExamineTests"); 26 | _indexer = InitTools.InitializeIndex(this, _tempBasePath, _analyzer, out _); 27 | } 28 | 29 | [GlobalCleanup] 30 | public override void TearDown() 31 | { 32 | _indexer!.Dispose(); 33 | base.TearDown(); 34 | System.IO.Directory.Delete(_tempBasePath!, true); 35 | } 36 | 37 | [Benchmark] 38 | public void IndexItemsNonAsync() => IndexItems(_indexer!, _valueSets); 39 | 40 | #if RELEASE 41 | protected override ILoggerFactory CreateLoggerFactory() 42 | => Microsoft.Extensions.Logging.LoggerFactory.Create(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Information)); 43 | #endif 44 | 45 | private static void IndexItems(LuceneIndex indexer, IEnumerable valueSets) 46 | => indexer.IndexItems(valueSets); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Examine.Benchmarks/InitTools.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Analysis; 2 | using Lucene.Net.Store; 3 | 4 | namespace Examine.Benchmarks 5 | { 6 | internal class InitTools 7 | { 8 | public static TestIndex InitializeIndex( 9 | ExamineBaseTest examineBaseTest, 10 | string tempBasePath, 11 | Analyzer analyzer, 12 | out DirectoryInfo indexDir) 13 | { 14 | var tempPath = Path.Combine(tempBasePath, Guid.NewGuid().ToString()); 15 | System.IO.Directory.CreateDirectory(tempPath); 16 | indexDir = new DirectoryInfo(tempPath); 17 | var luceneDirectory = FSDirectory.Open(indexDir); 18 | var indexer = examineBaseTest.GetTestIndex(luceneDirectory, analyzer); 19 | return indexer; 20 | } 21 | 22 | public static List CreateValueSet(int count) 23 | { 24 | var random = new Random(); 25 | var valueSets = new List(); 26 | 27 | for (var i = 0; i < count; i++) 28 | { 29 | valueSets.Add(ValueSet.FromObject(Guid.NewGuid().ToString(), "content", 30 | new 31 | { 32 | nodeName = "location" + (i % 2 == 0 ? "1" : "2"), 33 | bodyText = Enumerable.Range(0, random.Next(10, 100)).Select(x => Guid.NewGuid().ToString()), 34 | number = random.Next(0, 1000), 35 | date = DateTime.Now.AddMinutes(random.Next(-1000, 1000)) 36 | })); 37 | } 38 | 39 | return valueSets; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Examine.Benchmarks/NugetConfig.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Configs; 2 | using BenchmarkDotNet.Environments; 3 | using BenchmarkDotNet.Jobs; 4 | 5 | namespace Examine.Benchmarks 6 | { 7 | public class NugetConfig : ManualConfig 8 | { 9 | public NugetConfig() 10 | { 11 | var baseJob = Job.ShortRun 12 | .WithRuntime(CoreRuntime.Core80); 13 | 14 | AddJob(baseJob.WithId("Source")); 15 | AddJob(baseJob.WithNuGet("Examine", "3.3.0").WithId("3.3.0").WithArguments([new MsBuildArgument("/p:LocalBuild=false")])); 16 | AddJob(baseJob.WithNuGet("Examine", "3.2.1").WithId("3.2.1").WithArguments([new MsBuildArgument("/p:LocalBuild=false")])); 17 | AddJob(baseJob.WithNuGet("Examine", "3.1.0").WithId("3.1.0").WithArguments([new MsBuildArgument("/p:LocalBuild=false")])); 18 | AddJob(baseJob.WithNuGet("Examine", "3.0.1").WithId("3.0.1").WithArguments([new MsBuildArgument("/p:LocalBuild=false")])); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Examine.Benchmarks/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using BenchmarkDotNet.Running; 7 | using Microsoft.Diagnostics.Tracing.Parsers.Kernel; 8 | 9 | namespace Examine.Benchmarks 10 | { 11 | public class Program 12 | { 13 | #if RELEASE 14 | public static void Main(string[] args) => 15 | BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); 16 | #else 17 | public static async Task Main(string[] args) 18 | { 19 | var bench = new SearchVersionComparison(); 20 | try 21 | { 22 | bench.Setup(); 23 | //await Threads100(bench); 24 | await Threads1(bench); 25 | } 26 | finally 27 | { 28 | bench.TearDown(); 29 | } 30 | } 31 | #endif 32 | // Call your function here. 33 | 34 | #if LocalBuild 35 | private static async Task Threads100(ConcurrentSearchBenchmarks bench) 36 | { 37 | bench.ThreadCount = 50; 38 | //bench.MaxResults = 10; 39 | 40 | for (var i = 0; i < 100; i++) 41 | { 42 | await bench.ExamineStandard(); 43 | } 44 | } 45 | 46 | private static async Task Threads1(SearchVersionComparison bench) 47 | { 48 | bench.ThreadCount = 1; 49 | //bench.MaxResults = 10; 50 | 51 | for (var i = 0; i < 100; i++) 52 | { 53 | await bench.ConcurrentSearch(); 54 | } 55 | } 56 | #endif 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Examine.Benchmarks/TestIndex.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene; 2 | using Examine.Lucene.Providers; 3 | using Lucene.Net.Index; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.Options; 6 | 7 | namespace Examine.Benchmarks 8 | { 9 | public class TestIndex : LuceneIndex 10 | { 11 | public const string TestIndexName = "testIndexer"; 12 | 13 | public TestIndex(ILoggerFactory loggerFactory, IOptionsMonitor options) 14 | : base(loggerFactory, TestIndexName, options) 15 | { 16 | RunAsync = false; 17 | } 18 | 19 | //public TestIndex(ILoggerFactory loggerFactory, IOptionsMonitor options, IndexWriter writer) 20 | // : base(loggerFactory, TestIndexName, options, writer) 21 | //{ 22 | // RunAsync = false; 23 | //} 24 | 25 | public static IEnumerable AllData() 26 | { 27 | var data = new List(); 28 | for (var i = 0; i < 100; i++) 29 | { 30 | data.Add(ValueSet.FromObject(i.ToString(), "category" + (i % 2), new { item1 = "value" + i, item2 = "value" + i })); 31 | } 32 | return data; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Examine.Core/BaseSearchProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Search; 3 | 4 | namespace Examine 5 | { 6 | /// 7 | /// Abstract searcher 8 | /// 9 | public abstract class BaseSearchProvider : ISearcher 10 | { 11 | protected BaseSearchProvider(string name) 12 | { 13 | if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); 14 | Name = name; 15 | } 16 | 17 | public string Name { get; } 18 | 19 | /// 20 | /// Searches the index 21 | /// 22 | /// 23 | /// 24 | /// 25 | public abstract ISearchResults Search(string searchText, QueryOptions options = null); 26 | 27 | /// 28 | public abstract IQuery CreateQuery(string category = null, BooleanOperation defaultOperation = BooleanOperation.And); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Examine.Core/DisposableObjectSlim.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Examine 4 | { 5 | 6 | /// 7 | /// This should not be use if there are unmanaged resources to be disposed, use DisposableObject instead 8 | /// 9 | public abstract class DisposableObjectSlim : IDisposable 10 | { 11 | private readonly object _locko = new object(); 12 | 13 | // gets a value indicating whether this instance is disposed. 14 | // for internal tests only (not thread safe) 15 | protected bool Disposed { get; private set; } 16 | 17 | // implements IDisposable 18 | public void Dispose() 19 | { 20 | Dispose(true); 21 | } 22 | 23 | private void Dispose(bool disposing) 24 | { 25 | lock (_locko) 26 | { 27 | if (Disposed) return; 28 | Disposed = true; 29 | } 30 | 31 | if (disposing) 32 | DisposeResources(); 33 | } 34 | 35 | protected abstract void DisposeResources(); 36 | } 37 | } -------------------------------------------------------------------------------- /src/Examine.Core/EmptySearchResults.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Examine 6 | { 7 | public sealed class EmptySearchResults : ISearchResults 8 | { 9 | private EmptySearchResults() 10 | { 11 | } 12 | 13 | public static ISearchResults Instance { get; } = new EmptySearchResults(); 14 | 15 | public IEnumerator GetEnumerator() 16 | { 17 | return Enumerable.Empty().GetEnumerator(); 18 | } 19 | 20 | IEnumerator IEnumerable.GetEnumerator() 21 | { 22 | return Enumerable.Empty().GetEnumerator(); 23 | } 24 | 25 | public long TotalItemCount => 0; 26 | 27 | 28 | #pragma warning disable IDE0060 // Remove unused parameter 29 | public IEnumerable Skip(int skip) 30 | #pragma warning restore IDE0060 // Remove unused parameter 31 | { 32 | return Enumerable.Empty(); 33 | } 34 | 35 | #pragma warning disable IDE0060 // Remove unused parameter 36 | public IEnumerable SkipTake(int skip, int? take = null) 37 | #pragma warning restore IDE0060 // Remove unused parameter 38 | { 39 | return Enumerable.Empty(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Examine.Core/Examine.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Examine 5 | false 6 | Examine is an abstraction for indexing and search operations with implementations such as Lucene.Net 7 | examine search index 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Examine.Core/ExamineExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace Examine 5 | { 6 | /// 7 | /// Static methods to help query umbraco xml 8 | /// 9 | public static class ExamineExtensions 10 | { 11 | public static T GetNamedOptions(this IOptionsMonitor optionsMonitor, string name) 12 | where T : class 13 | { 14 | T options = optionsMonitor.Get(name); 15 | 16 | if (options == null) 17 | { 18 | throw new InvalidOperationException($"No named {typeof(T)} options with name {name}"); 19 | } 20 | 21 | return options; 22 | } 23 | 24 | /// 25 | /// Gets the index by name, throw if not found 26 | /// 27 | /// 28 | /// 29 | /// 30 | public static IIndex GetIndex(this IExamineManager examineManager, string indexName) 31 | { 32 | if (examineManager.TryGetIndex(indexName, out IIndex index)) 33 | { 34 | return index; 35 | } 36 | throw new InvalidOperationException("No index found with name " + indexName); 37 | } 38 | 39 | public static void DeleteFromIndex(this IIndex index, string itemId) 40 | { 41 | index.DeleteFromIndex(new[] {itemId}); 42 | } 43 | 44 | /// 45 | /// Method to re-index specific data 46 | /// 47 | /// 48 | /// 49 | public static void IndexItem(this IIndex index, ValueSet node) 50 | { 51 | index.IndexItems(new[] { node }); 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Examine.Core/ExamineFieldNames.cs: -------------------------------------------------------------------------------- 1 | namespace Examine 2 | { 3 | public static class ExamineFieldNames 4 | { 5 | /// 6 | /// The prefix characters denoting a special field stored in the lucene index for use internally 7 | /// 8 | public const string SpecialFieldPrefix = "__"; 9 | 10 | /// 11 | /// The prefix added to a field when it is included in the index for sorting 12 | /// 13 | public const string SortedFieldNamePrefix = "__Sort_"; 14 | 15 | /// 16 | /// Used to store a non-tokenized key for the document for the Category 17 | /// 18 | public const string CategoryFieldName = "__IndexType"; 19 | 20 | /// 21 | /// Used to store a non-tokenized type for the document 22 | /// 23 | public const string ItemIdFieldName = "__NodeId"; 24 | 25 | public const string ItemTypeFieldName = "__NodeTypeAlias"; 26 | } 27 | 28 | 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/Examine.Core/FieldDefinition.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Examine 4 | { 5 | /// 6 | /// Defines a field to be indexed 7 | /// 8 | public readonly struct FieldDefinition : IEquatable 9 | { 10 | /// 11 | /// Constructor 12 | /// 13 | /// 14 | /// 15 | public FieldDefinition(string name, string type) 16 | { 17 | if (string.IsNullOrWhiteSpace(name)) 18 | { 19 | throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); 20 | } 21 | if (string.IsNullOrWhiteSpace(type)) 22 | { 23 | throw new ArgumentException("Value cannot be null or whitespace.", nameof(type)); 24 | } 25 | Name = name; 26 | Type = type; 27 | } 28 | 29 | /// 30 | /// The name of the index field 31 | /// 32 | public string Name { get; } 33 | 34 | /// 35 | /// The data type 36 | /// 37 | public string Type { get; } 38 | 39 | public bool Equals(FieldDefinition other) => string.Equals(Name, other.Name) && string.Equals(Type, other.Type); 40 | 41 | public override bool Equals(object obj) 42 | { 43 | if (ReferenceEquals(null, obj)) 44 | return false; 45 | return obj is FieldDefinition definition && Equals(definition); 46 | } 47 | 48 | public override int GetHashCode() 49 | { 50 | unchecked 51 | { 52 | return (Name.GetHashCode() * 397) ^ Type.GetHashCode(); 53 | } 54 | } 55 | 56 | public static bool operator ==(FieldDefinition left, FieldDefinition right) => left.Equals(right); 57 | 58 | public static bool operator !=(FieldDefinition left, FieldDefinition right) => !left.Equals(right); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Examine.Core/FieldDefinitionCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Examine 4 | { 5 | public class FieldDefinitionCollection : ReadOnlyFieldDefinitionCollection 6 | { 7 | public FieldDefinitionCollection(params FieldDefinition[] definitions) : base(definitions) 8 | { 9 | } 10 | 11 | public FieldDefinitionCollection() 12 | { 13 | } 14 | 15 | public FieldDefinition GetOrAdd(string fieldName, Func add) => Definitions.GetOrAdd(fieldName, add); 16 | 17 | /// 18 | /// Replace any definition with the specified one, if one doesn't exist then it is added 19 | /// 20 | /// 21 | public void AddOrUpdate(FieldDefinition definition) => Definitions.AddOrUpdate(definition.Name, definition, (s, factory) => definition); 22 | 23 | public bool TryAdd(FieldDefinition definition) => Definitions.TryAdd(definition.Name, definition); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Examine.Core/FieldDefinitionTypes.cs: -------------------------------------------------------------------------------- 1 | namespace Examine 2 | { 3 | /// 4 | /// Contains the names of field definition types 5 | /// 6 | public static class FieldDefinitionTypes 7 | { 8 | public const string Integer = "int"; 9 | public const string Float = "float"; 10 | public const string Double = "double"; 11 | public const string Long = "long"; 12 | public const string DateTime = "datetime"; 13 | public const string DateYear = "date.year"; 14 | public const string DateMonth = "date.month"; 15 | public const string DateDay = "date.day"; 16 | public const string DateHour = "date.hour"; 17 | public const string DateMinute = "date.minute"; 18 | 19 | /// 20 | /// Will be indexed without analysis 21 | /// 22 | public const string Raw = "raw"; 23 | 24 | /// 25 | /// The default type, will be indexed with the specified indexer's analyzer 26 | /// 27 | public const string FullText = "fulltext"; 28 | 29 | /// 30 | /// Will be indexed with the specified indexer's analyzer and with a sorting field 31 | /// 32 | public const string FullTextSortable = "fulltextsortable"; 33 | 34 | /// 35 | /// Will be indexed with a culture invariant whitespace analyzer, this is what is used for 'special' prefixed fields 36 | /// 37 | public const string InvariantCultureIgnoreCase = "invariantcultureignorecase"; 38 | 39 | /// 40 | /// Will be indexed with an email address analyzer 41 | /// 42 | public const string EmailAddress = "emailaddress"; 43 | } 44 | } -------------------------------------------------------------------------------- /src/Examine.Core/IExamineManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Examine 5 | { 6 | public interface IExamineManager 7 | { 8 | /// 9 | /// Gets a list of all index providers 10 | /// 11 | /// 12 | /// This returns all config based indexes and indexers registered in code 13 | /// 14 | IEnumerable Indexes { get; } 15 | 16 | /// 17 | /// Gets a list of all manually configured search providers 18 | /// 19 | /// 20 | /// This returns only those searchers explicitly registered with or config based searchers 21 | /// 22 | IEnumerable RegisteredSearchers { get; } 23 | 24 | void Dispose(); 25 | 26 | /// 27 | /// Returns an indexer by name 28 | /// 29 | /// 30 | /// 31 | /// true if the index was found by name 32 | bool TryGetIndex(string indexName, out IIndex index); 33 | 34 | /// 35 | /// Returns a searcher that was registered with or via config 36 | /// 37 | /// 38 | /// 39 | /// 40 | /// true if the searcher was found by name 41 | /// 42 | bool TryGetSearcher(string searcherName, out ISearcher searcher); 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Examine.Core/IIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Examine 5 | { 6 | 7 | /// 8 | /// Interface to represent an Examine Indexer 9 | /// 10 | public interface IIndex 11 | { 12 | string Name { get; } 13 | 14 | /// 15 | /// Returns a searcher for the index 16 | /// 17 | /// 18 | ISearcher Searcher { get; } 19 | 20 | /// 21 | /// Method to index data 22 | /// 23 | /// 24 | void IndexItems(IEnumerable values); 25 | 26 | /// 27 | /// Deletes a node from the index 28 | /// 29 | /// Node to delete 30 | void DeleteFromIndex(IEnumerable itemIds); 31 | 32 | /// 33 | /// Creates a new index, any existing index will be deleted 34 | /// 35 | void CreateIndex(); 36 | 37 | /// 38 | /// Returns the field definitions for the index 39 | /// 40 | ReadOnlyFieldDefinitionCollection FieldDefinitions { get; } 41 | 42 | /// 43 | /// determines whether the index exsists or not 44 | /// 45 | bool IndexExists(); 46 | 47 | /// 48 | /// Raised once an index operation is completed 49 | /// 50 | event EventHandler IndexOperationComplete; 51 | 52 | /// 53 | /// Raised before the item is indexed allowing developers to customize the data that get's stored in the index 54 | /// 55 | event EventHandler TransformingIndexValues; 56 | 57 | /// 58 | /// Occurs for an Indexing Error 59 | /// 60 | event EventHandler IndexingError; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Examine.Core/IIndexStats.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace Examine 5 | { 6 | public interface IIndexStats 7 | { 8 | long GetDocumentCount(); 9 | IEnumerable GetFieldNames(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Examine.Core/ISearchResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Examine 4 | { 5 | public interface ISearchResult 6 | { 7 | string Id { get; } 8 | 9 | float Score { get; } 10 | 11 | /// 12 | /// Returns the values in the result 13 | /// 14 | IReadOnlyDictionary Values { get; } 15 | 16 | /// 17 | /// Returns the values in the result 18 | /// 19 | /// 20 | /// This is used to retrieve multiple values per field if there are any 21 | /// 22 | IReadOnlyDictionary> AllValues { get; } 23 | 24 | /// 25 | /// If a single field was indexed with multiple values this will return those values, otherwise it will just return the single 26 | /// value stored for that field. If the field is not found it returns an empty collection. 27 | /// 28 | /// 29 | /// 30 | IEnumerable GetValues(string key); 31 | 32 | /// 33 | /// Returns the key value pair for the index specified 34 | /// 35 | /// 36 | /// 37 | KeyValuePair this[int resultIndex] { get; } 38 | 39 | /// 40 | /// Returns the value for the key specified 41 | /// 42 | /// 43 | /// 44 | string this[string key] { get; } 45 | } 46 | } -------------------------------------------------------------------------------- /src/Examine.Core/ISearchResults.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Examine 4 | { 5 | public interface ISearchResults : IEnumerable 6 | { 7 | /// 8 | /// Returns the Total item count for the search regardless of skip/take/max count values 9 | /// 10 | long TotalItemCount { get; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/Examine.Core/ISearcher.cs: -------------------------------------------------------------------------------- 1 | using Examine.Search; 2 | 3 | namespace Examine 4 | { 5 | /// 6 | /// An interface representing an Examine Searcher 7 | /// 8 | public interface ISearcher 9 | { 10 | string Name { get; } 11 | 12 | /// 13 | /// Searches the index 14 | /// 15 | /// The search text or a native query 16 | /// 17 | /// Search Results 18 | ISearchResults Search(string searchText, QueryOptions options = null); 19 | 20 | /// 21 | /// Creates a search criteria instance as required by the implementation 22 | /// 23 | /// The type of data in the index. 24 | /// The default operation. 25 | /// 26 | /// An instance of 27 | /// 28 | IQuery CreateQuery(string category = null, BooleanOperation defaultOperation = BooleanOperation.And); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Examine.Core/IValueSetValidator.cs: -------------------------------------------------------------------------------- 1 | namespace Examine 2 | { 3 | /// 4 | /// Used to validate a value set to be indexed, if validation fails it will not be indexed 5 | /// 6 | public interface IValueSetValidator 7 | { 8 | ValueSetValidationResult Validate(ValueSet valueSet); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Examine.Core/IndexOperation.cs: -------------------------------------------------------------------------------- 1 | namespace Examine 2 | { 3 | /// 4 | /// Represents an indexing operation (either add/remove) 5 | /// 6 | public readonly struct IndexOperation 7 | { 8 | /// 9 | /// Initializes a new instance of the class. 10 | /// 11 | public IndexOperation(ValueSet valueSet, IndexOperationType operation) 12 | { 13 | ValueSet = valueSet; 14 | Operation = operation; 15 | } 16 | 17 | /// 18 | /// Gets the Index item 19 | /// 20 | public ValueSet ValueSet { get; } 21 | 22 | /// 23 | /// Gets or sets the operation. 24 | /// 25 | /// 26 | /// The operation. 27 | /// 28 | public IndexOperationType Operation { get; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Examine.Core/IndexOperationEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Examine 4 | { 5 | public class IndexOperationEventArgs : EventArgs 6 | { 7 | public IIndex Index { get; } 8 | public int ItemsIndexed { get; } 9 | 10 | public IndexOperationEventArgs(IIndex index, int itemsIndexed) 11 | { 12 | Index = index; 13 | ItemsIndexed = itemsIndexed; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Examine.Core/IndexOperationType.cs: -------------------------------------------------------------------------------- 1 | namespace Examine 2 | { 3 | /// 4 | /// The type of index operation 5 | /// 6 | public enum IndexOperationType 7 | { 8 | Add, 9 | Delete 10 | } 11 | } -------------------------------------------------------------------------------- /src/Examine.Core/IndexOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Examine 2 | { 3 | public class IndexOptions 4 | { 5 | public IndexOptions() => FieldDefinitions = new FieldDefinitionCollection(); 6 | 7 | public FieldDefinitionCollection FieldDefinitions { get; set; } 8 | public IValueSetValidator Validator { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Examine.Core/IndexingErrorEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Examine 7 | { 8 | public class IndexingErrorEventArgs : EventArgs 9 | { 10 | 11 | public IndexingErrorEventArgs(IIndex index, string message, string itemId, Exception exception) 12 | { 13 | Index = index; 14 | ItemId = itemId; 15 | Message = message; 16 | Exception = exception; 17 | } 18 | 19 | public Exception Exception { get; } 20 | public string Message { get; } 21 | public IIndex Index { get; } 22 | public string ItemId { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Examine.Core/IndexingItemEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | 5 | namespace Examine 6 | { 7 | public class IndexingItemEventArgs : CancelEventArgs 8 | { 9 | public IIndex Index { get; } 10 | 11 | public ValueSet ValueSet { get; private set; } 12 | 13 | public IndexingItemEventArgs(IIndex index, ValueSet valueSet) 14 | { 15 | Index = index; 16 | ValueSet = valueSet; 17 | } 18 | 19 | public void SetValues(IDictionary> values) 20 | => ValueSet = new ValueSet(ValueSet.Id, ValueSet.Category, ValueSet.ItemType, values); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Examine.Core/ObjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Linq.Expressions; 9 | using System.Reflection; 10 | using System.Runtime.Serialization; 11 | using System.Runtime.Serialization.Formatters.Binary; 12 | using System.Security; 13 | using System.Xml; 14 | 15 | namespace Examine 16 | { 17 | public static class ObjectExtensions 18 | { 19 | /// 20 | /// Turns object into dictionary 21 | /// 22 | /// 23 | /// Properties to ignore 24 | /// 25 | public static IDictionary ConvertObjectToDictionary(object o, params string[] ignoreProperties) 26 | { 27 | if (o != null) 28 | { 29 | if (o is IDictionary) 30 | throw new InvalidOperationException($"The input object is already of type {typeof(IDictionary)}"); 31 | 32 | var props = TypeDescriptor.GetProperties(o); 33 | var d = new Dictionary(); 34 | foreach (var prop in props.Cast().Where(x => !ignoreProperties.Contains(x.Name))) 35 | { 36 | var val = prop.GetValue(o); 37 | if (val != null) 38 | { 39 | d.Add(prop.Name, val); 40 | } 41 | } 42 | return d; 43 | } 44 | return new Dictionary(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Examine.Core/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- 1 | const Examine.Search.QueryOptions.AbsoluteMaxResults = 10000 -> int 2 | const Examine.Search.QueryOptions.DefaultMaxResults = 100 -> int 3 | -------------------------------------------------------------------------------- /src/Examine.Core/ReadOnlyFieldDefinitionCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace Examine 8 | { 9 | /// 10 | /// Manages the mappings between a field name and it's index type 11 | /// 12 | public class ReadOnlyFieldDefinitionCollection : IEnumerable 13 | { 14 | public ReadOnlyFieldDefinitionCollection() 15 | : this(Enumerable.Empty()) 16 | { 17 | } 18 | 19 | public ReadOnlyFieldDefinitionCollection(params FieldDefinition[] definitions) 20 | : this((IEnumerable)definitions) 21 | { 22 | 23 | } 24 | 25 | public ReadOnlyFieldDefinitionCollection(IEnumerable definitions) 26 | { 27 | if (definitions == null) return; 28 | 29 | foreach (var f in definitions.GroupBy(x => x.Name)) 30 | { 31 | var indexField = f.FirstOrDefault(); 32 | if (indexField != default) 33 | { 34 | Definitions.TryAdd(f.Key, indexField); 35 | } 36 | } 37 | } 38 | 39 | /// 40 | /// Tries to get a by field name 41 | /// 42 | /// 43 | /// 44 | /// 45 | /// returns true if one was found otherwise false 46 | /// 47 | /// 48 | /// Marked as virtual so developers can inherit this class and override this method in case 49 | /// field definitions are dynamic. 50 | /// 51 | public virtual bool TryGetValue(string fieldName, out FieldDefinition fieldDefinition) => Definitions.TryGetValue(fieldName, out fieldDefinition); 52 | 53 | public int Count => Definitions.Count; 54 | 55 | protected ConcurrentDictionary Definitions { get; } = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); 56 | 57 | public IEnumerator GetEnumerator() => Definitions.Values.GetEnumerator(); 58 | 59 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/BooleanOperation.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Search 2 | { 3 | public enum BooleanOperation 4 | { 5 | And, Or, Not 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/ExamineValue.cs: -------------------------------------------------------------------------------- 1 | using Examine.Search; 2 | 3 | namespace Examine.Search 4 | { 5 | public readonly struct ExamineValue : IExamineValue 6 | { 7 | public ExamineValue(Examineness vagueness, string value) 8 | : this(vagueness, value, 1) 9 | { 10 | } 11 | 12 | public ExamineValue(Examineness vagueness, string value, float level) 13 | { 14 | Examineness = vagueness; 15 | Value = value; 16 | Level = level; 17 | } 18 | 19 | public Examineness Examineness { get; } 20 | 21 | public string Value { get; } 22 | 23 | public float Level { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/Examineness.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Examine.Search 3 | { 4 | 5 | /// 6 | /// Different ways to match terms 7 | /// 8 | public enum Examineness 9 | { 10 | /// 11 | /// Matches terms using 'fuzzy' logic 12 | /// 13 | Fuzzy, 14 | 15 | /// 16 | /// Wildcard matching a single character 17 | /// 18 | SimpleWildcard, 19 | 20 | /// 21 | /// Wildcard matching multiple characters 22 | /// 23 | ComplexWildcard, 24 | 25 | /// 26 | /// A normal phrase query 27 | /// 28 | Explicit, 29 | 30 | /// 31 | /// Becomes exact match 32 | /// 33 | Escaped, 34 | 35 | /// 36 | /// Makes the term rank differently than normal 37 | /// 38 | Boosted, 39 | 40 | /// 41 | /// Searches for terms within a proximity of each other 42 | /// 43 | Proximity 44 | 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/IBooleanOperation.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | 4 | namespace Examine.Search 5 | { 6 | /// 7 | /// Defines the supported operation for addition of additional clauses in the fluent API 8 | /// 9 | public interface IBooleanOperation : IOrdering 10 | { 11 | /// 12 | /// Sets the next operation to be AND 13 | /// 14 | /// 15 | IQuery And(); 16 | 17 | /// 18 | /// Adds the nested query 19 | /// 20 | /// 21 | /// 22 | /// 23 | IBooleanOperation And(Func inner, BooleanOperation defaultOp = BooleanOperation.And); 24 | 25 | /// 26 | /// Sets the next operation to be OR 27 | /// 28 | /// 29 | IQuery Or(); 30 | 31 | /// 32 | /// Adds the nested query 33 | /// 34 | /// 35 | /// 36 | /// 37 | IBooleanOperation Or(Func inner, BooleanOperation defaultOp = BooleanOperation.And); 38 | 39 | /// 40 | /// Sets the next operation to be NOT 41 | /// 42 | /// 43 | IQuery Not(); 44 | 45 | /// 46 | /// Adds the nested query 47 | /// 48 | /// 49 | /// 50 | /// 51 | IBooleanOperation AndNot(Func inner, BooleanOperation defaultOp = BooleanOperation.And); 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/IExamineValue.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Examine.Search 3 | { 4 | public interface IExamineValue 5 | { 6 | Examineness Examineness { get; } 7 | float Level { get; } 8 | string Value { get; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/INestedBooleanOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Examine.Search 4 | { 5 | public interface INestedBooleanOperation 6 | { 7 | /// 8 | /// Sets the next operation to be AND 9 | /// 10 | /// 11 | INestedQuery And(); 12 | 13 | /// 14 | /// Adds the nested query 15 | /// 16 | /// 17 | /// 18 | /// 19 | INestedBooleanOperation And(Func inner, BooleanOperation defaultOp = BooleanOperation.And); 20 | 21 | /// 22 | /// Sets the next operation to be OR 23 | /// 24 | /// 25 | INestedQuery Or(); 26 | 27 | /// 28 | /// Adds the nested query 29 | /// 30 | /// 31 | /// 32 | /// 33 | INestedBooleanOperation Or(Func inner, BooleanOperation defaultOp = BooleanOperation.And); 34 | 35 | /// 36 | /// Sets the next operation to be NOT 37 | /// 38 | /// 39 | INestedQuery Not(); 40 | 41 | /// 42 | /// Adds the nested query 43 | /// 44 | /// 45 | /// 46 | /// 47 | INestedBooleanOperation AndNot(Func inner, BooleanOperation defaultOp = BooleanOperation.And); 48 | } 49 | } -------------------------------------------------------------------------------- /src/Examine.Core/Search/IOrdering.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Examine.Search 4 | { 5 | public interface IOrdering : IQueryExecutor 6 | { 7 | /// 8 | /// Orders the results by the specified fields 9 | /// 10 | /// The field names. 11 | /// 12 | IOrdering OrderBy(params SortableField[] fields); 13 | 14 | /// 15 | /// Orders the results by the specified fields in a descending order 16 | /// 17 | /// The field names. 18 | /// 19 | IOrdering OrderByDescending(params SortableField[] fields); 20 | 21 | /// 22 | /// Return only the specified fields 23 | /// 24 | /// The field names for fields to load 25 | /// 26 | IOrdering SelectFields(ISet fieldNames); 27 | 28 | /// 29 | /// Return only the specified field. Use when possible as internally a new HashSet is created on each call 30 | /// 31 | /// The field name of the field to load 32 | /// 33 | IOrdering SelectField(string fieldName); 34 | 35 | /// 36 | /// Return all fields in the index 37 | /// 38 | /// 39 | IOrdering SelectAllFields(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/IQueryExecutor.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Search 2 | { 3 | 4 | /// 5 | /// Executes a query 6 | /// 7 | public interface IQueryExecutor 8 | { 9 | /// 10 | /// Executes the query 11 | /// 12 | ISearchResults Execute(QueryOptions options = null); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/QueryOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Examine.Search 4 | { 5 | public class QueryOptions 6 | { 7 | public const int AbsoluteMaxResults = 10000; 8 | 9 | public const int DefaultMaxResults = 100; 10 | 11 | public static QueryOptions SkipTake(int skip, int? take = null) => new QueryOptions(skip, take ?? DefaultMaxResults); 12 | 13 | public static QueryOptions Default { get; } = new QueryOptions(0, DefaultMaxResults); 14 | 15 | public QueryOptions(int skip, int? take = null) 16 | { 17 | if (skip < 0) 18 | { 19 | throw new ArgumentException("Skip cannot be negative"); 20 | } 21 | 22 | if (take.HasValue && take < 0) 23 | { 24 | throw new ArgumentException("Take cannot be negative"); 25 | } 26 | 27 | Skip = skip; 28 | Take = take ?? DefaultMaxResults; 29 | } 30 | 31 | /// 32 | /// The number of documents to skip in the result set. 33 | /// 34 | public int Skip { get; } 35 | 36 | /// 37 | /// The number of documents to take in the result set. 38 | /// 39 | public int Take { get; } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/SortType.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Search 2 | { 3 | /// 4 | /// Used during a sort operation to specify how the field should be sorted 5 | /// 6 | public enum SortType 7 | { 8 | /// 9 | /// Sort by document score (relevancy). Sort values are Float and higher 10 | /// values are at the front. 11 | /// 12 | /// 13 | Score, 14 | /// 15 | /// Sort by document number (index order). Sort values are Integer and lower 16 | /// values are at the front. 17 | /// 18 | /// 19 | DocumentOrder, 20 | /// 21 | /// Sort using term values as Strings. Sort values are String and lower 22 | /// values are at the front. 23 | /// 24 | /// 25 | String, 26 | /// 27 | /// Sort using term values as encoded Integers. Sort values are Integer and 28 | /// lower values are at the front. 29 | /// 30 | /// 31 | Int, 32 | /// 33 | /// Sort using term values as encoded Floats. Sort values are Float and 34 | /// lower values are at the front. 35 | /// 36 | /// 37 | Float, 38 | /// 39 | /// Sort using term values as encoded Longs. Sort values are Long and 40 | /// lower values are at the front. 41 | /// 42 | /// 43 | Long, 44 | /// 45 | /// Sort using term values as encoded Doubles. Sort values are Double and 46 | /// lower values are at the front. 47 | /// 48 | /// 49 | Double 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Examine.Core/Search/SortableField.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Search 2 | { 3 | /// 4 | /// Represents a field used to sort results 5 | /// 6 | public readonly struct SortableField 7 | { 8 | /// 9 | /// The field name to sort by 10 | /// 11 | public string FieldName { get; } 12 | 13 | /// 14 | /// The way in which the results will be sorted by the field specified. 15 | /// 16 | public SortType SortType { get; } 17 | 18 | /// 19 | /// Constructor 20 | /// 21 | /// 22 | public SortableField(string fieldName) 23 | { 24 | FieldName = fieldName; 25 | SortType = SortType.String; 26 | } 27 | 28 | /// 29 | /// Constructor 30 | /// 31 | /// 32 | /// 33 | public SortableField(string fieldName, SortType sortType) 34 | { 35 | FieldName = fieldName; 36 | SortType = sortType; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Examine.Core/ValueSetValidationResult.cs: -------------------------------------------------------------------------------- 1 | namespace Examine 2 | { 3 | public readonly struct ValueSetValidationResult 4 | { 5 | public ValueSetValidationResult(ValueSetValidationStatus status, ValueSet valueSet) 6 | { 7 | Status = status; 8 | ValueSet = valueSet; 9 | } 10 | 11 | public ValueSetValidationStatus Status { get; } 12 | 13 | public ValueSet ValueSet { get; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Examine.Core/ValueSetValidationStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Examine 2 | { 3 | public enum ValueSetValidationStatus 4 | { 5 | /// 6 | /// If the result is valid 7 | /// 8 | Valid, 9 | 10 | /// 11 | /// If validation failed, the value set will not be included in the index 12 | /// 13 | Failed, 14 | 15 | /// 16 | /// If validation passed but the value set was filtered 17 | /// 18 | Filtered 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Examine.Host/AspNetCoreApplicationIdentifier.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Lucene.Directories; 3 | using Microsoft.AspNetCore.DataProtection; 4 | 5 | namespace Examine 6 | { 7 | 8 | public class AspNetCoreApplicationIdentifier : IApplicationIdentifier 9 | { 10 | private readonly IServiceProvider _services; 11 | public AspNetCoreApplicationIdentifier(IServiceProvider services) => _services = services; 12 | public string GetApplicationUniqueIdentifier() => _services.GetApplicationUniqueIdentifier(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Examine.Host/CurrentEnvironmentApplicationRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Examine 5 | { 6 | public class CurrentEnvironmentApplicationRoot : IApplicationRoot 7 | { 8 | public DirectoryInfo ApplicationRoot { get; } = new DirectoryInfo(Path.Combine(Environment.CurrentDirectory, "Examine")); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Examine.Host/Examine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | A Lucene.Net search and indexing implementation for Examine 5 | examine lucene lucene.net lucenenet search index 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | all 16 | runtime; build; native; contentfiles; analyzers; buildtransitive 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Examine.Host/IApplicationRoot.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Examine 4 | { 5 | public interface IApplicationRoot 6 | { 7 | DirectoryInfo ApplicationRoot { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Examine.Host/PublicAPI.Unshipped.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Host/PublicAPI.Unshipped.txt -------------------------------------------------------------------------------- /src/Examine.Lucene/Analyzers/CultureInvariantStandardAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Lucene.Net.Analysis; 4 | using Lucene.Net.Analysis.Core; 5 | using Lucene.Net.Analysis.Miscellaneous; 6 | using Lucene.Net.Analysis.Standard; 7 | using Lucene.Net.Analysis.Util; 8 | using Lucene.Net.Util; 9 | 10 | namespace Examine.Lucene.Analyzers 11 | { 12 | /// 13 | /// The same as the but with an additional 14 | /// 15 | public sealed class CultureInvariantStandardAnalyzer : Analyzer 16 | { 17 | private readonly CharArraySet _stopWordsSet; 18 | private readonly bool _caseInsensitive; 19 | private readonly bool _ignoreLanguageAccents; 20 | 21 | public CultureInvariantStandardAnalyzer(CharArraySet stopWords) 22 | : this(stopWords, true, true) 23 | { 24 | 25 | } 26 | 27 | public CultureInvariantStandardAnalyzer() 28 | : this(StandardAnalyzer.STOP_WORDS_SET) 29 | { 30 | } 31 | 32 | public CultureInvariantStandardAnalyzer(CharArraySet stopWords, bool caseInsensitive, bool ignoreLanguageAccents) 33 | { 34 | _stopWordsSet = stopWords; 35 | _caseInsensitive = caseInsensitive; 36 | _ignoreLanguageAccents = ignoreLanguageAccents; 37 | } 38 | 39 | protected override TokenStreamComponents CreateComponents( 40 | string fieldName, 41 | TextReader reader) 42 | { 43 | var tokenizer = new StandardTokenizer(LuceneInfo.CurrentVersion, reader) 44 | { 45 | MaxTokenLength = MaxTokenLength 46 | }; 47 | 48 | TokenStream result = new StandardFilter(LuceneInfo.CurrentVersion, tokenizer); 49 | 50 | if (_caseInsensitive) 51 | { 52 | result = new LowerCaseFilter(LuceneInfo.CurrentVersion, result); 53 | } 54 | 55 | if (_ignoreLanguageAccents) 56 | { 57 | result = new ASCIIFoldingFilter(result ?? tokenizer); 58 | } 59 | 60 | result = new StopFilter(LuceneInfo.CurrentVersion, result, _stopWordsSet); 61 | 62 | return new TokenStreamComponents(tokenizer, result); 63 | } 64 | 65 | public int MaxTokenLength { set; get; } = byte.MaxValue; 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Analyzers/CultureInvariantWhitespaceAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using J2N; 3 | using Lucene.Net.Analysis; 4 | using Lucene.Net.Analysis.Core; 5 | using Lucene.Net.Analysis.Miscellaneous; 6 | using Lucene.Net.Analysis.Util; 7 | 8 | namespace Examine.Lucene.Analyzers 9 | { 10 | /// 11 | /// A whitespace analyzer that can be configured to be culture invariant 12 | /// 13 | /// 14 | /// Includes a LetterOrDigitTokenizer which only includes letters or digits along with 15 | /// and 16 | /// 17 | public sealed class CultureInvariantWhitespaceAnalyzer : Analyzer 18 | { 19 | private readonly bool _caseInsensitive; 20 | private readonly bool _ignoreLanguageAccents; 21 | 22 | public CultureInvariantWhitespaceAnalyzer() : this(true, true) 23 | { 24 | } 25 | 26 | public CultureInvariantWhitespaceAnalyzer(bool caseInsensitive, bool ignoreLanguageAccents) 27 | { 28 | _caseInsensitive = caseInsensitive; 29 | _ignoreLanguageAccents = ignoreLanguageAccents; 30 | } 31 | 32 | protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader) 33 | { 34 | Tokenizer tokenizer = new LetterOrDigitTokenizer(reader); 35 | 36 | TokenStream result = null; 37 | 38 | if (_caseInsensitive) 39 | { 40 | result = new LowerCaseFilter(LuceneInfo.CurrentVersion, tokenizer); 41 | } 42 | 43 | if (_ignoreLanguageAccents) 44 | { 45 | result = new ASCIIFoldingFilter(result ?? tokenizer); 46 | } 47 | 48 | return new TokenStreamComponents(tokenizer, result); 49 | } 50 | 51 | private sealed class LetterOrDigitTokenizer : CharTokenizer 52 | { 53 | public LetterOrDigitTokenizer(TextReader tr) 54 | : base(LuceneInfo.CurrentVersion, tr) 55 | { 56 | } 57 | 58 | protected override bool IsTokenChar(int c) => Character.IsLetter(c) || IsNumber(c); 59 | 60 | private bool IsNumber(int c) 61 | { 62 | var include = char.IsLetterOrDigit((char)c); 63 | return include; 64 | } 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Analyzers/EmailAddressAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Lucene.Net.Analysis; 3 | using Lucene.Net.Analysis.Core; 4 | using Lucene.Net.Analysis.Util; 5 | 6 | namespace Examine.Lucene.Analyzers 7 | { 8 | /// 9 | /// Used for email addresses 10 | /// 11 | public class EmailAddressAnalyzer : Analyzer 12 | { 13 | protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader) 14 | { 15 | Tokenizer tokenizer = new EmailAddressTokenizer(reader); 16 | 17 | //case insensitive 18 | TokenStream result = new LowerCaseFilter(LuceneInfo.CurrentVersion, tokenizer); 19 | 20 | return new TokenStreamComponents(tokenizer, result); 21 | } 22 | 23 | /// 24 | /// Used for email addresses 25 | /// 26 | private sealed class EmailAddressTokenizer : CharTokenizer 27 | { 28 | public EmailAddressTokenizer(TextReader tr) 29 | : base(LuceneInfo.CurrentVersion, tr) 30 | { 31 | } 32 | 33 | protected override bool IsTokenChar(int c) 34 | { 35 | var asChar = (char)c; 36 | 37 | // Make whitespace characters and the @ symbol be indicators of new words. 38 | return !(char.IsWhiteSpace(asChar) || asChar == '@'); 39 | } 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Analyzers/PatternAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text.RegularExpressions; 3 | using Lucene.Net.Analysis; 4 | using Lucene.Net.Analysis.Core; 5 | using Lucene.Net.Analysis.Pattern; 6 | using Lucene.Net.Analysis.Util; 7 | 8 | namespace Examine.Lucene.Analyzers 9 | { 10 | /// 11 | /// Analyzer that uses regex to parse out tokens 12 | /// 13 | public class PatternAnalyzer : Analyzer 14 | { 15 | private readonly int _regexGroup; 16 | private readonly bool _lowercase; 17 | private readonly CharArraySet _stopWords; 18 | private readonly Regex _pattern; 19 | 20 | /// 21 | /// Creates a new 22 | /// 23 | /// The regex pattern 24 | /// The regex group number to match. -1 to use as a split. 25 | /// Whether to lower case the tokens 26 | /// Any stop words that should be included 27 | public PatternAnalyzer(string format, int regexGroup, bool lowercase = false, CharArraySet stopWords = null) 28 | { 29 | _regexGroup = regexGroup; 30 | _lowercase = lowercase; 31 | _stopWords = stopWords; 32 | _pattern = new Regex(format); 33 | } 34 | 35 | protected override TokenStreamComponents CreateComponents(string fieldName, TextReader reader) 36 | { 37 | Tokenizer tokenizer = new PatternTokenizer(reader, _pattern, _regexGroup); 38 | TokenStream stream = tokenizer; 39 | 40 | if (_lowercase) 41 | { 42 | stream = new LowerCaseFilter(LuceneInfo.CurrentVersion, stream); 43 | } 44 | 45 | if (_stopWords != null) 46 | { 47 | stream = new StopFilter(LuceneInfo.CurrentVersion, stream, _stopWords); 48 | } 49 | 50 | return new TokenStreamComponents(tokenizer, stream); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Examine.Lucene/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/Examine.Lucene/DelegateFieldValueTypeFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Lucene.Indexing; 3 | 4 | namespace Examine.Lucene 5 | { 6 | /// 7 | /// 8 | /// A factory to create a for a field name based on a Func delegate 9 | /// 10 | public class DelegateFieldValueTypeFactory : IFieldValueTypeFactory 11 | { 12 | private readonly Func _factory; 13 | 14 | public DelegateFieldValueTypeFactory(Func factory) => _factory = factory; 15 | 16 | public IIndexFieldValueType Create(string fieldName) => _factory(fieldName); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/DefaultLockFactory.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Lucene.Net.Store; 3 | 4 | namespace Examine.Lucene.Directories 5 | { 6 | public class DefaultLockFactory : ILockFactory 7 | { 8 | public LockFactory GetLockFactory(DirectoryInfo directory) 9 | { 10 | var nativeFsLockFactory = new NativeFSLockFactory(directory) 11 | { 12 | LockPrefix = null 13 | }; 14 | return nativeFsLockFactory; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/DirectoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Lucene.Providers; 3 | using Lucene.Net.Index; 4 | using Directory = Lucene.Net.Store.Directory; 5 | 6 | namespace Examine.Lucene.Directories 7 | { 8 | public class GenericDirectoryFactory : DirectoryFactoryBase 9 | { 10 | private readonly Func _factory; 11 | 12 | public GenericDirectoryFactory(Func factory) 13 | { 14 | _factory = factory; 15 | } 16 | 17 | internal GenericDirectoryFactory(Func factory, bool externallyManaged) 18 | { 19 | _factory = factory; 20 | ExternallyManaged = externallyManaged; 21 | } 22 | 23 | /// 24 | /// When set to true, indicates that the directory is managed externally and will be disposed of by the caller, not the index. 25 | /// 26 | internal bool ExternallyManaged { get; } 27 | 28 | protected override Directory CreateDirectory(LuceneIndex luceneIndex, bool forceUnlock) 29 | { 30 | var dir = _factory(luceneIndex.Name); 31 | if (forceUnlock) 32 | { 33 | IndexWriter.Unlock(dir); 34 | } 35 | return dir; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/DirectoryFactoryBase.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using Examine.Lucene.Providers; 3 | using Directory = Lucene.Net.Store.Directory; 4 | 5 | namespace Examine.Lucene.Directories 6 | { 7 | public abstract class DirectoryFactoryBase : IDirectoryFactory 8 | { 9 | Directory IDirectoryFactory.CreateDirectory(LuceneIndex luceneIndex, bool forceUnlock) 10 | => CreateDirectory(luceneIndex, forceUnlock); 11 | 12 | protected abstract Directory CreateDirectory(LuceneIndex luceneIndex, bool forceUnlock); 13 | 14 | protected virtual void Dispose(bool disposing) 15 | { 16 | } 17 | 18 | public void Dispose() => 19 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 20 | Dispose(disposing: true); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/FakeLuceneDirectoryIndexOptionsOptionsMonitor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Options; 3 | 4 | namespace Examine.Lucene.Directories 5 | { 6 | internal sealed class FakeLuceneDirectoryIndexOptionsOptionsMonitor : IOptionsMonitor 7 | { 8 | private static readonly LuceneDirectoryIndexOptions s_default = new LuceneDirectoryIndexOptions(); 9 | 10 | public LuceneDirectoryIndexOptions CurrentValue => s_default; 11 | 12 | public LuceneDirectoryIndexOptions Get(string name) => s_default; 13 | 14 | public IDisposable OnChange(Action listener) => throw new NotImplementedException(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/FileSystemDirectoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Examine.Lucene.Providers; 4 | using Lucene.Net.Index; 5 | using Lucene.Net.Store; 6 | using Microsoft.Extensions.Options; 7 | using Directory = Lucene.Net.Store.Directory; 8 | 9 | namespace Examine.Lucene.Directories 10 | { 11 | public class FileSystemDirectoryFactory : DirectoryFactoryBase 12 | { 13 | private readonly DirectoryInfo _baseDir; 14 | 15 | [Obsolete("Use ctor with all dependencies")] 16 | public FileSystemDirectoryFactory( 17 | DirectoryInfo baseDir, 18 | ILockFactory lockFactory) 19 | : this (baseDir, lockFactory, new FakeLuceneDirectoryIndexOptionsOptionsMonitor()) 20 | { 21 | } 22 | 23 | public FileSystemDirectoryFactory( 24 | DirectoryInfo baseDir, 25 | ILockFactory lockFactory, 26 | IOptionsMonitor indexOptions) 27 | { 28 | _baseDir = baseDir; 29 | LockFactory = lockFactory; 30 | IndexOptions = indexOptions; 31 | } 32 | 33 | public ILockFactory LockFactory { get; } 34 | 35 | protected IOptionsMonitor IndexOptions { get; } 36 | 37 | protected override Directory CreateDirectory(LuceneIndex luceneIndex, bool forceUnlock) 38 | { 39 | var path = Path.Combine(_baseDir.FullName, luceneIndex.Name); 40 | var luceneIndexFolder = new DirectoryInfo(path); 41 | 42 | var dir = FSDirectory.Open(luceneIndexFolder, LockFactory.GetLockFactory(luceneIndexFolder)); 43 | if (forceUnlock) 44 | { 45 | IndexWriter.Unlock(dir); 46 | } 47 | 48 | var options = IndexOptions.GetNamedOptions(luceneIndex.Name); 49 | if (options.NrtEnabled) 50 | { 51 | return new NRTCachingDirectory(dir, options.NrtCacheMaxMergeSizeMB, options.NrtCacheMaxCachedMB); 52 | } 53 | else 54 | { 55 | return dir; 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/IApplicationIdentifier.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Lucene.Directories 2 | { 3 | public interface IApplicationIdentifier 4 | { 5 | string GetApplicationUniqueIdentifier(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/IDirectoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Lucene.Providers; 3 | using Directory = Lucene.Net.Store.Directory; 4 | 5 | namespace Examine.Lucene.Directories 6 | { 7 | /// 8 | /// Creates a Lucene for an index 9 | /// 10 | /// 11 | /// The directory created must only be created ONCE per index and disposed when the index is disposed. 12 | /// 13 | public interface IDirectoryFactory : IDisposable 14 | { 15 | /// 16 | /// Creates the directory instance 17 | /// 18 | /// 19 | /// If true, will force unlock the directory when created 20 | /// 21 | /// 22 | /// Any subsequent calls for the same index will return the same directory instance 23 | /// 24 | Directory CreateDirectory(LuceneIndex luceneIndex, bool forceUnlock); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/ILockFactory.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Lucene.Net.Store; 3 | 4 | namespace Examine.Lucene.Directories 5 | { 6 | public interface ILockFactory 7 | { 8 | LockFactory GetLockFactory(DirectoryInfo directory); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/MultiIndexLock.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security; 3 | using Lucene.Net.Store; 4 | 5 | namespace Examine.Lucene.Directories 6 | { 7 | /// 8 | /// Lock that wraps multiple locks 9 | /// 10 | 11 | internal class MultiIndexLock : Lock 12 | { 13 | private readonly Lock _dirMaster; 14 | private readonly Lock _dirChild; 15 | private bool _isDisposed = false; 16 | 17 | 18 | public MultiIndexLock(Lock dirMaster, Lock dirChild) 19 | { 20 | _dirMaster = dirMaster; 21 | _dirChild = dirChild; 22 | } 23 | 24 | /// 25 | /// Attempts to obtain exclusive access and immediately return 26 | /// upon success or failure. 27 | /// 28 | /// 29 | /// true iff exclusive access is obtained 30 | /// 31 | 32 | public override bool Obtain() 33 | { 34 | var master = _dirMaster.Obtain(); 35 | if (!master) return false; 36 | var child = _dirChild.Obtain(); 37 | return child; 38 | } 39 | 40 | 41 | protected override void Dispose(bool disposing) 42 | { 43 | if (disposing) 44 | { 45 | if (!_isDisposed) 46 | { 47 | var isChild = false; 48 | try 49 | { 50 | //try to release master 51 | _dirMaster.Dispose(); 52 | 53 | //if that succeeds try to release child 54 | isChild = true; 55 | _dirChild.Dispose(); 56 | } 57 | catch (System.Exception) 58 | { 59 | //if an error occurs above for the master still attempt to release child 60 | if (!isChild) 61 | _dirChild.Dispose(); 62 | 63 | throw; 64 | } 65 | 66 | // this.Dispose(true); 67 | } 68 | } 69 | } 70 | 71 | /// 72 | /// Returns true if the resource is currently locked. Note that one must 73 | /// still call before using the resource. 74 | /// 75 | 76 | public override bool IsLocked() 77 | { 78 | return _dirMaster.IsLocked() || _dirChild.IsLocked(); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/MultiIndexLockFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security; 3 | using Lucene.Net.Store; 4 | 5 | namespace Examine.Lucene.Directories 6 | { 7 | /// 8 | /// Lock factory that wraps multiple factories 9 | /// 10 | 11 | public class MultiIndexLockFactory : LockFactory 12 | { 13 | private readonly LockFactory _master; 14 | private readonly LockFactory _child; 15 | 16 | private static readonly object Locker = new object(); 17 | 18 | public MultiIndexLockFactory(Directory master, Directory child) 19 | { 20 | if (master == null) throw new ArgumentNullException("master"); 21 | if (child == null) throw new ArgumentNullException("child"); 22 | _master = master.LockFactory; 23 | _child = child.LockFactory; 24 | } 25 | 26 | public MultiIndexLockFactory(LockFactory master, LockFactory child) 27 | { 28 | if (master == null) throw new ArgumentNullException("master"); 29 | if (child == null) throw new ArgumentNullException("child"); 30 | lock (Locker) 31 | { 32 | _master = master; 33 | _child = child; 34 | } 35 | } 36 | 37 | 38 | public override Lock MakeLock(string lockName) 39 | { 40 | lock (Locker) 41 | { 42 | return new MultiIndexLock(_master.MakeLock(lockName), _child.MakeLock(lockName)); 43 | } 44 | } 45 | 46 | 47 | public override void ClearLock(string lockName) 48 | { 49 | lock (Locker) 50 | { 51 | var isChild = false; 52 | try 53 | { 54 | //try to release master 55 | _master.ClearLock(lockName); 56 | 57 | //if that succeeds try to release child 58 | isChild = true; 59 | _child.ClearLock(lockName); 60 | } 61 | catch (Exception) 62 | { 63 | //if an error occurs above for the master still attempt to release child 64 | if (!isChild) 65 | _child.ClearLock(lockName); 66 | 67 | throw; 68 | } 69 | 70 | } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/SyncedFileSystemDirectory.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Examine.Lucene.Providers; 3 | using Lucene.Net.Store; 4 | using Microsoft.Extensions.Logging; 5 | using Directory = Lucene.Net.Store.Directory; 6 | 7 | namespace Examine.Lucene.Directories 8 | { 9 | internal class SyncedFileSystemDirectory : FilterDirectory 10 | { 11 | private readonly ExamineReplicator _replicator; 12 | 13 | public SyncedFileSystemDirectory( 14 | ILogger replicatorLogger, 15 | ILogger clientLogger, 16 | Directory localLuceneDirectory, 17 | Directory mainLuceneDirectory, 18 | LuceneIndex luceneIndex, 19 | DirectoryInfo tempDir) 20 | : base(localLuceneDirectory) 21 | { 22 | // now create the replicator that will copy from local to main on schedule 23 | _replicator = new ExamineReplicator(replicatorLogger, clientLogger, luceneIndex, localLuceneDirectory, mainLuceneDirectory, tempDir); 24 | LocalLuceneDirectory = localLuceneDirectory; 25 | MainLuceneDirectory = mainLuceneDirectory; 26 | } 27 | 28 | internal Directory LocalLuceneDirectory { get; } 29 | 30 | internal Directory MainLuceneDirectory { get; } 31 | 32 | public override Lock MakeLock(string name) 33 | { 34 | // Start replicating back to main, this is ok to call multiple times, it will only execute once. 35 | _replicator.StartIndexReplicationOnSchedule(1000); 36 | 37 | return base.MakeLock(name); 38 | } 39 | 40 | protected override void Dispose(bool disposing) 41 | { 42 | _replicator.Dispose(); 43 | MainLuceneDirectory.Dispose(); 44 | base.Dispose(disposing); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Directories/TempEnvFileSystemDirectoryFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Microsoft.Extensions.Options; 4 | 5 | namespace Examine.Lucene.Directories 6 | { 7 | 8 | /// 9 | /// A directory factory used to create an instance of FSDirectory that uses the current %temp% environment variable 10 | /// 11 | /// 12 | /// This works well for Azure Web Apps directory sync 13 | /// 14 | public class TempEnvFileSystemDirectoryFactory : FileSystemDirectoryFactory 15 | { 16 | [Obsolete("Use ctor with all dependencies")] 17 | public TempEnvFileSystemDirectoryFactory( 18 | IApplicationIdentifier applicationIdentifier, 19 | ILockFactory lockFactory) 20 | : this(applicationIdentifier, lockFactory, new FakeLuceneDirectoryIndexOptionsOptionsMonitor()) 21 | { 22 | } 23 | 24 | public TempEnvFileSystemDirectoryFactory( 25 | IApplicationIdentifier applicationIdentifier, 26 | ILockFactory lockFactory, 27 | IOptionsMonitor indexOptions) 28 | : base(new DirectoryInfo(GetTempPath(applicationIdentifier)), lockFactory, indexOptions) 29 | { 30 | } 31 | 32 | public static string GetTempPath(IApplicationIdentifier applicationIdentifier) 33 | { 34 | var appDomainHash = applicationIdentifier.GetApplicationUniqueIdentifier().GenerateHash(); 35 | 36 | var cachePath = Path.Combine( 37 | Path.GetTempPath(), 38 | "ExamineIndexes", 39 | //include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back 40 | // to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not 41 | // utilizing an old index 42 | appDomainHash); 43 | 44 | return cachePath; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Examine.Lucene/DocumentWritingEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security; 5 | using System.Text; 6 | using System.ComponentModel; 7 | using Lucene.Net.Documents; 8 | using Examine; 9 | 10 | namespace Examine.Lucene 11 | { 12 | /// 13 | /// Event arguments for a Document Writing event 14 | /// 15 | public class DocumentWritingEventArgs : CancelEventArgs 16 | { 17 | /// 18 | /// Lucene.NET Document, including all previously added fields 19 | /// 20 | public Document Document { get; } 21 | 22 | /// 23 | /// Fields of the indexer 24 | /// 25 | public ValueSet ValueSet { get; } 26 | 27 | 28 | /// 29 | /// 30 | /// 31 | /// 32 | /// 33 | public DocumentWritingEventArgs(ValueSet valueSet, Document d) 34 | { 35 | this.Document = d; 36 | this.ValueSet = valueSet; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Examine.Lucene.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | A Lucene.Net search and indexing implementation for Examine 6 | examine lucene lucene.net lucenenet search index 7 | 8 | 9 | 10 | 11 | 12 | 13 | <_Parameter1>Examine.Test 14 | 15 | 16 | <_Parameter1>Examine.Benchmarks 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 4.8.0-beta00017 25 | 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 4.3.0 33 | 34 | 35 | 8.0.0 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Examine.csproj.vspscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" 10 | } 11 | -------------------------------------------------------------------------------- /src/Examine.Lucene/IFieldValueTypeFactory.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene.Indexing; 2 | 3 | namespace Examine.Lucene 4 | { 5 | /// 6 | /// A factory to create a for a field name 7 | /// 8 | public interface IFieldValueTypeFactory 9 | { 10 | IIndexFieldValueType Create(string fieldName); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/DateTimeType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Lucene.Providers; 3 | using Lucene.Net.Documents; 4 | using Lucene.Net.Search; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Examine.Lucene.Indexing 8 | { 9 | 10 | public class DateTimeType : IndexFieldRangeValueType 11 | { 12 | public DateResolution Resolution { get; } 13 | 14 | /// 15 | /// Can be sorted by the normal field name 16 | /// 17 | public override string SortableFieldName => FieldName; 18 | 19 | public DateTimeType(string fieldName, ILoggerFactory logger, DateResolution resolution, bool store = true) 20 | : base(fieldName, logger, store) 21 | { 22 | Resolution = resolution; 23 | } 24 | 25 | protected override void AddSingleValue(Document doc, object value) 26 | { 27 | if (!TryConvert(value, out DateTime parsedVal)) 28 | return; 29 | 30 | var val = DateToLong(parsedVal); 31 | 32 | doc.Add(new Int64Field(FieldName,val, Store ? Field.Store.YES : Field.Store.NO));; 33 | } 34 | 35 | /// 36 | /// Returns the ticks to be indexed, then use NumericRangeQuery to query against it 37 | /// 38 | /// 39 | /// 40 | protected long DateToLong(DateTime date) 41 | { 42 | return DateTools.Round(date, Resolution).Ticks; 43 | } 44 | 45 | public override Query GetQuery(string query) 46 | { 47 | if (!TryConvert(query, out DateTime parsedVal)) 48 | return null; 49 | 50 | return GetQuery(parsedVal, parsedVal); 51 | } 52 | 53 | public override Query GetQuery(DateTime? lower, DateTime? upper, bool lowerInclusive = true, bool upperInclusive = true) 54 | { 55 | return NumericRangeQuery.NewInt64Range(FieldName, 56 | lower != null ? DateToLong(lower.Value) : (long?)null, 57 | upper != null ? DateToLong(upper.Value) : (long?)null, lowerInclusive, upperInclusive); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/DoubleType.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene.Providers; 2 | using Lucene.Net.Documents; 3 | using Lucene.Net.Search; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Examine.Lucene.Indexing 7 | { 8 | public class DoubleType : IndexFieldRangeValueType 9 | { 10 | public DoubleType(string fieldName, ILoggerFactory logger, bool store= true) 11 | : base(fieldName, logger, store) 12 | { 13 | } 14 | 15 | /// 16 | /// Can be sorted by the normal field name 17 | /// 18 | public override string SortableFieldName => FieldName; 19 | 20 | protected override void AddSingleValue(Document doc, object value) 21 | { 22 | if (!TryConvert(value, out double parsedVal)) 23 | return; 24 | 25 | doc.Add(new DoubleField(FieldName,parsedVal, Store ? Field.Store.YES : Field.Store.NO)); 26 | } 27 | 28 | public override Query GetQuery(string query) 29 | { 30 | return !TryConvert(query, out double parsedVal) ? null : GetQuery(parsedVal, parsedVal); 31 | } 32 | 33 | public override Query GetQuery(double? lower, double? upper, bool lowerInclusive = true, bool upperInclusive = true) 34 | { 35 | return NumericRangeQuery.NewDoubleRange(FieldName, 36 | lower ?? double.MinValue, 37 | upper ?? double.MaxValue, lowerInclusive, upperInclusive); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/GenericAnalyzerFieldValueType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Lucene.Providers; 3 | using Lucene.Net.Analysis; 4 | using Lucene.Net.Analysis.Miscellaneous; 5 | using Lucene.Net.Documents; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace Examine.Lucene.Indexing 9 | { 10 | /// 11 | /// A generic value type that will index a value based on the analyzer provider and will store the value with normal term vectors 12 | /// 13 | public class GenericAnalyzerFieldValueType : IndexFieldValueTypeBase 14 | { 15 | private readonly Analyzer _analyzer; 16 | private readonly bool _sortable; 17 | 18 | public GenericAnalyzerFieldValueType(string fieldName, ILoggerFactory logger, Analyzer analyzer, bool sortable = false) 19 | : base(fieldName, logger, true) 20 | { 21 | _analyzer = analyzer ?? throw new ArgumentNullException(nameof(analyzer)); 22 | _sortable = sortable; 23 | } 24 | 25 | /// 26 | /// Can be sorted by a concatenated field name since to be sortable it cannot be analyzed 27 | /// 28 | public override string SortableFieldName => _sortable ? ExamineFieldNames.SortedFieldNamePrefix + FieldName : null; 29 | 30 | public override Analyzer Analyzer => _analyzer; 31 | 32 | protected override void AddSingleValue(Document doc, object value) 33 | { 34 | if (TryConvert(value, out var str)) 35 | { 36 | doc.Add(new TextField(FieldName, str, Field.Store.YES)); 37 | 38 | if (_sortable) 39 | { 40 | //to be sortable it cannot be analyzed so we have to make a different field 41 | // TODO: Investigate https://lucene.apache.org/core/4_3_0/core/org/apache/lucene/document/SortedDocValuesField.html 42 | doc.Add(new StringField( 43 | ExamineFieldNames.SortedFieldNamePrefix + FieldName, 44 | str, 45 | Field.Store.YES)); 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/IIndexFieldValueType.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Analysis; 2 | using Lucene.Net.Analysis.Miscellaneous; 3 | using Lucene.Net.Documents; 4 | using Lucene.Net.Search; 5 | 6 | namespace Examine.Lucene.Indexing 7 | { 8 | /// 9 | /// Defines how a field value is stored in the index and is responsible for generating a query for the field when a managed query is used 10 | /// 11 | public interface IIndexFieldValueType 12 | { 13 | string FieldName { get; } 14 | 15 | /// 16 | /// Returns the sortable field name or null if the value isn't sortable 17 | /// 18 | string SortableFieldName { get; } 19 | 20 | bool Store { get; } 21 | 22 | /// 23 | /// Returns the analyzer for this field type, or null to use the default 24 | /// 25 | Analyzer Analyzer { get; } 26 | 27 | void AddValue(Document doc, object value); 28 | 29 | Query GetQuery(string query); 30 | 31 | //IHighlighter GetHighlighter(Query query, Searcher searcher, FacetsLoader facetsLoader); 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/IIndexRangeValueType.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Search; 2 | 3 | namespace Examine.Lucene.Indexing 4 | { 5 | /// 6 | /// Used for value range types when the type requires parsing from string 7 | /// 8 | public interface IIndexRangeValueType 9 | { 10 | Query GetQuery(string lower, string upper, bool lowerInclusive = true, bool upperInclusive = true); 11 | } 12 | 13 | /// 14 | /// Used for value range types when the type is known 15 | /// 16 | /// 17 | public interface IIndexRangeValueType where T : struct 18 | { 19 | Query GetQuery(T? lower, T? upper, bool lowerInclusive = true, bool upperInclusive = true); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/IndexFieldRangeValueType.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Search; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Examine.Lucene.Indexing 5 | { 6 | public abstract class IndexFieldRangeValueType : IndexFieldValueTypeBase, IIndexRangeValueType, IIndexRangeValueType 7 | where T : struct 8 | { 9 | protected IndexFieldRangeValueType(string fieldName, ILoggerFactory logger, bool store = true) : base(fieldName, logger, store) 10 | { 11 | } 12 | 13 | #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters 14 | public abstract Query GetQuery(T? lower, T? upper, bool lowerInclusive = true, bool upperInclusive = true); 15 | #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters 16 | 17 | #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters 18 | public Query GetQuery(string lower, string upper, bool lowerInclusive = true, bool upperInclusive = true) 19 | #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters 20 | { 21 | var lowerParsed = TryConvert(lower, out var lowerValue); 22 | var upperParsed = TryConvert(upper, out var upperValue); 23 | 24 | return GetQuery(lowerParsed ? (T?)lowerValue : null, upperParsed ? (T?)upperValue : null, lowerInclusive, upperInclusive); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/Int32Type.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene.Providers; 2 | using Lucene.Net.Documents; 3 | using Lucene.Net.Search; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Examine.Lucene.Indexing 7 | { 8 | public class Int32Type : IndexFieldRangeValueType 9 | { 10 | public Int32Type(string fieldName, ILoggerFactory logger, bool store = true) 11 | : base(fieldName, logger, store) 12 | { 13 | } 14 | 15 | /// 16 | /// Can be sorted by the normal field name 17 | /// 18 | public override string SortableFieldName => FieldName; 19 | 20 | protected override void AddSingleValue(Document doc, object value) 21 | { 22 | if (!TryConvert(value, out int parsedVal)) 23 | return; 24 | 25 | // TODO: We can use this for better scoring/sorting performance 26 | // https://stackoverflow.com/a/44953624/694494 27 | // https://lucene.apache.org/core/7_4_0/core/org/apache/lucene/document/NumericDocValuesField.html 28 | //var dvField = new NumericDocValuesField(_docValuesFieldName, 0); 29 | //dvField.SetInt32Value(parsedVal); 30 | //doc.Add(dvField); 31 | 32 | doc.Add(new Int32Field(FieldName, parsedVal, Store ? Field.Store.YES : Field.Store.NO)); 33 | } 34 | 35 | public override Query GetQuery(string query) 36 | { 37 | return !TryConvert(query, out int parsedVal) ? null : GetQuery(parsedVal, parsedVal); 38 | } 39 | 40 | public override Query GetQuery(int? lower, int? upper, bool lowerInclusive = true, bool upperInclusive = true) 41 | { 42 | return NumericRangeQuery.NewInt32Range(FieldName, 43 | lower, 44 | upper, lowerInclusive, upperInclusive); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/Int64Type.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene.Providers; 2 | using Lucene.Net.Documents; 3 | using Lucene.Net.Search; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Examine.Lucene.Indexing 7 | { 8 | public class Int64Type : IndexFieldRangeValueType 9 | { 10 | public Int64Type(string fieldName, ILoggerFactory logger, bool store = true) 11 | : base(fieldName, logger, store) 12 | { 13 | } 14 | 15 | /// 16 | /// Can be sorted by the normal field name 17 | /// 18 | public override string SortableFieldName => FieldName; 19 | 20 | protected override void AddSingleValue(Document doc, object value) 21 | { 22 | if (!TryConvert(value, out long parsedVal)) 23 | return; 24 | 25 | doc.Add(new Int64Field(FieldName,parsedVal, Store ? Field.Store.YES : Field.Store.NO));; 26 | } 27 | 28 | public override Query GetQuery(string query) 29 | { 30 | return !TryConvert(query, out long parsedVal) ? null : GetQuery(parsedVal, parsedVal); 31 | } 32 | 33 | public override Query GetQuery(long? lower, long? upper, bool lowerInclusive = true, bool upperInclusive = true) 34 | { 35 | return NumericRangeQuery.NewInt64Range(FieldName, 36 | lower, 37 | upper, lowerInclusive, upperInclusive); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/RawStringType.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Analysis; 2 | using Lucene.Net.Analysis.Core; 3 | using Lucene.Net.Documents; 4 | using Lucene.Net.Index; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Examine.Lucene.Indexing 8 | { 9 | 10 | /// 11 | /// Indexes a raw string value - not analyzed 12 | /// 13 | public class RawStringType : IndexFieldValueTypeBase 14 | { 15 | private readonly Analyzer _analyzer; 16 | 17 | /// 18 | /// Constructor 19 | /// 20 | /// 21 | /// 22 | public RawStringType(string fieldName, ILoggerFactory logger, bool store = true) 23 | : base(fieldName, logger, store) 24 | => _analyzer = new KeywordAnalyzer(); 25 | 26 | public override Analyzer Analyzer => _analyzer; 27 | 28 | protected override void AddSingleValue(Document doc, object value) 29 | { 30 | switch (value) 31 | { 32 | case IIndexableField f: 33 | // https://lucene.apache.org/core/4_3_0/core/org/apache/lucene/index/IndexableField.html 34 | // BinaryDocValuesField, ByteDocValuesField, DerefBytesDocValuesField, DoubleDocValuesField, DoubleField, 35 | // Field, FloatDocValuesField, FloatField, IntDocValuesField, IntField, LongDocValuesField, LongField, 36 | // NumericDocValuesField, PackedLongDocValuesField, ShortDocValuesField, SortedBytesDocValuesField, 37 | // SortedDocValuesField, SortedSetDocValuesField, StoredField, StraightBytesDocValuesField, StringField, TextField 38 | // https://solr.apache.org/guide/6_6/docvalues.html 39 | doc.Add(f); 40 | break; 41 | case TokenStream ts: 42 | doc.Add(new TextField(FieldName, ts)); 43 | break; 44 | default: 45 | if (TryConvert(value, out var str)) 46 | { 47 | doc.Add(new StringField( 48 | FieldName, 49 | str, 50 | Store ? Field.Store.YES : Field.Store.NO)); 51 | } 52 | break; 53 | } 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Indexing/SingleType.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene.Providers; 2 | using Lucene.Net.Documents; 3 | using Lucene.Net.Search; 4 | using Microsoft.Extensions.Logging; 5 | 6 | namespace Examine.Lucene.Indexing 7 | { 8 | public class SingleType : IndexFieldRangeValueType 9 | { 10 | public SingleType(string fieldName, ILoggerFactory logger, bool store = true) 11 | : base(fieldName, logger, store) 12 | { 13 | } 14 | 15 | /// 16 | /// Can be sorted by the normal field name 17 | /// 18 | public override string SortableFieldName => FieldName; 19 | 20 | protected override void AddSingleValue(Document doc, object value) 21 | { 22 | if (!TryConvert(value, out float parsedVal)) 23 | return; 24 | 25 | doc.Add(new DoubleField(FieldName,parsedVal, Store ? Field.Store.YES : Field.Store.NO)); 26 | } 27 | 28 | public override Query GetQuery(string query) 29 | { 30 | return !TryConvert(query, out float parsedVal) ? null : GetQuery(parsedVal, parsedVal); 31 | } 32 | 33 | public override Query GetQuery(float? lower, float? upper, bool lowerInclusive = true, bool upperInclusive = true) 34 | { 35 | return NumericRangeQuery.NewDoubleRange(FieldName, 36 | lower ?? float.MinValue, 37 | upper ?? float.MaxValue, lowerInclusive, upperInclusive); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Examine.Lucene/LoggingInfoStream.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Util; 2 | using Microsoft.Extensions.Logging; 3 | 4 | namespace Examine.Lucene 5 | { 6 | internal class LoggingInfoStream : InfoStream 7 | { 8 | private readonly LogLevel _logLevel; 9 | 10 | public LoggingInfoStream(ILogger logger, LogLevel logLevel) 11 | { 12 | Logger = logger; 13 | _logLevel = logLevel; 14 | } 15 | 16 | public ILogger Logger { get; } 17 | 18 | public override bool IsEnabled(string component) => Logger.IsEnabled(_logLevel); 19 | 20 | public override void Message(string component, string message) 21 | { 22 | if (Logger.IsEnabled(_logLevel)) 23 | { 24 | Logger.LogDebug("{Component} - {Message}", component, message); 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Examine.Lucene/LoggingReplicationClient.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Lucene.Net.Replicator; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Examine.Lucene 6 | { 7 | /// 8 | /// Custom replication client that logs 9 | /// 10 | public class LoggingReplicationClient : ReplicationClient 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public LoggingReplicationClient( 15 | ILogger logger, 16 | IReplicator replicator, 17 | IReplicationHandler handler, 18 | ISourceDirectoryFactory factory) 19 | : base(replicator, handler, factory) 20 | { 21 | _logger = logger; 22 | InfoStream = new CustomLoggingInfoStream(logger); 23 | } 24 | 25 | protected override void HandleUpdateException(Exception exception) 26 | => _logger.LogError(exception, "Index replication error occurred"); 27 | 28 | private class CustomLoggingInfoStream : LoggingInfoStream 29 | { 30 | public CustomLoggingInfoStream(ILogger logger) 31 | : base(logger, LogLevel.Debug) 32 | { 33 | } 34 | 35 | public override void Message(string component, string message) 36 | { 37 | if (Logger.IsEnabled(LogLevel.Debug)) 38 | { 39 | // don't log this, it means there is no session 40 | if (!message.EndsWith('=')) 41 | { 42 | base.Message(component, message); 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Examine.Lucene/LuceneDirectoryIndexOptions.cs: -------------------------------------------------------------------------------- 1 | 2 | using Examine.Lucene.Directories; 3 | using Lucene.Net.Store; 4 | 5 | namespace Examine.Lucene 6 | { 7 | public class LuceneDirectoryIndexOptions : LuceneIndexOptions 8 | { 9 | /// 10 | /// Returns the directory factory to use 11 | /// 12 | public IDirectoryFactory DirectoryFactory { get; set; } 13 | 14 | /// 15 | /// If true will force unlock the index on startup 16 | /// 17 | public bool UnlockIndex { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Examine.Lucene/LuceneExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security; 5 | using System.Text; 6 | using Lucene.Net.Search; 7 | using Lucene.Net.Store; 8 | using Lucene.Net.Index; 9 | 10 | namespace Examine.Lucene 11 | { 12 | /// 13 | /// Extension methods for Lucene 14 | /// 15 | public static class LuceneExtensions 16 | { 17 | /// 18 | /// Copies from IndexInput to IndexOutput 19 | /// 20 | /// 21 | /// 22 | /// 23 | /// 24 | /// From Another interesting project I found: 25 | /// http://www.compass-project.org/ 26 | /// which has some interesting bits like: 27 | /// https://github.com/kimchy/compass/blob/master/src/main/src/org/apache/lucene/index/LuceneUtils.java 28 | /// 29 | /// 30 | 31 | internal static void CopyTo(this IndexInput indexInput, IndexOutput indexOutput, string name) 32 | { 33 | var buffer = new byte[32768]; 34 | 35 | long length = indexInput.Length; 36 | long remainder = length; 37 | int chunk = buffer.Length; 38 | 39 | while (remainder > 0) 40 | { 41 | int len = (int)Math.Min(chunk, remainder); 42 | indexInput.ReadBytes(buffer, 0, len); 43 | indexOutput.WriteBytes(buffer, len); 44 | remainder -= len; 45 | } 46 | 47 | // Verify that remainder is 0 48 | if (remainder != 0) 49 | throw new InvalidOperationException( 50 | "Non-zero remainder length after copying [" + remainder 51 | + "] (id [" + name + "] length [" + length 52 | + "] buffer size [" + chunk + "])"); 53 | } 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Examine.Lucene/LuceneIndexOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Lucene.Net.Analysis; 3 | using Lucene.Net.Index; 4 | 5 | namespace Examine.Lucene 6 | { 7 | 8 | public class LuceneIndexOptions : IndexOptions 9 | { 10 | public bool NrtEnabled { get; set; } = true; 11 | 12 | public double NrtTargetMaxStaleSec { get; set; } = 60.0; 13 | 14 | public double NrtTargetMinStaleSec { get; set; } = 1.0; 15 | 16 | public double NrtCacheMaxMergeSizeMB { get; set; } = 5.0; 17 | 18 | public double NrtCacheMaxCachedMB { get; set; } = 60.0; 19 | 20 | public IndexDeletionPolicy IndexDeletionPolicy { get; set; } 21 | 22 | public Analyzer Analyzer { get; set; } 23 | 24 | /// 25 | /// Specifies the index value types to use for this indexer, if this is not specified then the result of will be used. 26 | /// This is generally used to initialize any custom value types for your indexer since the value type collection cannot be modified at runtime. 27 | /// 28 | public IReadOnlyDictionary IndexValueTypesFactory { get; set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Examine.Lucene/LuceneInfo.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Util; 2 | 3 | namespace Examine 4 | { 5 | public static class LuceneInfo 6 | { 7 | public static LuceneVersion CurrentVersion { get; } = LuceneVersion.LUCENE_48; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Providers/ErrorLoggingConcurrentMergeScheduler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Lucene.Net.Index; 3 | 4 | 5 | namespace Examine.Lucene.Providers 6 | { 7 | /// 8 | /// Used to prevent the appdomain from crashing when lucene runs into a concurrent merge scheduler failure 9 | /// 10 | internal class ErrorLoggingConcurrentMergeScheduler : ConcurrentMergeScheduler 11 | { 12 | private readonly Action _logger; 13 | 14 | public ErrorLoggingConcurrentMergeScheduler(string indexName, Action logger) 15 | { 16 | IndexName = indexName; 17 | _logger = logger; 18 | } 19 | 20 | public string IndexName { get; } 21 | 22 | protected override void HandleMergeException(System.Exception exc) 23 | { 24 | try 25 | { 26 | base.HandleMergeException(exc); 27 | } 28 | catch (Exception e) 29 | { 30 | _logger($"Concurrent merge failed for index: {IndexName} if this error is persistent then index rebuilding is necessary", e); 31 | } 32 | } 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Providers/IndexThreadingMode.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Lucene.Providers 2 | { 3 | public enum IndexThreadingMode 4 | { 5 | /// 6 | /// The deafult, processes the index queue on a background thread 7 | /// 8 | Asynchronous, 9 | 10 | /// 11 | /// Optional, mostly for testing, processes the index queue on the same execution thread (blocking) 12 | /// 13 | Synchronous 14 | } 15 | 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Providers/LuceneSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Lucene.Search; 3 | using Lucene.Net.Search; 4 | using Lucene.Net.Analysis; 5 | 6 | namespace Examine.Lucene.Providers 7 | { 8 | 9 | /// 10 | /// Standard object used to search a Lucene index 11 | /// 12 | public class LuceneSearcher : BaseLuceneSearcher, IDisposable 13 | { 14 | private readonly SearcherManager _searcherManager; 15 | private readonly FieldValueTypeCollection _fieldValueTypeCollection; 16 | private readonly bool _isNrt; 17 | private bool _disposedValue; 18 | private volatile ISearchContext _searchContext; 19 | 20 | [Obsolete("Use ctor with all dependencies")] 21 | public LuceneSearcher(string name, SearcherManager searcherManager, Analyzer analyzer, FieldValueTypeCollection fieldValueTypeCollection) 22 | : base(name, analyzer) 23 | { 24 | _searcherManager = searcherManager; 25 | _fieldValueTypeCollection = fieldValueTypeCollection; 26 | } 27 | 28 | public LuceneSearcher(string name, SearcherManager searcherManager, Analyzer analyzer, FieldValueTypeCollection fieldValueTypeCollection, bool isNrt) 29 | : base(name, analyzer) 30 | { 31 | _searcherManager = searcherManager; 32 | _fieldValueTypeCollection = fieldValueTypeCollection; 33 | _isNrt = isNrt; 34 | } 35 | 36 | public override ISearchContext GetSearchContext() 37 | { 38 | // Don't create a new search context unless something has changed 39 | var isCurrent = _searcherManager.IsSearcherCurrent(); 40 | if (_searchContext is null || !isCurrent) 41 | { 42 | _searchContext = new SearchContext(_searcherManager, _fieldValueTypeCollection, _isNrt); 43 | } 44 | 45 | return _searchContext; 46 | } 47 | 48 | protected virtual void Dispose(bool disposing) 49 | { 50 | if (!_disposedValue) 51 | { 52 | if (disposing) 53 | { 54 | _searcherManager.Dispose(); 55 | } 56 | 57 | _disposedValue = true; 58 | } 59 | } 60 | 61 | public void Dispose() 62 | { 63 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 64 | Dispose(disposing: true); 65 | } 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Providers/MultiIndexSearcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Examine.Lucene.Search; 5 | using Lucene.Net.Analysis; 6 | using Lucene.Net.Analysis.Standard; 7 | 8 | namespace Examine.Lucene.Providers 9 | { 10 | /// 11 | /// A provider that allows for searching across multiple indexes 12 | /// 13 | public class MultiIndexSearcher : BaseLuceneSearcher 14 | { 15 | private readonly Lazy> _searchers; 16 | 17 | 18 | /// 19 | /// Constructor to allow for creating a searcher at runtime 20 | /// 21 | /// 22 | /// 23 | /// 24 | #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters 25 | public MultiIndexSearcher(string name, IEnumerable indexes, Analyzer analyzer = null) 26 | #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters 27 | : base(name, analyzer ?? new StandardAnalyzer(LuceneInfo.CurrentVersion)) 28 | { 29 | _searchers = new Lazy>(() => indexes.Select(x => x.Searcher)); 30 | } 31 | 32 | /// 33 | /// Constructor to allow for creating a searcher at runtime 34 | /// 35 | /// 36 | /// 37 | /// 38 | #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters 39 | public MultiIndexSearcher(string name, Lazy> searchers, Analyzer analyzer = null) 40 | #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters 41 | : base(name, analyzer ?? new StandardAnalyzer(LuceneInfo.CurrentVersion)) 42 | { 43 | _searchers = searchers; 44 | } 45 | 46 | /// 47 | /// The underlying LuceneSearchers that will be searched across 48 | /// 49 | public IEnumerable Searchers => _searchers.Value.OfType(); 50 | 51 | // for tests 52 | public bool SearchersInitialized => _searchers.IsValueCreated; 53 | 54 | public override ISearchContext GetSearchContext() 55 | => new MultiSearchContext(Searchers.Select(s => s.GetSearchContext()).ToArray()); 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Providers/ValueSetValidatorDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Examine.Lucene.Providers 4 | { 5 | /// 6 | /// Simple validator that uses a delegate for validation 7 | /// 8 | public class ValueSetValidatorDelegate : IValueSetValidator 9 | { 10 | private readonly Func _validator; 11 | 12 | public ValueSetValidatorDelegate(Func validator) 13 | => _validator = validator; 14 | 15 | public ValueSetValidationResult Validate(ValueSet valueSet) 16 | => _validator(valueSet); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Examine.Lucene/ReaderStatus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Examine.Lucene 7 | { 8 | public enum ReaderStatus { Current, Closed, NotCurrent } 9 | } 10 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/ExamineMultiFieldQueryParser.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene.Indexing; 2 | using Lucene.Net.Analysis; 3 | using Lucene.Net.Search; 4 | using Lucene.Net.Util; 5 | 6 | namespace Examine.Lucene.Search 7 | { 8 | /// 9 | /// Custom query parser to deal with Examine/Lucene field value types 10 | /// 11 | public class ExamineMultiFieldQueryParser : CustomMultiFieldQueryParser 12 | { 13 | private readonly ISearchContext _searchContext; 14 | 15 | public ExamineMultiFieldQueryParser(ISearchContext searchContext, LuceneVersion matchVersion, Analyzer analyzer) 16 | : base(matchVersion, searchContext.SearchableFields, analyzer) 17 | { 18 | _searchContext = searchContext ?? throw new System.ArgumentNullException(nameof(searchContext)); 19 | } 20 | 21 | /// 22 | /// Override to provide support for numerical range query parsing 23 | /// 24 | /// 25 | /// 26 | /// 27 | /// 28 | /// 29 | /// 30 | /// By Default the lucene query parser only deals with strings and the result is a TermRangeQuery, however for numerics it needs to be a 31 | /// NumericRangeQuery. We can override this method to provide that behavior. 32 | /// 33 | /// In previous releases people were complaining that this wouldn't work and this is why. The answer came from here https://stackoverflow.com/questions/5026185/how-do-i-make-the-queryparser-in-lucene-handle-numeric-ranges 34 | /// 35 | /// protected override Query GetRangeQuery(string field, string part1, string part2, bool startInclusive,bool endInclusive) 36 | protected override Query GetRangeQuery(string field, string part1, string part2, bool startInclusive,bool endInclusive) 37 | { 38 | // if the field is IIndexRangeValueType then return it's query, else return the default 39 | var fieldType = _searchContext.GetFieldValueType(field); 40 | if (fieldType != null && fieldType is IIndexRangeValueType rangeType) 41 | { 42 | return rangeType.GetQuery(part1, part2, startInclusive, endInclusive); 43 | } 44 | 45 | return base.GetRangeQuery(field, part1, part2, startInclusive, endInclusive); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/ILuceneSearchResults.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Lucene.Search 2 | { 3 | /// 4 | /// Lucene.NET Search Results 5 | /// 6 | public interface ILuceneSearchResults : ISearchResults 7 | { 8 | /// 9 | /// Options for Searching After. Used for efficent deep paging. 10 | /// 11 | SearchAfterOptions SearchAfter { get; } 12 | 13 | /// 14 | /// Returns the maximum score value encountered. Note that in case 15 | /// scores are not tracked, this returns . 16 | /// 17 | float MaxScore { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/ISearchContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Examine.Lucene.Indexing; 3 | 4 | namespace Examine.Lucene.Search 5 | { 6 | public interface ISearchContext 7 | { 8 | ISearcherReference GetSearcher(); 9 | 10 | string[] SearchableFields { get; } 11 | 12 | IIndexFieldValueType GetFieldValueType(string fieldName); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/ISearcherReference.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Lucene.Net.Search; 3 | 4 | namespace Examine.Lucene.Search 5 | { 6 | // Dispose will release it from the manager 7 | public interface ISearcherReference : IDisposable 8 | { 9 | IndexSearcher IndexSearcher { get; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/LateBoundQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Lucene.Net.Index; 4 | using Lucene.Net.Search; 5 | using Lucene.Net.Search.Similarities; 6 | 7 | namespace Examine.Lucene.Search 8 | { 9 | public class LateBoundQuery : Query 10 | { 11 | private readonly Func _factory; 12 | 13 | private Query _wrapped; 14 | public Query Wrapped => _wrapped ?? (_wrapped = _factory()); 15 | 16 | public LateBoundQuery(Func factory) 17 | { 18 | _factory = factory; 19 | } 20 | 21 | public override object Clone() 22 | { 23 | return Wrapped.Clone(); 24 | } 25 | 26 | public override Weight CreateWeight(IndexSearcher searcher) 27 | { 28 | return Wrapped.CreateWeight(searcher); 29 | } 30 | 31 | /// 32 | /// Expert: adds all terms occuring in this query to the terms set. Only 33 | /// works if this query is in its rewritten form. 34 | /// 35 | /// UnsupportedOperationException if this query is not yet rewritten 36 | public override void ExtractTerms(ISet terms) 37 | { 38 | Wrapped.ExtractTerms(terms); 39 | } 40 | 41 | /// 42 | /// Gets or sets the boost for this query clause to b. Documents 43 | /// matching this clause will (in addition to the normal weightings) have 44 | /// their score multiplied by b. The boost is 1.0 by default. 45 | /// 46 | public override float Boost 47 | { 48 | get => Wrapped.Boost; 49 | set => Wrapped.Boost = value; 50 | } 51 | 52 | 53 | 54 | public override Query Rewrite(IndexReader reader) 55 | { 56 | return Wrapped.Rewrite(reader); 57 | } 58 | 59 | 60 | 61 | 62 | public override string ToString(string field) 63 | { 64 | return Wrapped.ToString(field); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/LuceneBooleanOperation.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using Examine.Lucene.Providers; 4 | using Examine.Search; 5 | using Lucene.Net.Search; 6 | 7 | namespace Examine.Lucene.Search 8 | { 9 | /// 10 | /// An implementation of the fluent API boolean operations 11 | /// 12 | [DebuggerDisplay("{_search}")] 13 | public class LuceneBooleanOperation : LuceneBooleanOperationBase, IQueryExecutor 14 | { 15 | private readonly LuceneSearchQuery _search; 16 | 17 | public LuceneBooleanOperation(LuceneSearchQuery search) 18 | : base(search) 19 | { 20 | _search = search; 21 | } 22 | 23 | #region IBooleanOperation Members 24 | 25 | /// 26 | protected override INestedQuery AndNested() => new LuceneQuery(this._search, Occur.MUST); 27 | 28 | /// 29 | protected override INestedQuery OrNested() => new LuceneQuery(this._search, Occur.SHOULD); 30 | 31 | /// 32 | protected override INestedQuery NotNested() => new LuceneQuery(this._search, Occur.MUST_NOT); 33 | 34 | /// 35 | public override IQuery And() => new LuceneQuery(this._search, Occur.MUST); 36 | 37 | 38 | /// 39 | public override IQuery Or() => new LuceneQuery(this._search, Occur.SHOULD); 40 | 41 | 42 | /// 43 | public override IQuery Not() => new LuceneQuery(this._search, Occur.MUST_NOT); 44 | 45 | #endregion 46 | 47 | public override ISearchResults Execute(QueryOptions options = null) => _search.Execute(options); 48 | 49 | #region IOrdering 50 | 51 | public override IOrdering OrderBy(params SortableField[] fields) => _search.OrderBy(fields); 52 | 53 | public override IOrdering OrderByDescending(params SortableField[] fields) => _search.OrderByDescending(fields); 54 | 55 | #endregion 56 | 57 | #region Select Fields 58 | 59 | public override IOrdering SelectFields(ISet fieldNames) => _search.SelectFieldsInternal(fieldNames); 60 | 61 | public override IOrdering SelectField(string fieldName) => _search.SelectFieldInternal(fieldName); 62 | 63 | public override IOrdering SelectAllFields() => _search.SelectAllFieldsInternal(); 64 | 65 | #endregion 66 | 67 | public override string ToString() => _search.ToString(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/LuceneSearchExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Examine.Search; 3 | using Lucene.Net.Search; 4 | 5 | namespace Examine.Lucene.Search 6 | { 7 | /// 8 | /// A set of helpers for working with Lucene.Net in Examine 9 | /// 10 | public static class LuceneSearchExtensions 11 | { 12 | 13 | /// 14 | /// Converts an Examine boolean operation to a Lucene representation 15 | /// 16 | /// The operation. 17 | /// The translated Boolean operation 18 | public static Occur ToLuceneOccurrence(this BooleanOperation o) 19 | { 20 | switch (o) 21 | { 22 | case BooleanOperation.And: 23 | return Occur.MUST; 24 | case BooleanOperation.Not: 25 | return Occur.MUST_NOT; 26 | case BooleanOperation.Or: 27 | default: 28 | return Occur.SHOULD; 29 | } 30 | } 31 | 32 | /// 33 | /// Converts a Lucene boolean occurrence to an Examine representation 34 | /// 35 | /// The occurrence to translate. 36 | /// The translated boolean occurrence 37 | 38 | public static BooleanOperation ToBooleanOperation(this Occur o) 39 | { 40 | if (o == Occur.MUST) 41 | { 42 | return BooleanOperation.And; 43 | } 44 | else if (o == Occur.MUST_NOT) 45 | { 46 | return BooleanOperation.Not; 47 | } 48 | else 49 | { 50 | return BooleanOperation.Or; 51 | } 52 | } 53 | /// 54 | /// Executes the query 55 | /// 56 | public static ILuceneSearchResults ExecuteWithLucene(this IQueryExecutor queryExecutor, QueryOptions options = null) 57 | { 58 | if(queryExecutor is LuceneBooleanOperation 59 | || queryExecutor is LuceneSearchQuery) 60 | { 61 | var results = queryExecutor.Execute(options); 62 | if(results is ILuceneSearchResults luceneSearchResults) 63 | { 64 | return luceneSearchResults; 65 | } 66 | } 67 | throw new NotSupportedException("QueryExecutor is not Lucene.NET"); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/LuceneSearchResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Examine.Lucene.Search 8 | { 9 | public class LuceneSearchResult : SearchResult, ISearchResult 10 | { 11 | public LuceneSearchResult(string id, float score, Func>> lazyFieldVals, int shardId) 12 | : base(id, score, lazyFieldVals) 13 | { 14 | ShardIndex = shardId; 15 | } 16 | 17 | public int ShardIndex { get; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/LuceneSearchResults.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Examine.Lucene.Search 6 | { 7 | public class LuceneSearchResults : ILuceneSearchResults 8 | { 9 | public static LuceneSearchResults Empty { get; } = new LuceneSearchResults(Array.Empty(), 0,float.NaN, default); 10 | 11 | private readonly IReadOnlyCollection _results; 12 | 13 | public LuceneSearchResults( 14 | IReadOnlyCollection results, 15 | int totalItemCount, 16 | float maxScore, 17 | SearchAfterOptions searchAfterOptions) 18 | { 19 | _results = results; 20 | TotalItemCount = totalItemCount; 21 | MaxScore = maxScore; 22 | SearchAfter = searchAfterOptions; 23 | } 24 | 25 | public long TotalItemCount { get; } 26 | 27 | /// 28 | /// Returns the maximum score value encountered. Note that in case 29 | /// scores are not tracked, this returns . 30 | /// 31 | public float MaxScore { get; } 32 | 33 | public SearchAfterOptions SearchAfter { get; } 34 | 35 | public IEnumerator GetEnumerator() => _results.GetEnumerator(); 36 | 37 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/MultiSearchContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Examine.Lucene.Indexing; 4 | 5 | namespace Examine.Lucene.Search 6 | { 7 | 8 | public class MultiSearchContext : ISearchContext 9 | { 10 | private readonly ISearchContext[] _inner; 11 | 12 | private string[] _fields; 13 | 14 | public MultiSearchContext(ISearchContext[] inner) => _inner = inner; 15 | 16 | public ISearcherReference GetSearcher() 17 | => new MultiSearchSearcherReference(_inner.Select(x => x.GetSearcher()).ToArray()); 18 | 19 | public string[] SearchableFields => _fields ?? (_fields = _inner.SelectMany(x => x.SearchableFields).Distinct().ToArray()); 20 | 21 | public IIndexFieldValueType GetFieldValueType(string fieldName) 22 | => _inner.Select(cc => cc.GetFieldValueType(fieldName)).FirstOrDefault(type => type != null); 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/MultiSearchSearcherReference.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Index; 2 | using Lucene.Net.Search; 3 | 4 | namespace Examine.Lucene.Search 5 | { 6 | public class MultiSearchSearcherReference : ISearcherReference 7 | { 8 | public MultiSearchSearcherReference(ISearcherReference[] inner) 9 | { 10 | _inner = inner; 11 | } 12 | 13 | private bool _disposedValue; 14 | private IndexSearcher _searcher; 15 | private readonly ISearcherReference[] _inner; 16 | 17 | public IndexSearcher IndexSearcher 18 | { 19 | get 20 | { 21 | if (_searcher == null) 22 | { 23 | var searchables = new IndexReader[_inner.Length]; 24 | for (int i = 0; i < _inner.Length; i++) 25 | { 26 | searchables[i] = _inner[i].IndexSearcher.IndexReader; 27 | } 28 | _searcher = new IndexSearcher(new MultiReader(searchables)); 29 | } 30 | return _searcher; 31 | 32 | } 33 | } 34 | 35 | protected virtual void Dispose(bool disposing) 36 | { 37 | if (!_disposedValue) 38 | { 39 | if (disposing) 40 | { 41 | foreach (var i in _inner) 42 | { 43 | i.Dispose(); 44 | } 45 | } 46 | 47 | _disposedValue = true; 48 | } 49 | } 50 | 51 | public void Dispose() 52 | { 53 | // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method 54 | Dispose(disposing: true); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/SearchAfterOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Lucene.Search 2 | { 3 | /// 4 | /// Options for Searching After. Used for efficent deep paging. 5 | /// 6 | public class SearchAfterOptions 7 | { 8 | 9 | public SearchAfterOptions(int documentId, float documentScore, object[] fields, int shardIndex) 10 | { 11 | DocumentId = documentId; 12 | DocumentScore = documentScore; 13 | Fields = fields; 14 | ShardIndex = shardIndex; 15 | } 16 | 17 | /// 18 | /// The Id of the last document in the previous result set. 19 | /// The search will search after this document 20 | /// 21 | public int DocumentId { get; } 22 | 23 | /// 24 | /// The Score of the last document in the previous result set. 25 | /// The search will search after this document 26 | /// 27 | public float DocumentScore { get; } 28 | 29 | /// 30 | /// The index of the shard the doc belongs to 31 | /// 32 | public int? ShardIndex { get; } 33 | 34 | /// 35 | /// Search fields. Should contain null or J2N.Int 36 | /// 37 | public object[] Fields { get; } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Examine.Lucene/Search/SearcherReference.cs: -------------------------------------------------------------------------------- 1 | using Lucene.Net.Search; 2 | 3 | namespace Examine.Lucene.Search 4 | { 5 | public readonly struct SearcherReference : ISearcherReference 6 | { 7 | private readonly SearcherManager _searcherManager; 8 | 9 | public SearcherReference(SearcherManager searcherManager) 10 | { 11 | _searcherManager = searcherManager; 12 | IndexSearcher = _searcherManager.Acquire(); 13 | } 14 | 15 | public IndexSearcher IndexSearcher { get; } 16 | 17 | public void Dispose() => _searcherManager.Release(IndexSearcher); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Examine.Test/App_Data/PDFStandards.PDF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Test/App_Data/PDFStandards.PDF -------------------------------------------------------------------------------- /src/Examine.Test/App_Data/StringTheory.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Test/App_Data/StringTheory.pdf -------------------------------------------------------------------------------- /src/Examine.Test/App_Data/TemplateIndex/_0.cfs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Test/App_Data/TemplateIndex/_0.cfs -------------------------------------------------------------------------------- /src/Examine.Test/App_Data/TemplateIndex/segments.gen: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Test/App_Data/TemplateIndex/segments.gen -------------------------------------------------------------------------------- /src/Examine.Test/App_Data/TemplateIndex/segments_2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Test/App_Data/TemplateIndex/segments_2 -------------------------------------------------------------------------------- /src/Examine.Test/App_Data/UmbracoContour.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Test/App_Data/UmbracoContour.pdf -------------------------------------------------------------------------------- /src/Examine.Test/App_Data/VS2010CSharp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Test/App_Data/VS2010CSharp.pdf -------------------------------------------------------------------------------- /src/Examine.Test/Examine.Lucene/Analyzers/TokenStreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Lucene.Net.Analysis; 3 | using Lucene.Net.Analysis.TokenAttributes; 4 | 5 | namespace Examine.Test.Examine.Lucene.Analyzers 6 | { 7 | public static class TokenStreamExtensions 8 | { 9 | public static string GetString(this TokenStream @in) 10 | { 11 | var @out = new StringBuilder(); 12 | ICharTermAttribute termAtt = @in.AddAttribute(); 13 | // extra safety to enforce, that the state is not preserved and also 14 | // assign bogus values 15 | @in.ClearAttributes(); 16 | termAtt.SetEmpty().Append("bogusTerm"); 17 | @in.Reset(); 18 | while (@in.IncrementToken()) 19 | { 20 | if (@out.Length > 0) 21 | { 22 | @out.Append(' '); 23 | } 24 | @out.Append(termAtt.ToString()); 25 | @in.ClearAttributes(); 26 | termAtt.SetEmpty().Append("bogusTerm"); 27 | } 28 | 29 | @in.Dispose(); 30 | return @out.ToString(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Examine.Test/Examine.Lucene/Index/AnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Lucene.Net.Analysis.Standard; 3 | using NUnit.Framework; 4 | using Lucene.Net.Analysis.TokenAttributes; 5 | 6 | namespace Examine.Test.Examine.Lucene.Index 7 | { 8 | [TestFixture] 9 | [Ignore("This is just here to confirm that Standard Analyzer no longer strips apostrophe's")] 10 | public class AnalyzerTests 11 | { 12 | [Test] 13 | public void Underscores() 14 | { 15 | var analyzer = new StandardAnalyzer(LuceneInfo.CurrentVersion); 16 | var ts = analyzer.GetTokenStream("myField", "This is Warren's book"); 17 | ts.Reset(); 18 | while (ts.IncrementToken()) 19 | { 20 | var termAtt = ts.GetAttribute(); 21 | Console.WriteLine(termAtt); 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Examine.Test/Examine.Lucene/Search/ConcurrentSearchBenchmarks.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Test/Examine.Lucene/Search/ConcurrentSearchBenchmarks.cs -------------------------------------------------------------------------------- /src/Examine.Test/Examine.Lucene/Search/StringTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | 3 | namespace Examine.Test.Examine.Lucene.Search 4 | { 5 | [TestFixture] 6 | public class StringTests //: AbstractPartialTrustFixture 7 | { 8 | [Test] 9 | public void Search_Remove_Stop_Words() 10 | { 11 | 12 | var stringPhrase1 = "hello my name is \"Shannon Deminick\" \"and I like to code\", here is a stop word and or two"; 13 | var stringPhrase2 = "\"into the darkness\" this is a sentence with a quote at \"the front and the end\""; 14 | 15 | var parsed1 = stringPhrase1.RemoveStopWords(); 16 | var parsed2 = stringPhrase2.RemoveStopWords(); 17 | 18 | Assert.AreEqual("hello my name \"Shannon Deminick\" \"and I like to code\" , here stop word two", parsed1); 19 | Assert.AreEqual("\"into the darkness\" sentence quote \"the front and the end\"", parsed2); 20 | } 21 | 22 | [Test] 23 | public void Search_Remove_Stop_Words_Uneven_Quotes() 24 | { 25 | 26 | var stringPhrase1 = "hello my name is \"Shannon Deminick \"and I like to code\", here is a stop word and or two"; 27 | 28 | var parsed1 = stringPhrase1.RemoveStopWords(); 29 | 30 | Assert.AreEqual("hello my name \"Shannon Deminick\" I like code , here stop word two", parsed1); 31 | 32 | } 33 | 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Examine.Test/Examine.Test.csproj.vspscc: -------------------------------------------------------------------------------- 1 | "" 2 | { 3 | "FILE_VERSION" = "9237" 4 | "ENLISTMENT_CHOICE" = "NEVER" 5 | "PROJECT_FILE_RELATIVE_PATH" = "" 6 | "NUMBER_OF_EXCLUDED_FILES" = "0" 7 | "ORIGINAL_PROJECT_FILE_PATH" = "" 8 | "NUMBER_OF_NESTED_PROJECTS" = "0" 9 | "SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER" 10 | } 11 | -------------------------------------------------------------------------------- /src/Examine.Test/IndexTypes.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Test 2 | { 3 | /// 4 | /// The index types stored in the Lucene Index 5 | /// 6 | internal static class IndexTypes 7 | { 8 | 9 | /// 10 | /// The content index type 11 | /// 12 | /// 13 | /// Is lower case because the Standard Analyzer requires lower case 14 | /// 15 | public const string Content = "content"; 16 | 17 | /// 18 | /// The media index type 19 | /// 20 | /// 21 | /// Is lower case because the Standard Analyzer requires lower case 22 | /// 23 | public const string Media = "media"; 24 | 25 | /// 26 | /// The member index type 27 | /// 28 | /// 29 | /// Is lower case because the Standard Analyzer requires lower case 30 | /// 31 | public const string Member = "member"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Examine.Test/RandomIdRAMDirectory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Lucene.Net.Store; 3 | 4 | namespace Examine.Test 5 | { 6 | public class RandomIdRAMDirectory : RAMDirectory 7 | { 8 | private readonly string _lockId = Guid.NewGuid().ToString(); 9 | public override string GetLockID() 10 | { 11 | return _lockId; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Examine.Test/TestContentService.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Xml.Linq; 3 | using System.IO; 4 | using System.Xml.XPath; 5 | using NUnit.Framework; 6 | 7 | namespace Examine.Test 8 | { 9 | 10 | /// 11 | /// A mock data service used to return content from the XML data file created with CWS 12 | /// 13 | public class TestContentService 14 | { 15 | private XDocument _xDoc; 16 | 17 | /// 18 | /// Return the XDocument containing the xml from the umbraco.config xml file 19 | /// 20 | /// 21 | /// 22 | public XDocument GetPublishedContentByXPath(string xpath) 23 | { 24 | if (_xDoc == null) 25 | { 26 | var xmlFile = new DirectoryInfo(TestContext.CurrentContext.TestDirectory).GetDirectories("App_Data") 27 | .Single() 28 | .GetFiles("umbraco.config") 29 | .Single(); 30 | 31 | _xDoc = XDocument.Load(xmlFile.FullName); 32 | } 33 | 34 | var xdoc = XDocument.Parse(""); 35 | xdoc.Root.Add(_xDoc.XPathSelectElements(xpath)); 36 | 37 | return xdoc; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Examine.Test/TestIndex.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Examine.Lucene; 3 | using Examine.Lucene.Providers; 4 | using Lucene.Net.Index; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.Extensions.Options; 7 | 8 | namespace Examine.Test 9 | { 10 | public class TestIndex : LuceneIndex 11 | { 12 | public const string TestIndexName = "testIndexer"; 13 | 14 | public TestIndex(ILoggerFactory loggerFactory, IOptionsMonitor options) 15 | : base(loggerFactory, TestIndexName, options) 16 | { 17 | RunAsync = false; 18 | } 19 | 20 | public TestIndex(ILoggerFactory loggerFactory, IOptionsMonitor options, IndexWriter writer) 21 | : base(loggerFactory, TestIndexName, options, writer) 22 | { 23 | RunAsync = false; 24 | } 25 | 26 | public static IEnumerable AllData() 27 | { 28 | var data = new List(); 29 | for (int i = 0; i < 100; i++) 30 | { 31 | data.Add(ValueSet.FromObject(i.ToString(), "category" + (i % 2), new { item1 = "value" + i, item2 = "value" + i })); 32 | } 33 | return data; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/ConfigureIndexOptions.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene; 2 | using Examine.Lucene.Analyzers; 3 | using Examine.Lucene.Indexing; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace Examine.Web.Demo 7 | { 8 | /// 9 | /// Configure Examine indexes using .NET IOptions 10 | /// 11 | public sealed class ConfigureIndexOptions : IConfigureNamedOptions 12 | { 13 | private readonly ILoggerFactory _loggerFactory; 14 | 15 | public ConfigureIndexOptions(ILoggerFactory loggerFactory) 16 | { 17 | _loggerFactory = loggerFactory; 18 | } 19 | 20 | public void Configure(string? name, LuceneDirectoryIndexOptions options) 21 | { 22 | switch (name) 23 | { 24 | case "MyIndex": 25 | // Create a dictionary for custom value types. 26 | // They keys are the value type names. 27 | options.IndexValueTypesFactory = new Dictionary 28 | { 29 | // Create a phone number value type using the GenericAnalyzerFieldValueType 30 | // to pass in a custom analyzer. As an example, it could use Examine's 31 | // PatternAnalyzer to pass in a phone number pattern to match. 32 | ["phone"] = new DelegateFieldValueTypeFactory(name => 33 | new GenericAnalyzerFieldValueType( 34 | name, 35 | _loggerFactory, 36 | new PatternAnalyzer(@"\d{3}\s\d{3}\s\d{4}", 0))) 37 | }; 38 | 39 | // Add the field definition for a field called "phone" which maps 40 | // to a Value Type called "phone" defined above. 41 | options.FieldDefinitions.AddOrUpdate(new FieldDefinition("phone", "phone")); 42 | break; 43 | } 44 | } 45 | 46 | public void Configure(LuceneDirectoryIndexOptions options) 47 | => throw new NotImplementedException("This is never called and is just part of the interface"); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Data/BogusDataService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Bogus; 5 | using Lucene.Net.Facet.Range; 6 | 7 | namespace Examine.Web.Demo.Controllers 8 | { 9 | public class BogusDataService 10 | { 11 | /// 12 | /// Return a ton of people 13 | /// 14 | /// 15 | public IEnumerable GenerateData(int count) 16 | { 17 | return Enumerable.Range(1, count) 18 | .Select(x => new Person()) 19 | .Select((person, index) => new ValueSet( 20 | index.ToString(), 21 | "person", 22 | PersonValues(person))); 23 | } 24 | 25 | private IDictionary> PersonValues(Person person) 26 | { 27 | var values = new Dictionary> 28 | { 29 | [nameof(person.FullName)] = new List(1) { person.FullName }, 30 | [nameof(person.Email)] = new List(1) { person.Email }, 31 | [nameof(person.Phone)] = new List(1) { person.Phone }, 32 | [nameof(person.Website)] = new List(1) { person.Website }, 33 | [$"{nameof(person.Company)}{nameof(person.Company.Name)}"] = new List(1) { person.Company.Name }, 34 | [$"{nameof(person.Company)}{nameof(person.Company.CatchPhrase)}"] = new List(1) { person.Company.CatchPhrase }, 35 | [$"{nameof(person.Address)}{nameof(person.Address.City)}"] = new List(1) { person.Address.City }, 36 | [$"{nameof(person.Address)}{nameof(person.Address.State)}"] = new List(1) { person.Address.State }, 37 | [$"{nameof(person.Address)}{nameof(person.Address.Street)}"] = new List(1) { person.Address.Street } 38 | }; 39 | return values; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Data/Models/IndexInformation.cs: -------------------------------------------------------------------------------- 1 | namespace Examine.Web.Demo.Data.Models 2 | { 3 | public class IndexInformation 4 | { 5 | public IndexInformation(long documentCount, List fields) 6 | { 7 | DocumentCount = documentCount; 8 | Fields = fields; 9 | FieldCount = fields.Count; 10 | } 11 | 12 | public long DocumentCount { get; } 13 | public List Fields { get; } 14 | public int FieldCount { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Examine.Web.Demo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/IndexFactoryExtensions.cs: -------------------------------------------------------------------------------- 1 | using Examine.Lucene.Directories; 2 | using Examine.Lucene.Providers; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace Examine.Web.Demo 6 | { 7 | /// 8 | /// Creates the indexes 9 | /// 10 | public static class IndexFactoryExtensions 11 | { 12 | public static IServiceCollection CreateIndexes(this IServiceCollection services) 13 | { 14 | services.AddExamineLuceneIndex("MyIndex"); 15 | 16 | services.AddExamineLuceneIndex("SyncedIndex"); 17 | 18 | services.AddExamineLuceneMultiSearcher( 19 | "MultiIndexSearcher", 20 | new[] { "MyIndex", "SyncedIndex" }); 21 | 22 | services.ConfigureOptions(); 23 | 24 | return services; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Examine.Web.Demo.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Error.

19 |

An error occurred while processing your request.

20 | 21 | @if (Model.ShowRequestId) 22 | { 23 |

24 | Request ID: @Model.RequestId 25 |

26 | } 27 | 28 |

Development Mode

29 |

30 | Swapping to the Development environment displays detailed information about the error that occurred. 31 |

32 |

33 | The Development environment shouldn't be enabled for deployed applications. 34 | It can result in displaying sensitive information from exceptions to end users. 35 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 36 | and restarting the app. 37 |

38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace Examine.Web.Demo.Pages 6 | { 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Examine 4 | 5 |

Welcome to Examine

6 | 7 |

This project is used to demonstrate what can be done with Examine

8 | 9 |

What is Examine?

10 |

Examine allows you to index and search data easily and wraps the Lucene.Net indexing/searching engine. Lucene is _super_ fast and allows for very fast searching even on very large amounts of data. Examine is very extensible and allows you to configure as many indexes as you like and each may be configured individually. Out of the box Examine gives you a Lucene based index implementation as well as a Fluent API that can be used to search for your data.

11 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Pages/Indexes.razor: -------------------------------------------------------------------------------- 1 | @page "/indexes" 2 | @using Examine.Web.Demo.Data 3 | @using System.Linq 4 | @using Examine.Web.Demo.Data.Models 5 | @inject IndexService IndexService 6 | 7 |

Indexes

8 |

Here is a list of the indexes created in the demo application.

9 | 10 | @foreach (var indexData in _indexes) 11 | { 12 | 13 | } 14 | 15 | @code { 16 | private List _indexes = new(); 17 | 18 | protected override void OnInitialized() 19 | { 20 | _indexes = IndexService.GetAllIndexes().Select(index => new IndexData(index, IndexService.GetIndexInformation(index.Name))).ToList(); 21 | } 22 | 23 | 24 | private class IndexData 25 | { 26 | public IndexData(IIndex index, IndexInformation indexInformation) 27 | { 28 | Index = index; 29 | Information = indexInformation; 30 | } 31 | 32 | public IIndex Index { get; } 33 | public IndexInformation Information { get; set; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace Examine.Web.Demo.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = "_Layout"; 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Pages/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @namespace Examine.Web.Demo.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | @RenderBody() 19 | 20 |
21 | 22 | An error has occurred. This application may no longer respond until reloaded. 23 | 24 | 25 | An unhandled exception has occurred. See browser dev tools for details. 26 | 27 | Reload 28 | 🗙 29 |
30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Program.cs: -------------------------------------------------------------------------------- 1 | using Examine.Web.Demo; 2 | using Examine; 3 | using Examine.Web.Demo.Controllers; 4 | using Examine.Web.Demo.Data; 5 | 6 | var builder = WebApplication.CreateBuilder(args); 7 | 8 | builder.Logging.ClearProviders(); 9 | builder.Logging.AddConsole(); 10 | 11 | // Add services for Blazor 12 | builder.Services.AddRazorPages(); 13 | builder.Services.AddServerSideBlazor(); 14 | 15 | // Adds Examine Core services 16 | builder.Services.AddExamine(); 17 | 18 | // A custom extension method to create custom indexes 19 | builder.Services.CreateIndexes(); 20 | 21 | // Custom services for the demo 22 | builder.Services.AddTransient(); 23 | builder.Services.AddTransient(); 24 | 25 | var app = builder.Build(); 26 | 27 | // Configure the HTTP request pipeline. 28 | if (!app.Environment.IsDevelopment()) 29 | { 30 | app.UseExceptionHandler("/Error"); 31 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 32 | app.UseHsts(); 33 | } 34 | 35 | app.UseHttpsRedirection(); 36 | 37 | app.UseStaticFiles(); 38 | 39 | app.UseRouting(); 40 | 41 | app.MapBlazorHub(); 42 | app.MapFallbackToPage("/_Host"); 43 | 44 | app.Run(); 45 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:30991", 7 | "sslPort": 44345 8 | } 9 | }, 10 | "profiles": { 11 | "Examine.Web.Demo": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "applicationUrl": "https://localhost:7139;http://localhost:5139", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "IIS Express": { 21 | "commandName": "IISExpress", 22 | "launchBrowser": true, 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | Examine Demo 4 | 5 |
6 | 9 | 10 |
11 |
12 | @Body 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | } 28 | 29 | .top-row a:first-child { 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | } 33 | 34 | @media (max-width: 640.98px) { 35 | .top-row:not(.auth) { 36 | display: none; 37 | } 38 | 39 | .top-row.auth { 40 | justify-content: space-between; 41 | } 42 | 43 | .top-row a, .top-row .btn-link { 44 | margin-left: 0; 45 | } 46 | } 47 | 48 | @media (min-width: 641px) { 49 | .page { 50 | flex-direction: row; 51 | } 52 | 53 | .sidebar { 54 | width: 250px; 55 | height: 100vh; 56 | position: sticky; 57 | top: 0; 58 | } 59 | 60 | .top-row { 61 | position: sticky; 62 | top: 0; 63 | z-index: 1; 64 | } 65 | 66 | .top-row, article { 67 | padding-left: 2rem !important; 68 | padding-right: 1.5rem !important; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Shared/NavMenu.razor: -------------------------------------------------------------------------------- 1 | 9 | 10 |
11 | 28 |
29 | 30 | @code { 31 | private bool collapseNavMenu = true; 32 | 33 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 34 | 35 | private void ToggleNavMenu() 36 | { 37 | collapseNavMenu = !collapseNavMenu; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .nav-item { 22 | font-size: 0.9rem; 23 | padding-bottom: 0.5rem; 24 | } 25 | 26 | .nav-item:first-of-type { 27 | padding-top: 1rem; 28 | } 29 | 30 | .nav-item:last-of-type { 31 | padding-bottom: 1rem; 32 | } 33 | 34 | .nav-item ::deep a { 35 | color: #d7d7d7; 36 | border-radius: 4px; 37 | height: 3rem; 38 | display: flex; 39 | align-items: center; 40 | line-height: 3rem; 41 | } 42 | 43 | .nav-item ::deep a.active { 44 | background-color: rgba(255,255,255,0.25); 45 | color: white; 46 | } 47 | 48 | .nav-item ::deep a:hover { 49 | background-color: rgba(255,255,255,0.1); 50 | color: white; 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .navbar-toggler { 55 | display: none; 56 | } 57 | 58 | .collapse { 59 | /* Never collapse the sidebar for wide screens */ 60 | display: block; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using Examine.Web.Demo 10 | @using Examine.Web.Demo.Shared 11 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Examine.Web.Demo/wwwroot/css/bootstrap/fonts/bootstrap-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Web.Demo/wwwroot/css/bootstrap/fonts/bootstrap-icons.woff -------------------------------------------------------------------------------- /src/Examine.Web.Demo/wwwroot/css/bootstrap/fonts/bootstrap-icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Web.Demo/wwwroot/css/bootstrap/fonts/bootstrap-icons.woff2 -------------------------------------------------------------------------------- /src/Examine.Web.Demo/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shazwazza/Examine/7ab37467ec9a8dc5c507b5a0045130df26e3fcbb/src/Examine.Web.Demo/wwwroot/favicon.ico --------------------------------------------------------------------------------