Move elastic and manticore into services

This commit is contained in:
DariusIII
2025-12-18 12:58:15 +01:00
parent d63324f0cb
commit a368c133c9
26 changed files with 2519 additions and 116 deletions
+6 -4
View File
@@ -4,6 +4,8 @@ namespace Blacklight;
use App\Models\Predb;
use App\Models\UsenetGroup;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
@@ -49,9 +51,9 @@ class IRCScraper extends IRCClient
*/
protected string|false $_titleIgnoreRegex = false;
protected ManticoreSearch $manticoreSearch;
protected ManticoreSearchService $manticoreSearch;
private ElasticSearchSiteSearch $elasticsearch;
private ElasticSearchService $elasticsearch;
/**
* Construct.
@@ -106,8 +108,8 @@ class IRCScraper extends IRCClient
$this->_titleIgnoreRegex = (string) config('irc_settings.scrape_irc_title_ignore');
}
$this->elasticsearch = new ElasticSearchSiteSearch;
$this->manticoreSearch = new ManticoreSearch;
$this->elasticsearch = app(ElasticSearchService::class);
$this->manticoreSearch = app(ManticoreSearchService::class);
$this->_groupList = [];
$this->_silent = $silent;
+2 -2
View File
@@ -576,10 +576,10 @@ class ReleaseRemover
private function performSearch(string $regexMatch): array|string
{
if (config('nntmux.elasticsearch_enabled') === true) {
return (new ElasticSearchSiteSearch)->indexSearch($regexMatch, 100);
return app(\App\Services\Search\ElasticSearchService::class)->indexSearch($regexMatch, 100);
}
$searchResult = (new ManticoreSearch)->searchIndexes('releases_rt', $regexMatch, ['name,searchname']);
$searchResult = app(\App\Services\Search\ManticoreSearchService::class)->searchIndexes('releases_rt', $regexMatch, ['name,searchname']);
return ! empty($searchResult) ? Arr::wrap(Arr::get($searchResult, 'id')) : '';
}
+6 -4
View File
@@ -6,6 +6,8 @@ use App\Models\Category;
use App\Models\Release;
use App\Models\Settings;
use App\Models\UsenetGroup;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Elasticsearch;
use Elasticsearch\Common\Exceptions\Missing404Exception;
use Illuminate\Database\Eloquent\Collection;
@@ -29,9 +31,9 @@ class Releases extends Release
public int $passwordStatus;
private ManticoreSearch $manticoreSearch;
private ManticoreSearchService $manticoreSearch;
private ElasticSearchSiteSearch $elasticSearch;
private ElasticSearchService $elasticSearch;
/**
* @var array Class instances.
@@ -41,8 +43,8 @@ class Releases extends Release
public function __construct()
{
parent::__construct();
$this->manticoreSearch = new ManticoreSearch;
$this->elasticSearch = new ElasticSearchSiteSearch;
$this->manticoreSearch = app(ManticoreSearchService::class);
$this->elasticSearch = app(ElasticSearchService::class);
}
/**
+4 -5
View File
@@ -2,8 +2,8 @@
namespace App\Console\Commands;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
@@ -147,9 +147,8 @@ class DeleteReleases extends Command
DB::delete('DELETE FROM releases WHERE id = ?', [$releaseId]);
// Delete from search indexes
$identifiers = ['id' => $releaseId, 'g' => $release->guid];
ManticoreSearch::deleteRelease($identifiers);
ElasticSearchSiteSearch::deleteRelease($releaseId);
app(ManticoreSearchService::class)->deleteRelease($releaseId);
app(ElasticSearchService::class)->deleteRelease($releaseId);
$deleted++;
@@ -4,7 +4,7 @@ namespace App\Console\Commands;
use App\Models\Predb;
use App\Models\Release;
use Blacklight\ManticoreSearch;
use App\Services\Search\ManticoreSearchService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
@@ -276,7 +276,7 @@ class NntmuxOffsetPopulate extends Command
private function clearIndex(string $engine, string $index): void
{
if ($engine === 'manticore') {
$manticore = new ManticoreSearch;
$manticore = app(ManticoreSearchService::class);
$indexName = $index === 'releases' ? 'releases_rt' : 'predb_rt';
$manticore->truncateRTIndex(Arr::wrap($indexName));
$this->info("Truncated ManticoreSearch index: {$indexName}");
+3 -3
View File
@@ -4,7 +4,7 @@ namespace App\Console\Commands;
use App\Models\Predb;
use App\Models\Release;
use Blacklight\ManticoreSearch;
use App\Services\Search\ManticoreSearchService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
@@ -92,7 +92,7 @@ class NntmuxOffsetWorker extends Command
$startTime = microtime(true);
if ($engine === 'manticore') {
$manticore = new ManticoreSearch;
$manticore = app(ManticoreSearchService::class);
$batchData = [];
$query->chunk($batchSize, function ($items) use ($manticore, $indexName, $transformer, &$processed, &$errors, &$batchData, $batchSize, $workerId) {
@@ -248,7 +248,7 @@ class NntmuxOffsetWorker extends Command
/**
* Process ManticoreSearch batch
*/
private function processManticoreBatch(ManticoreSearch $manticore, string $indexName, array $data, int $workerId): void
private function processManticoreBatch(ManticoreSearchService $manticore, string $indexName, array $data, int $workerId): void
{
$retries = 3;
$attempt = 0;
@@ -4,7 +4,7 @@ namespace App\Console\Commands;
use App\Models\Predb;
use App\Models\Release;
use Blacklight\ManticoreSearch;
use App\Services\Search\ManticoreSearchService;
use Exception;
use Illuminate\Console\Command;
use Illuminate\Support\Arr;
@@ -116,7 +116,7 @@ class NntmuxPopulateSearchIndexes extends Command
$this->info('Optimizing ManticoreSearch indexes...');
try {
(new ManticoreSearch)->optimizeRTIndex();
app(ManticoreSearchService::class)->optimizeRTIndex();
$this->info('Optimization completed successfully!');
return Command::SUCCESS;
@@ -155,7 +155,7 @@ class NntmuxPopulateSearchIndexes extends Command
private function manticoreReleases(): int
{
$manticore = new ManticoreSearch;
$manticore = app(ManticoreSearchService::class);
$indexName = 'releases_rt';
$manticore->truncateRTIndex(Arr::wrap($indexName));
@@ -208,7 +208,7 @@ class NntmuxPopulateSearchIndexes extends Command
*/
private function manticorePredb(): int
{
$manticore = new ManticoreSearch;
$manticore = app(ManticoreSearchService::class);
$indexName = 'predb_rt';
$manticore->truncateRTIndex([$indexName]);
@@ -244,7 +244,7 @@ class NntmuxPopulateSearchIndexes extends Command
*/
private function processManticoreData(string $indexName, int $total, $query, callable $transformer): int
{
$manticore = new ManticoreSearch;
$manticore = app(ManticoreSearchService::class);
$chunkSize = $this->getChunkSize();
$batchSize = $this->getBatchSize();
@@ -487,7 +487,7 @@ class NntmuxPopulateSearchIndexes extends Command
/**
* Process ManticoreSearch batch with retry logic
*/
private function processBatch(ManticoreSearch $manticore, string $indexName, array $data): void
private function processBatch(ManticoreSearchService $manticore, string $indexName, array $data): void
{
$retries = 3;
$attempt = 0;
@@ -4,7 +4,7 @@ namespace App\Console\Commands;
use App\Models\Release;
use App\Models\ReleaseFile;
use Blacklight\ManticoreSearch;
use App\Services\Search\ManticoreSearchService;
use Blacklight\NZB;
use Blacklight\ReleaseImage;
use Elasticsearch;
@@ -64,12 +64,8 @@ class NntmuxRemoveBadReleases extends Command
// we do nothing here just catch the error, we don't care if release is missing from ES, we are deleting it anyway
}
} else {
$identifiers = [
'i' => $badRelease->id,
'g' => $badRelease->guid,
];
// Delete from sphinx.
(new ManticoreSearch)->deleteRelease($identifiers);
// Delete from Manticore
app(ManticoreSearchService::class)->deleteRelease($badRelease->id);
}
$badRelease->delete();
}
+2 -2
View File
@@ -3,7 +3,7 @@
namespace App\Console\Commands;
use App\Models\UsenetGroup;
use Blacklight\ManticoreSearch;
use App\Services\Search\ManticoreSearchService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
@@ -171,7 +171,7 @@ class NntmuxResetDb extends Command
$this->info('All done! ElasticSearch indexes are deleted and recreated.');
} else {
(new ManticoreSearch)->truncateRTIndex(['releases_rt', 'predb_rt']);
app(ManticoreSearchService::class)->truncateRTIndex(['releases_rt', 'predb_rt']);
}
$this->info('Deleting nzbfiles subfolders.');
+10 -10
View File
@@ -4,18 +4,18 @@ namespace App\Http\Controllers;
use App\Models\Category;
use App\Models\UsenetGroup;
use Blacklight\ManticoreSearch;
use App\Services\Search\Contracts\SearchServiceInterface;
use Blacklight\Releases;
use Illuminate\Http\Request;
class SearchController extends BasePageController
{
private ManticoreSearch $manticore;
private SearchServiceInterface $searchService;
public function __construct()
public function __construct(SearchServiceInterface $searchService)
{
parent::__construct();
$this->manticore = new ManticoreSearch;
$this->searchService = $searchService;
}
/**
@@ -145,9 +145,9 @@ class SearchController extends BasePageController
// Get spell correction suggestions if we have a search query but few/no results
$spellSuggestion = null;
$searchQuery = $search ?: ($searchVars['searchadvr'] ?? '');
if (! empty($searchQuery) && $this->manticore->isSuggestEnabled()) {
// Get suggestions from ManticoreSearch
$suggestions = $this->manticore->suggest($searchQuery);
if (! empty($searchQuery) && $this->searchService->isSuggestEnabled()) {
// Get suggestions from search service
$suggestions = $this->searchService->suggest($searchQuery);
if (! empty($suggestions)) {
// Sort by doc count descending to get best suggestion
usort($suggestions, fn ($a, $b) => $b['docs'] - $a['docs']);
@@ -214,10 +214,10 @@ class SearchController extends BasePageController
'meta_title' => 'Search Nzbs',
'meta_keywords' => 'search,nzb,description,details',
'meta_description' => 'Search for Nzbs',
// ManticoreSearch enhanced features
// Search enhanced features
'spellSuggestion' => $spellSuggestion,
'autocompleteEnabled' => $this->manticore->isAutocompleteEnabled(),
'suggestEnabled' => $this->manticore->isSuggestEnabled(),
'autocompleteEnabled' => $this->searchService->isAutocompleteEnabled(),
'suggestEnabled' => $this->searchService->isSuggestEnabled(),
]);
return view('search.index', $this->viewData);
@@ -2,24 +2,18 @@
namespace App\Http\Controllers;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use App\Services\Search\Contracts\SearchServiceInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
class SearchSuggestController extends Controller
{
private ManticoreSearch|ElasticSearchSiteSearch $searchEngine;
private SearchServiceInterface $searchEngine;
public function __construct()
public function __construct(SearchServiceInterface $searchEngine)
{
// Use Elasticsearch if enabled, otherwise fall back to ManticoreSearch
if (config('nntmux.elasticsearch_enabled', false)) {
$this->searchEngine = new ElasticSearchSiteSearch;
} else {
$this->searchEngine = new ManticoreSearch;
}
$this->searchEngine = $searchEngine;
}
/**
+4 -4
View File
@@ -2,10 +2,10 @@
namespace App\Models;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Blacklight\ColorCLI;
use Blacklight\ConsoleTools;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Arr;
@@ -201,9 +201,9 @@ class Predb extends Model
->orderByDesc('predb.predate');
if (! empty($search)) {
if (config('nntmux.elasticsearch_enabled') === true) {
$ids = (new ElasticSearchSiteSearch)->predbIndexSearch($search);
$ids = app(ElasticSearchService::class)->predbIndexSearch($search);
} else {
$manticore = new ManticoreSearch;
$manticore = app(ManticoreSearchService::class);
$ids = Arr::get($manticore->searchIndexes('predb_rt', $search, ['title']), 'id');
}
$sql->whereIn('predb.id', $ids);
+8 -8
View File
@@ -2,8 +2,8 @@
namespace App\Models;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -136,9 +136,9 @@ class Release extends Model
);
if (config('nntmux.elasticsearch_enabled') === true) {
(new ElasticSearchSiteSearch)->insertRelease($parameters);
app(ElasticSearchService::class)->insertRelease($parameters);
} else {
(new ManticoreSearch)->insertRelease($parameters);
app(ManticoreSearchService::class)->insertRelease($parameters);
}
return $parameters['id'];
@@ -173,9 +173,9 @@ class Release extends Model
);
if (config('nntmux.elasticsearch_enabled') === true) {
(new ElasticSearchSiteSearch)->updateRelease($id);
app(ElasticSearchService::class)->updateRelease($id);
} else {
(new ManticoreSearch)->updateRelease($id);
app(ManticoreSearchService::class)->updateRelease($id);
}
}
@@ -397,9 +397,9 @@ class Release extends Model
if (! empty($similar)) {
if (config('nntmux.elasticsearch_enabled') === true) {
$searchResult = (new ElasticSearchSiteSearch)->indexSearch($similar[1], 10);
$searchResult = app(ElasticSearchService::class)->indexSearch($similar[1], 10);
} else {
$searchResult = (new ManticoreSearch)->searchIndexes('releases_rt', $similar[1]);
$searchResult = app(ManticoreSearchService::class)->searchIndexes('releases_rt', $similar[1]);
if (! empty($searchResult)) {
$searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
}
+4 -4
View File
@@ -2,8 +2,8 @@
namespace App\Models;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Log;
@@ -126,9 +126,9 @@ class ReleaseFile extends Model
ParHash::insertOrIgnore(['releases_id' => $id, 'hash' => $hash]);
}
if (config('nntmux.elasticsearch_enabled') === true) {
(new ElasticSearchSiteSearch)->updateRelease($id);
app(ElasticSearchService::class)->updateRelease($id);
} else {
(new ManticoreSearch)->updateRelease($id);
app(ManticoreSearchService::class)->updateRelease($id);
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
namespace App\Providers;
use App\Services\Search\Contracts\SearchServiceInterface;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Illuminate\Support\ServiceProvider;
class SearchServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Register ManticoreSearchService as singleton
$this->app->singleton(ManticoreSearchService::class, function ($app) {
return new ManticoreSearchService();
});
// Register ElasticSearchService as singleton
$this->app->singleton(ElasticSearchService::class, function ($app) {
return new ElasticSearchService();
});
// Bind the interface to the appropriate implementation based on config
$this->app->singleton(SearchServiceInterface::class, function ($app) {
if (config('nntmux.elasticsearch_enabled') === true) {
return $app->make(ElasticSearchService::class);
}
return $app->make(ManticoreSearchService::class);
});
// Register alias for ManticoreSearch (for backward compatibility)
$this->app->alias(ManticoreSearchService::class, 'manticore');
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}
@@ -7,9 +7,8 @@ use App\Models\Release;
use App\Services\AdditionalProcessing\Config\ProcessingConfiguration;
use App\Services\AdditionalProcessing\DTO\ReleaseProcessingContext;
use App\Services\Categorization\CategorizationService;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use Blacklight\NameFixer;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Blacklight\ReleaseExtra;
use Blacklight\ReleaseImage;
use FFMpeg\Coordinate\Dimension;
@@ -32,8 +31,8 @@ class MediaExtractionService
private ?FFMpeg $ffmpeg = null;
private ?FFProbe $ffprobe = null;
private ?MediaInfo $mediaInfo = null;
private ?ManticoreSearch $manticore = null;
private ?ElasticSearchSiteSearch $elasticsearch = null;
private ?ManticoreSearchService $manticore = null;
private ?ElasticSearchService $elasticsearch = null;
public function __construct(
private readonly ProcessingConfiguration $config,
@@ -471,18 +470,18 @@ class MediaExtractionService
return $this->mediaInfo;
}
private function manticore(): ManticoreSearch
private function manticore(): ManticoreSearchService
{
if ($this->manticore === null) {
$this->manticore = new ManticoreSearch();
$this->manticore = app(ManticoreSearchService::class);
}
return $this->manticore;
}
private function elasticsearch(): ElasticSearchSiteSearch
private function elasticsearch(): ElasticSearchService
{
if ($this->elasticsearch === null) {
$this->elasticsearch = new ElasticSearchSiteSearch();
$this->elasticsearch = app(ElasticSearchService::class);
}
return $this->elasticsearch;
}
@@ -6,13 +6,13 @@ use App\Models\MediaInfo as MediaInfoModel;
use App\Models\Predb;
use App\Models\Release;
use App\Models\ReleaseFile;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Blacklight\Releases;
use App\Services\AdditionalProcessing\Config\ProcessingConfiguration;
use App\Services\AdditionalProcessing\DTO\ReleaseProcessingContext;
use App\Services\NameFixing\NameFixingService;
use App\Services\NameFixing\ReleaseUpdateService;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use Blacklight\Nfo;
use Blacklight\NZB;
use Blacklight\ReleaseExtra;
@@ -28,8 +28,8 @@ use Illuminate\Support\Facades\Log;
*/
class ReleaseFileManager
{
private ?ManticoreSearch $manticore = null;
private ?ElasticSearchSiteSearch $elasticsearch = null;
private ?ManticoreSearchService $manticore = null;
private ?ElasticSearchService $elasticsearch = null;
public function __construct(
private readonly ProcessingConfiguration $config,
@@ -590,18 +590,18 @@ class ReleaseFileManager
return $hasGroupSuffix || ($hasTV && $hasQuality) || ($hasYear && ($hasQuality || $hasTV)) || $hasXXX;
}
private function manticore(): ManticoreSearch
private function manticore(): ManticoreSearchService
{
if ($this->manticore === null) {
$this->manticore = new ManticoreSearch();
$this->manticore = app(ManticoreSearchService::class);
}
return $this->manticore;
}
private function elasticsearch(): ElasticSearchSiteSearch
private function elasticsearch(): ElasticSearchService
{
if ($this->elasticsearch === null) {
$this->elasticsearch = new ElasticSearchSiteSearch();
$this->elasticsearch = app(ElasticSearchService::class);
}
return $this->elasticsearch;
}
+4 -4
View File
@@ -5,8 +5,8 @@ namespace App\Services;
use App\Models\Category;
use App\Models\Release;
use App\Services\Categorization\CategorizationService;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Blacklight\ReleaseExtra;
use Blacklight\ReleaseImage;
use FFMpeg\Coordinate\Dimension;
@@ -28,8 +28,8 @@ class MediaProcessingService
private readonly MediaInfo $mediaInfo,
private readonly ReleaseImage $releaseImage,
private readonly ReleaseExtra $releaseExtra,
private readonly ManticoreSearch $manticore,
private readonly ElasticSearchSiteSearch $elasticsearch,
private readonly ManticoreSearchService $manticore,
private readonly ElasticSearchService $elasticsearch,
private readonly CategorizationService $categorize,
) {}
@@ -10,10 +10,10 @@ use App\Services\NameFixing\Contracts\NameSourceFixerInterface;
use App\Services\NameFixing\DTO\NameFixResult;
use App\Services\NameFixing\Extractors\NfoNameExtractor;
use App\Services\NameFixing\Extractors\FileNameExtractor;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Blacklight\ColorCLI;
use Blacklight\ConsoleTools;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use Illuminate\Support\Arr;
/**
@@ -50,8 +50,8 @@ class NameFixingService
protected FileNameExtractor $fileExtractor;
protected FileNameCleaner $fileNameCleaner;
protected FilePrioritizer $filePrioritizer;
protected ManticoreSearch $manticore;
protected ElasticSearchSiteSearch $elasticsearch;
protected ManticoreSearchService $manticore;
protected ElasticSearchService $elasticsearch;
protected ColorCLI $colorCLI;
protected bool $echoOutput;
@@ -70,8 +70,8 @@ class NameFixingService
?FileNameExtractor $fileExtractor = null,
?FileNameCleaner $fileNameCleaner = null,
?FilePrioritizer $filePrioritizer = null,
?ManticoreSearch $manticore = null,
?ElasticSearchSiteSearch $elasticsearch = null,
?ManticoreSearchService $manticore = null,
?ElasticSearchService $elasticsearch = null,
?ColorCLI $colorCLI = null
) {
$this->updateService = $updateService ?? new ReleaseUpdateService();
@@ -80,8 +80,8 @@ class NameFixingService
$this->fileExtractor = $fileExtractor ?? new FileNameExtractor();
$this->fileNameCleaner = $fileNameCleaner ?? new FileNameCleaner();
$this->filePrioritizer = $filePrioritizer ?? new FilePrioritizer();
$this->manticore = $manticore ?? new ManticoreSearch();
$this->elasticsearch = $elasticsearch ?? new ElasticSearchSiteSearch();
$this->manticore = $manticore ?? app(ManticoreSearchService::class);
$this->elasticsearch = $elasticsearch ?? app(ElasticSearchService::class);
$this->colorCLI = $colorCLI ?? new ColorCLI();
$this->echoOutput = config('nntmux.echocli');
@@ -9,10 +9,10 @@ use App\Models\Predb;
use App\Models\Release;
use App\Models\UsenetGroup;
use App\Services\Categorization\CategorizationService;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Blacklight\ColorCLI;
use Blacklight\ConsoleTools;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use Blacklight\ReleaseCleaning;
use Illuminate\Support\Arr;
@@ -50,8 +50,8 @@ class ReleaseUpdateService
public const IS_RENAMED_DONE = 1;
protected CategorizationService $category;
protected ManticoreSearch $manticore;
protected ElasticSearchSiteSearch $elasticsearch;
protected ManticoreSearchService $manticore;
protected ElasticSearchService $elasticsearch;
protected FileNameCleaner $fileNameCleaner;
protected ColorCLI $colorCLI;
protected bool $echoOutput;
@@ -83,14 +83,14 @@ class ReleaseUpdateService
public function __construct(
?CategorizationService $category = null,
?ManticoreSearch $manticore = null,
?ElasticSearchSiteSearch $elasticsearch = null,
?ManticoreSearchService $manticore = null,
?ElasticSearchService $elasticsearch = null,
?FileNameCleaner $fileNameCleaner = null,
?ColorCLI $colorCLI = null
) {
$this->category = $category ?? new CategorizationService();
$this->manticore = $manticore ?? new ManticoreSearch();
$this->elasticsearch = $elasticsearch ?? new ElasticSearchSiteSearch();
$this->manticore = $manticore ?? app(ManticoreSearchService::class);
$this->elasticsearch = $elasticsearch ?? app(ElasticSearchService::class);
$this->fileNameCleaner = $fileNameCleaner ?? new FileNameCleaner();
$this->colorCLI = $colorCLI ?? new ColorCLI();
$this->echoOutput = config('nntmux.echocli');
@@ -0,0 +1,102 @@
<?php
namespace App\Services\Search\Contracts;
/**
* Interface for full-text search services (ManticoreSearch, Elasticsearch).
*
* Provides a common API for release and predb search, indexing, and autocomplete.
*/
interface SearchServiceInterface
{
/**
* Check if autocomplete is enabled.
*/
public function isAutocompleteEnabled(): bool;
/**
* Check if suggest is enabled.
*/
public function isSuggestEnabled(): bool;
/**
* Get the releases index name.
*/
public function getReleasesIndex(): string;
/**
* Get the predb index name.
*/
public function getPredbIndex(): string;
/**
* Insert a release into the search index.
*
* @param array $parameters Release data with 'id', 'name', 'searchname', 'fromname', 'categories_id', 'filename'
*/
public function insertRelease(array $parameters): void;
/**
* Update a release in the search index.
*
* @param int|string $releaseID Release ID
*/
public function updateRelease(int|string $releaseID): void;
/**
* Delete a release from the search index.
*
* @param int $id Release ID
*/
public function deleteRelease(int $id): void;
/**
* Insert a predb record into the search index.
*
* @param array $parameters Predb data with 'id', 'title', 'filename', 'source'
*/
public function insertPredb(array $parameters): void;
/**
* Update a predb record in the search index.
*
* @param array $parameters Predb data with 'id', 'title', 'filename', 'source'
*/
public function updatePreDb(array $parameters): void;
/**
* Search the releases index.
*
* @param array|string $phrases Search phrases
* @param int $limit Maximum number of results
* @return array Array of release IDs
*/
public function searchReleases(array|string $phrases, int $limit = 1000): array;
/**
* Search the predb index.
*
* @param array|string $searchTerm Search term(s)
* @return array Array of predb records
*/
public function searchPredb(array|string $searchTerm): array;
/**
* Get autocomplete suggestions for a search query.
*
* @param string $query The partial search query
* @param string|null $index Index to search (defaults to releases index)
* @return array<array{suggest: string, distance: int, docs: int}>
*/
public function autocomplete(string $query, ?string $index = null): array;
/**
* Get spell correction suggestions ("Did you mean?").
*
* @param string $query The search query to check
* @param string|null $index Index to use for suggestions
* @return array<array{suggest: string, distance: int, docs: int}>
*/
public function suggest(string $query, ?string $index = null): array;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,831 @@
<?php
namespace App\Services\Search;
use App\Models\Release;
use App\Services\Search\Contracts\SearchServiceInterface;
use Blacklight\ColorCLI;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Manticoresearch\Client;
use Manticoresearch\Exceptions\ResponseException;
use Manticoresearch\Exceptions\RuntimeException;
use Manticoresearch\Search;
/**
* ManticoreSearch service for full-text search functionality.
*/
class ManticoreSearchService implements SearchServiceInterface
{
protected mixed $config;
protected array $connection;
public Client $manticoreSearch;
public Search $search;
private ColorCLI $cli;
/**
* Establishes a connection to ManticoreSearch HTTP port.
*/
public function __construct()
{
$this->config = config('manticoresearch');
$this->connection = ['host' => $this->config['host'], 'port' => $this->config['port']];
$this->manticoreSearch = new Client($this->connection);
$this->search = new Search($this->manticoreSearch);
$this->cli = new ColorCLI;
}
/**
* Check if autocomplete is enabled.
*/
public function isAutocompleteEnabled(): bool
{
return ($this->config['autocomplete']['enabled'] ?? true) === true;
}
/**
* Check if suggest is enabled.
*/
public function isSuggestEnabled(): bool
{
return ($this->config['suggest']['enabled'] ?? true) === true;
}
/**
* Get the releases index name.
*/
public function getReleasesIndex(): string
{
return $this->config['indexes']['releases'] ?? 'releases_rt';
}
/**
* Get the predb index name.
*/
public function getPredbIndex(): string
{
return $this->config['indexes']['predb'] ?? 'predb_rt';
}
/**
* Insert release into ManticoreSearch releases_rt realtime index
*/
public function insertRelease(array $parameters): void
{
if (empty($parameters['id'])) {
Log::warning('ManticoreSearch: Cannot insert release without ID');
return;
}
try {
$document = [
'name' => $parameters['name'] ?? '',
'searchname' => $parameters['searchname'] ?? '',
'fromname' => $parameters['fromname'] ?? '',
'categories_id' => (string) ($parameters['categories_id'] ?? ''),
'filename' => $parameters['filename'] ?? '',
];
$this->manticoreSearch->table($this->config['indexes']['releases'])
->replaceDocument($document, $parameters['id']);
} catch (ResponseException $e) {
Log::error('ManticoreSearch insertRelease ResponseException: '.$e->getMessage(), [
'release_id' => $parameters['id'],
'index' => $this->config['indexes']['releases'],
]);
} catch (RuntimeException $e) {
Log::error('ManticoreSearch insertRelease RuntimeException: '.$e->getMessage(), [
'release_id' => $parameters['id'],
]);
} catch (\Throwable $e) {
Log::error('ManticoreSearch insertRelease unexpected error: '.$e->getMessage(), [
'release_id' => $parameters['id'],
'trace' => $e->getTraceAsString(),
]);
}
}
/**
* Insert release into Manticore RT table.
*/
public function insertPredb(array $parameters): void
{
if (empty($parameters['id'])) {
Log::warning('ManticoreSearch: Cannot insert predb without ID');
return;
}
try {
$document = [
'title' => $parameters['title'] ?? '',
'filename' => $parameters['filename'] ?? '',
'source' => $parameters['source'] ?? '',
];
$this->manticoreSearch->table($this->config['indexes']['predb'])
->replaceDocument($document, $parameters['id']);
} catch (ResponseException $e) {
Log::error('ManticoreSearch insertPredb ResponseException: '.$e->getMessage(), [
'predb_id' => $parameters['id'],
]);
} catch (RuntimeException $e) {
Log::error('ManticoreSearch insertPredb RuntimeException: '.$e->getMessage(), [
'predb_id' => $parameters['id'],
]);
} catch (\Throwable $e) {
Log::error('ManticoreSearch insertPredb unexpected error: '.$e->getMessage(), [
'predb_id' => $parameters['id'],
]);
}
}
/**
* Delete release from Manticore RT tables.
*
* @param int $id Release ID
*/
public function deleteRelease(int $id): void
{
if (empty($id)) {
Log::warning('ManticoreSearch: Cannot delete release without ID');
return;
}
try {
$this->manticoreSearch->table($this->config['indexes']['releases'])
->deleteDocument($id);
} catch (ResponseException $e) {
Log::error('ManticoreSearch deleteRelease error: '.$e->getMessage(), [
'id' => $id,
]);
}
}
/**
* Delete release from Manticore RT tables by GUID.
*
* @param array $identifiers ['g' => Release GUID(mandatory), 'id' => ReleaseID(optional, pass false)]
*/
public function deleteReleaseByGuid(array $identifiers): void
{
if (empty($identifiers['g'])) {
Log::warning('ManticoreSearch: Cannot delete release without GUID');
return;
}
try {
if ($identifiers['i'] === false || empty($identifiers['i'])) {
$release = Release::query()->where('guid', $identifiers['g'])->first(['id']);
$identifiers['i'] = $release?->id;
}
if (! empty($identifiers['i'])) {
$this->manticoreSearch->table($this->config['indexes']['releases'])
->deleteDocument($identifiers['i']);
} else {
Log::warning('ManticoreSearch: Could not find release ID for deletion', [
'guid' => $identifiers['g'],
]);
}
} catch (ResponseException $e) {
Log::error('ManticoreSearch deleteRelease error: '.$e->getMessage(), [
'guid' => $identifiers['g'],
'id' => $identifiers['i'] ?? null,
]);
}
}
/**
* Escapes characters that are treated as special operators by the query language parser.
*/
public static function escapeString(string $string): string
{
if ($string === '*' || empty($string)) {
return '';
}
$from = ['\\', '(', ')', '@', '~', '"', '&', '/', '$', '=', "'", '--', '[', ']', '!', '-'];
$to = ['\\\\', '\(', '\)', '\@', '\~', '\"', '\&', '\/', '\$', '\=', "\'", '\--', '\[', '\]', '\!', '\-'];
$string = str_replace($from, $to, $string);
// Clean up trailing special characters
$string = rtrim($string, '-!');
return trim($string);
}
public function updateRelease(int|string $releaseID): void
{
if (empty($releaseID)) {
Log::warning('ManticoreSearch: Cannot update release without ID');
return;
}
try {
$release = Release::query()
->where('releases.id', $releaseID)
->leftJoin('release_files as rf', 'releases.id', '=', 'rf.releases_id')
->select([
'releases.id',
'releases.name',
'releases.searchname',
'releases.fromname',
'releases.categories_id',
DB::raw('IFNULL(GROUP_CONCAT(rf.name SEPARATOR " "),"") filename'),
])
->groupBy('releases.id')
->first();
if ($release !== null) {
$this->insertRelease($release->toArray());
} else {
Log::warning('ManticoreSearch: Release not found for update', ['id' => $releaseID]);
}
} catch (\Throwable $e) {
Log::error('ManticoreSearch updateRelease error: '.$e->getMessage(), [
'release_id' => $releaseID,
]);
}
}
/**
* Update Manticore Predb index for given predb_id.
*/
public function updatePreDb(array $parameters): void
{
if (empty($parameters)) {
Log::warning('ManticoreSearch: Cannot update predb with empty parameters');
return;
}
$this->insertPredb($parameters);
}
public function truncateRTIndex(array $indexes = []): bool
{
if (empty($indexes)) {
$this->cli->error('You need to provide index name to truncate');
return false;
}
$success = true;
foreach ($indexes as $index) {
if (! \in_array($index, $this->config['indexes'], true)) {
$this->cli->error('Unsupported index: '.$index);
$success = false;
continue;
}
try {
$this->manticoreSearch->table($index)->truncate();
$this->cli->info('Truncating index '.$index.' finished.');
} catch (ResponseException $e) {
if ($e->getMessage() === 'Invalid index') {
$this->createIndexIfNotExists($index);
} else {
$this->cli->error('Error truncating index '.$index.': '.$e->getMessage());
$success = false;
}
} catch (\Throwable $e) {
$this->cli->error('Unexpected error truncating index '.$index.': '.$e->getMessage());
$success = false;
}
}
return $success;
}
/**
* Create index if it doesn't exist
*/
private function createIndexIfNotExists(string $index): void
{
try {
if ($index === 'releases_rt') {
$this->manticoreSearch->table($index)->create([
'name' => ['type' => 'string'],
'searchname' => ['type' => 'string'],
'fromname' => ['type' => 'string'],
'filename' => ['type' => 'string'],
'categories_id' => ['type' => 'integer'],
]);
$this->cli->info('Created releases_rt index');
} elseif ($index === 'predb_rt') {
$this->manticoreSearch->table($index)->create([
'title' => ['type' => 'string'],
'filename' => ['type' => 'string'],
'source' => ['type' => 'string'],
]);
$this->cli->info('Created predb_rt index');
}
} catch (\Throwable $e) {
$this->cli->error('Error creating index '.$index.': '.$e->getMessage());
}
}
/**
* Optimize the RT indices.
*/
public function optimizeRTIndex(): bool
{
$success = true;
foreach ($this->config['indexes'] as $index) {
try {
$this->manticoreSearch->table($index)->flush();
$this->manticoreSearch->table($index)->optimize();
Log::info("Successfully optimized index: {$index}");
} catch (ResponseException $e) {
Log::error('Failed to optimize index '.$index.': '.$e->getMessage());
$success = false;
} catch (\Throwable $e) {
Log::error('Unexpected error optimizing index '.$index.': '.$e->getMessage());
$success = false;
}
}
return $success;
}
/**
* Search releases index.
*
* @param array|string $phrases Search phrases
* @param int $limit Maximum number of results
* @return array Array of release IDs
*/
public function searchReleases(array|string $phrases, int $limit = 1000): array
{
$searchArray = is_array($phrases) ? $phrases : ['searchname' => $phrases];
$result = $this->searchIndexes($this->getReleasesIndex(), '', [], $searchArray);
return ! empty($result) ? ($result['id'] ?? []) : [];
}
/**
* Search predb index.
*
* @param array|string $searchTerm Search term(s)
* @return array Array of predb records
*/
public function searchPredb(array|string $searchTerm): array
{
$searchString = is_array($searchTerm) ? implode(' ', $searchTerm) : $searchTerm;
$result = $this->searchIndexes($this->getPredbIndex(), $searchString, ['title', 'filename'], []);
return $result['data'] ?? [];
}
public function searchIndexes(string $rt_index, ?string $searchString, array $column = [], array $searchArray = []): array
{
if (empty($rt_index)) {
Log::warning('ManticoreSearch: Index name is required for search');
return [];
}
// Create cache key for search results
$cacheKey = md5(serialize([
'index' => $rt_index,
'search' => $searchString,
'columns' => $column,
'array' => $searchArray,
]));
$cached = Cache::get($cacheKey);
if ($cached !== null) {
return $cached;
}
// Build query string once so we can retry if needed
$searchExpr = null;
if (! empty($searchArray)) {
$terms = [];
foreach ($searchArray as $key => $value) {
if (! empty($value)) {
$escapedValue = self::escapeString($value);
if (! empty($escapedValue)) {
$terms[] = '@@relaxed @'.$key.' '.$escapedValue;
}
}
}
if (! empty($terms)) {
$searchExpr = implode(' ', $terms);
} else {
return [];
}
} elseif (! empty($searchString)) {
$escapedSearch = self::escapeString($searchString);
if (empty($escapedSearch)) {
return [];
}
$searchColumns = '';
if (! empty($column)) {
if (count($column) > 1) {
$searchColumns = '@('.implode(',', $column).')';
} else {
$searchColumns = '@'.$column[0];
}
}
$searchExpr = '@@relaxed '.$searchColumns.' '.$escapedSearch;
} else {
return [];
}
// Avoid explicit sort for predb_rt to prevent Manticore's "too many sort-by attributes" error
$avoidSortForIndex = ($rt_index === 'predb_rt');
try {
// Use a fresh Search instance for every query to avoid parameter accumulation across calls
$query = (new Search($this->manticoreSearch))
->setTable($rt_index)
->option('ranker', 'sph04')
->maxMatches(10000)
->limit(10000)
->stripBadUtf8(true)
->search($searchExpr);
if (! $avoidSortForIndex) {
$query->sort('id', 'desc');
}
$results = $query->get();
} catch (ResponseException $e) {
// If we hit Manticore's "too many sort-by attributes" limit, retry once without explicit sorting
if (stripos($e->getMessage(), 'too many sort-by attributes') !== false) {
try {
$query = (new Search($this->manticoreSearch))
->setTable($rt_index)
->option('ranker', 'sph04')
->maxMatches(10000)
->limit(10000)
->stripBadUtf8(true)
->search($searchExpr);
$results = $query->get();
Log::warning('ManticoreSearch: Retried search without sorting due to sort-by attributes limit', [
'index' => $rt_index,
]);
} catch (ResponseException $e2) {
Log::error('ManticoreSearch searchIndexes ResponseException after retry: '.$e2->getMessage(), [
'index' => $rt_index,
'search' => $searchString,
]);
return [];
}
} else {
Log::error('ManticoreSearch searchIndexes ResponseException: '.$e->getMessage(), [
'index' => $rt_index,
'search' => $searchString,
]);
return [];
}
} catch (RuntimeException $e) {
Log::error('ManticoreSearch searchIndexes RuntimeException: '.$e->getMessage(), [
'index' => $rt_index,
'search' => $searchString,
]);
return [];
} catch (\Throwable $e) {
Log::error('ManticoreSearch searchIndexes unexpected error: '.$e->getMessage(), [
'index' => $rt_index,
'search' => $searchString,
]);
return [];
}
// Parse results and cache
$resultIds = [];
$resultData = [];
foreach ($results as $doc) {
$resultIds[] = $doc->getId();
$resultData[] = $doc->getData();
}
$result = [
'id' => $resultIds,
'data' => $resultData,
];
// Cache results for 5 minutes
Cache::put($cacheKey, $result, now()->addMinutes(5));
return $result;
}
/**
* Get autocomplete suggestions for a search query.
* Searches the releases index and returns matching searchnames.
*
* @param string $query The partial search query
* @param string|null $index Index to search (defaults to releases index)
* @return array<array{suggest: string, distance: int, docs: int}>
*/
public function autocomplete(string $query, ?string $index = null): array
{
$autocompleteConfig = $this->config['autocomplete'] ?? [
'enabled' => true,
'min_length' => 2,
'max_results' => 10,
'cache_minutes' => 10,
];
if (! ($autocompleteConfig['enabled'] ?? true)) {
return [];
}
$query = trim($query);
$minLength = $autocompleteConfig['min_length'] ?? 2;
if (strlen($query) < $minLength) {
return [];
}
$index = $index ?? ($this->config['indexes']['releases'] ?? 'releases_rt');
$cacheKey = 'manticore:autocomplete:'.md5($index.$query);
$cached = Cache::get($cacheKey);
if ($cached !== null) {
return $cached;
}
$suggestions = [];
$maxResults = $autocompleteConfig['max_results'] ?? 10;
try {
// Search releases index for matching searchnames
$escapedQuery = self::escapeString($query);
if (empty($escapedQuery)) {
return [];
}
// Use relaxed search on searchname field
$searchExpr = '@@relaxed @searchname '.$escapedQuery;
$search = (new Search($this->manticoreSearch))
->setTable($index)
->search($searchExpr)
->sort('id', 'desc')
->limit($maxResults * 3)
->stripBadUtf8(true);
$results = $search->get();
$seen = [];
foreach ($results as $doc) {
$data = $doc->getData();
$searchname = $data['searchname'] ?? '';
if (empty($searchname)) {
continue;
}
// Create a clean suggestion from the searchname
$suggestion = $this->extractSuggestion($searchname, $query);
if (! empty($suggestion) && ! isset($seen[strtolower($suggestion)])) {
$seen[strtolower($suggestion)] = true;
$suggestions[] = [
'suggest' => $suggestion,
'distance' => 0,
'docs' => 1,
];
}
if (count($suggestions) >= $maxResults) {
break;
}
}
} catch (\Throwable $e) {
if (config('app.debug')) {
Log::warning('ManticoreSearch autocomplete error: '.$e->getMessage());
}
}
if (! empty($suggestions)) {
$cacheMinutes = (int) ($autocompleteConfig['cache_minutes'] ?? 10);
Cache::put($cacheKey, $suggestions, now()->addMinutes($cacheMinutes));
}
return $suggestions;
}
/**
* Extract a clean suggestion from a searchname.
*
* @param string $searchname The full searchname
* @param string $query The user's query
* @return string|null The extracted suggestion
*/
private function extractSuggestion(string $searchname, string $query): ?string
{
// Clean up the searchname - remove file extensions, quality tags at the end
$clean = preg_replace('/\.(mkv|avi|mp4|wmv|nfo|nzb|par2|rar|zip|r\d+)$/i', '', $searchname);
// Replace dots and underscores with spaces for readability
$clean = str_replace(['.', '_'], ' ', $clean);
// Remove multiple spaces
$clean = preg_replace('/\s+/', ' ', $clean);
$clean = trim($clean);
if (empty($clean)) {
return null;
}
// If the clean name is reasonable length, use it
if (strlen($clean) <= 80) {
return $clean;
}
// For very long names, try to extract the relevant part
// Find where the query matches and extract context around it
$pos = stripos($clean, $query);
if ($pos !== false) {
// Get up to 80 chars starting from the match position, or from beginning if match is early
$start = max(0, $pos - 10);
$extracted = substr($clean, $start, 80);
// Clean up - don't cut mid-word
if ($start > 0) {
$extracted = preg_replace('/^\S*\s/', '', $extracted);
}
$extracted = preg_replace('/\s\S*$/', '', $extracted);
return trim($extracted);
}
// Fallback: just truncate
return substr($clean, 0, 80);
}
/**
* Get spell correction suggestions ("Did you mean?").
*
* @param string $query The search query to check
* @param string|null $index Index to use for suggestions
* @return array<array{suggest: string, distance: int, docs: int}>
*/
public function suggest(string $query, ?string $index = null): array
{
$suggestConfig = $this->config['suggest'] ?? [
'enabled' => true,
'max_edits' => 4,
];
if (! ($suggestConfig['enabled'] ?? true)) {
return [];
}
$query = trim($query);
if (empty($query)) {
return [];
}
$index = $index ?? ($this->config['indexes']['releases'] ?? 'releases_rt');
$cacheKey = 'manticore:suggest:'.md5($index.$query);
$cached = Cache::get($cacheKey);
if ($cached !== null) {
return $cached;
}
$suggestions = [];
try {
// Try native CALL SUGGEST first
$result = $this->manticoreSearch->suggest([
'table' => $index,
'body' => [
'query' => $query,
'options' => [
'limit' => 5,
'max_edits' => $suggestConfig['max_edits'] ?? 4,
],
],
]);
if (! empty($result) && is_array($result)) {
foreach ($result as $item) {
if (isset($item['suggest']) && $item['suggest'] !== $query) {
$suggestions[] = [
'suggest' => $item['suggest'],
'distance' => $item['distance'] ?? 0,
'docs' => $item['docs'] ?? 0,
];
}
}
}
} catch (\Throwable $e) {
if (config('app.debug')) {
Log::debug('ManticoreSearch native suggest failed: '.$e->getMessage());
}
}
// If native suggest didn't return results, try a fuzzy search fallback
if (empty($suggestions)) {
$suggestions = $this->suggestFallback($query, $index);
}
if (! empty($suggestions)) {
Cache::put($cacheKey, $suggestions, now()->addMinutes(5));
}
return $suggestions;
}
/**
* Fallback suggest using similar searchname matches.
*
* @param string $query The search query
* @param string $index Index to search
* @return array<array{suggest: string, distance: int, docs: int}>
*/
private function suggestFallback(string $query, string $index): array
{
try {
$escapedQuery = self::escapeString($query);
if (empty($escapedQuery)) {
return [];
}
// Use relaxed search to find partial matches
$searchExpr = '@@relaxed @searchname '.$escapedQuery;
$search = (new Search($this->manticoreSearch))
->setTable($index)
->search($searchExpr)
->limit(20)
->stripBadUtf8(true);
$results = $search->get();
// Extract common terms from the results that differ from the query
$termCounts = [];
foreach ($results as $doc) {
$data = $doc->getData();
$searchname = $data['searchname'] ?? '';
// Extract words from searchname
$words = preg_split('/[\s.\-_]+/', strtolower($searchname));
foreach ($words as $word) {
if (strlen($word) >= 3 && $word !== strtolower($query)) {
// Check if word is similar to query (within edit distance)
$distance = levenshtein(strtolower($query), $word);
if ($distance > 0 && $distance <= 3) {
if (! isset($termCounts[$word])) {
$termCounts[$word] = ['count' => 0, 'distance' => $distance];
}
$termCounts[$word]['count']++;
}
}
}
}
// Sort by count (most common first)
uasort($termCounts, fn ($a, $b) => $b['count'] - $a['count']);
$suggestions = [];
foreach (array_slice($termCounts, 0, 5, true) as $term => $data) {
$suggestions[] = [
'suggest' => $term,
'distance' => $data['distance'],
'docs' => $data['count'],
];
}
return $suggestions;
} catch (\Throwable $e) {
if (config('app.debug')) {
Log::debug('ManticoreSearch suggest fallback error: '.$e->getMessage());
}
return [];
}
}
}
+1
View File
@@ -12,6 +12,7 @@ return [
App\Providers\TvProcessingServiceProvider::class,
App\Providers\UserServiceProvider::class,
App\Providers\VoltServiceProvider::class,
App\Providers\SearchServiceProvider::class,
];
+2 -2
View File
@@ -4,7 +4,7 @@ require_once dirname(__DIR__, 3).DIRECTORY_SEPARATOR.'bootstrap/autoload.php';
use App\Models\Release;
use App\Models\UsenetGroup;
use Blacklight\ManticoreSearch;
use App\Services\Search\ManticoreSearchService;
use Blacklight\ReleaseCleaning;
$message =
@@ -52,7 +52,7 @@ if (\count($releases) === 0) {
}
$releaseCleaner = new ReleaseCleaning;
$manticore = new ManticoreSearch;
$manticore = app(ManticoreSearchService::class);
foreach ($releases as $release) {
echo '.';
+6 -6
View File
@@ -5,8 +5,8 @@ namespace Tests\Unit;
use App\Models\Release as ReleaseModel;
use App\Services\Categorization\CategorizationService;
use App\Services\MediaProcessingService;
use Blacklight\ElasticSearchSiteSearch;
use Blacklight\ManticoreSearch;
use App\Services\Search\ElasticSearchService;
use App\Services\Search\ManticoreSearchService;
use Blacklight\ReleaseExtra;
use Blacklight\ReleaseImage;
use FFMpeg\FFMpeg;
@@ -51,8 +51,8 @@ class MediaProcessingServiceTest extends TestCase
?MediaInfo $mediaInfo = null,
?ReleaseImage $releaseImage = null,
?ReleaseExtra $releaseExtra = null,
?ManticoreSearch $manticore = null,
?ElasticSearchSiteSearch $elastic = null,
?ManticoreSearchService $manticore = null,
?ElasticSearchService $elastic = null,
?CategorizationService $categorize = null
): MediaProcessingService {
$ffmpeg ??= Mockery::mock(FFMpeg::class);
@@ -60,8 +60,8 @@ class MediaProcessingServiceTest extends TestCase
$mediaInfo ??= Mockery::mock(MediaInfo::class);
$releaseImage ??= Mockery::mock(ReleaseImage::class);
$releaseExtra ??= Mockery::mock(ReleaseExtra::class);
$manticore ??= Mockery::mock(ManticoreSearch::class);
$elastic ??= Mockery::mock(ElasticSearchSiteSearch::class);
$manticore ??= Mockery::mock(ManticoreSearchService::class);
$elastic ??= Mockery::mock(ElasticSearchService::class);
$categorize ??= Mockery::mock(CategorizationService::class);
return new MediaProcessingService($ffmpeg, $ffprobe, $mediaInfo, $releaseImage, $releaseExtra, $manticore, $elastic, $categorize);