mirror of
https://github.com/NNTmux/newznab-tmux.git
synced 2026-08-28 17:01:16 +00:00
Create Search service
This commit is contained in:
@@ -2,8 +2,7 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use App\Facades\Search;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@@ -147,8 +146,7 @@ class DeleteReleases extends Command
|
||||
DB::delete('DELETE FROM releases WHERE id = ?', [$releaseId]);
|
||||
|
||||
// Delete from search indexes
|
||||
app(ManticoreSearchService::class)->deleteRelease($releaseId);
|
||||
app(ElasticSearchService::class)->deleteRelease($releaseId);
|
||||
Search::deleteRelease($releaseId);
|
||||
|
||||
$deleted++;
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Predb;
|
||||
use App\Models\Release;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -276,9 +276,8 @@ class NntmuxOffsetPopulate extends Command
|
||||
private function clearIndex(string $engine, string $index): void
|
||||
{
|
||||
if ($engine === 'manticore') {
|
||||
$manticore = app(ManticoreSearchService::class);
|
||||
$indexName = $index === 'releases' ? 'releases_rt' : 'predb_rt';
|
||||
$manticore->truncateRTIndex(Arr::wrap($indexName));
|
||||
Search::truncateIndex([$indexName]);
|
||||
$this->info("Truncated ManticoreSearch index: {$indexName}");
|
||||
} else {
|
||||
// For ElasticSearch, just clear the data instead of recreating the index
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Predb;
|
||||
use App\Models\Release;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -92,10 +92,9 @@ class NntmuxOffsetWorker extends Command
|
||||
$startTime = microtime(true);
|
||||
|
||||
if ($engine === 'manticore') {
|
||||
$manticore = app(ManticoreSearchService::class);
|
||||
$batchData = [];
|
||||
|
||||
$query->chunk($batchSize, function ($items) use ($manticore, $indexName, $transformer, &$processed, &$errors, &$batchData, $batchSize, $workerId) {
|
||||
$query->chunk($batchSize, function ($items) use ($indexName, $transformer, &$processed, &$errors, &$batchData, $batchSize, $workerId) {
|
||||
$this->info("Worker {$workerId}: Processing chunk of {$items->count()} items");
|
||||
|
||||
foreach ($items as $item) {
|
||||
@@ -104,7 +103,7 @@ class NntmuxOffsetWorker extends Command
|
||||
$processed++;
|
||||
|
||||
if (count($batchData) >= $batchSize) {
|
||||
$this->processManticoreBatch($manticore, $indexName, $batchData, $workerId);
|
||||
$this->processSearchBatch($batchData, $workerId);
|
||||
$this->info("Worker {$workerId}: Inserted batch of ".count($batchData).' records');
|
||||
$batchData = [];
|
||||
}
|
||||
@@ -117,7 +116,7 @@ class NntmuxOffsetWorker extends Command
|
||||
|
||||
// Process remaining items
|
||||
if (! empty($batchData)) {
|
||||
$this->processManticoreBatch($manticore, $indexName, $batchData, $workerId);
|
||||
$this->processSearchBatch($batchData, $workerId);
|
||||
$this->info("Worker {$workerId}: Inserted final batch of ".count($batchData).' records');
|
||||
}
|
||||
|
||||
@@ -246,21 +245,21 @@ class NntmuxOffsetWorker extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Process ManticoreSearch batch
|
||||
* Process search batch
|
||||
*/
|
||||
private function processManticoreBatch(ManticoreSearchService $manticore, string $indexName, array $data, int $workerId): void
|
||||
private function processSearchBatch(array $data, int $workerId): void
|
||||
{
|
||||
$retries = 3;
|
||||
$attempt = 0;
|
||||
|
||||
while ($attempt < $retries) {
|
||||
try {
|
||||
$manticore->manticoreSearch->table($indexName)->replaceDocuments($data);
|
||||
Search::bulkInsertReleases($data);
|
||||
break;
|
||||
} catch (Exception $e) {
|
||||
$attempt++;
|
||||
if ($attempt >= $retries) {
|
||||
throw new Exception("Worker {$workerId}: Failed to process ManticoreSearch batch after {$retries} attempts: {$e->getMessage()}");
|
||||
throw new Exception("Worker {$workerId}: Failed to process search batch after {$retries} attempts: {$e->getMessage()}");
|
||||
}
|
||||
usleep(200000); // 200ms delay before retry
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Predb;
|
||||
use App\Models\Release;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Arr;
|
||||
@@ -113,10 +113,10 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
*/
|
||||
private function handleOptimize(): int
|
||||
{
|
||||
$this->info('Optimizing ManticoreSearch indexes...');
|
||||
$this->info('Optimizing search indexes...');
|
||||
|
||||
try {
|
||||
app(ManticoreSearchService::class)->optimizeRTIndex();
|
||||
Search::optimizeIndex();
|
||||
$this->info('Optimization completed successfully!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
@@ -155,10 +155,9 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
|
||||
private function manticoreReleases(): int
|
||||
{
|
||||
$manticore = app(ManticoreSearchService::class);
|
||||
$indexName = 'releases_rt';
|
||||
|
||||
$manticore->truncateRTIndex(Arr::wrap($indexName));
|
||||
Search::truncateIndex([$indexName]);
|
||||
|
||||
$total = Release::count();
|
||||
if (! $total) {
|
||||
@@ -208,10 +207,9 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
*/
|
||||
private function manticorePredb(): int
|
||||
{
|
||||
$manticore = app(ManticoreSearchService::class);
|
||||
$indexName = 'predb_rt';
|
||||
|
||||
$manticore->truncateRTIndex([$indexName]);
|
||||
Search::truncateIndex([$indexName]);
|
||||
|
||||
$total = Predb::count();
|
||||
if (! $total) {
|
||||
@@ -244,7 +242,6 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
*/
|
||||
private function processManticoreData(string $indexName, int $total, $query, callable $transformer): int
|
||||
{
|
||||
$manticore = app(ManticoreSearchService::class);
|
||||
$chunkSize = $this->getChunkSize();
|
||||
$batchSize = $this->getBatchSize();
|
||||
|
||||
@@ -252,7 +249,7 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
$this->setGroupConcatMaxLen();
|
||||
|
||||
$this->info(sprintf(
|
||||
"Populating ManticoreSearch index '%s' with %s rows using chunks of %s and batch size of %s.",
|
||||
"Populating search index '%s' with %s rows using chunks of %s and batch size of %s.",
|
||||
$indexName,
|
||||
number_format($total),
|
||||
number_format($chunkSize),
|
||||
@@ -268,7 +265,7 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
$batchData = [];
|
||||
|
||||
try {
|
||||
$query->chunk($chunkSize, function ($items) use ($manticore, $indexName, $transformer, $bar, &$processedCount, &$errorCount, $batchSize, &$batchData) {
|
||||
$query->chunk($chunkSize, function ($items) use ($indexName, $transformer, $bar, &$processedCount, &$errorCount, $batchSize, &$batchData) {
|
||||
foreach ($items as $item) {
|
||||
try {
|
||||
$batchData[] = $transformer($item);
|
||||
@@ -276,7 +273,7 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
|
||||
// Process in optimized batch sizes
|
||||
if (count($batchData) >= $batchSize) {
|
||||
$this->processBatch($manticore, $indexName, $batchData);
|
||||
$this->processBatch($indexName, $batchData);
|
||||
$batchData = [];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
@@ -291,7 +288,7 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
|
||||
// Process remaining items
|
||||
if (! empty($batchData)) {
|
||||
$this->processBatch($manticore, $indexName, $batchData);
|
||||
$this->processBatch($indexName, $batchData);
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
@@ -300,7 +297,7 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
if ($errorCount > 0) {
|
||||
$this->warn("Completed with {$errorCount} errors out of {$processedCount} processed items.");
|
||||
} else {
|
||||
$this->info('ManticoreSearch population completed successfully!');
|
||||
$this->info('Search index population completed successfully!');
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
@@ -485,16 +482,16 @@ class NntmuxPopulateSearchIndexes extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Process ManticoreSearch batch with retry logic
|
||||
* Process search index batch with retry logic
|
||||
*/
|
||||
private function processBatch(ManticoreSearchService $manticore, string $indexName, array $data): void
|
||||
private function processBatch(string $indexName, array $data): void
|
||||
{
|
||||
$retries = 3;
|
||||
$attempt = 0;
|
||||
|
||||
while ($attempt < $retries) {
|
||||
try {
|
||||
$manticore->manticoreSearch->table($indexName)->replaceDocuments($data);
|
||||
Search::bulkInsertReleases($data);
|
||||
break;
|
||||
} catch (Exception $e) {
|
||||
$attempt++;
|
||||
|
||||
@@ -2,13 +2,11 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Release;
|
||||
use App\Models\ReleaseFile;
|
||||
use App\Services\ReleaseImageService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use Blacklight\NZB;
|
||||
use Elasticsearch;
|
||||
use Elasticsearch\Common\Exceptions\Missing404Exception;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
@@ -52,21 +50,8 @@ class NntmuxRemoveBadReleases extends Command
|
||||
$nzbPath = (new NZB)->getNZBPath($badRelease->guid);
|
||||
File::delete($nzbPath);
|
||||
(new ReleaseImageService)->delete($badRelease->guid);
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$params = [
|
||||
'index' => 'releases',
|
||||
'id' => $badRelease->id,
|
||||
];
|
||||
|
||||
try {
|
||||
Elasticsearch::delete($params);
|
||||
} catch (Missing404Exception $e) {
|
||||
// we do nothing here just catch the error, we don't care if release is missing from ES, we are deleting it anyway
|
||||
}
|
||||
} else {
|
||||
// Delete from Manticore
|
||||
app(ManticoreSearchService::class)->deleteRelease($badRelease->id);
|
||||
}
|
||||
// Delete from search index
|
||||
Search::deleteRelease($badRelease->id);
|
||||
$badRelease->delete();
|
||||
}
|
||||
Release::query()->where('passwordstatus', '=', -2)->delete();
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\UsenetGroup;
|
||||
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 {
|
||||
app(ManticoreSearchService::class)->truncateRTIndex(['releases_rt', 'predb_rt']);
|
||||
Search::truncateIndex(['releases_rt', 'predb_rt']);
|
||||
}
|
||||
|
||||
$this->info('Deleting nzbfiles subfolders.');
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use App\Services\Search\SearchService;
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
/**
|
||||
* Search Facade - provides static access to the SearchService.
|
||||
*
|
||||
* @method static bool isAutocompleteEnabled()
|
||||
* @method static bool isSuggestEnabled()
|
||||
* @method static string getReleasesIndex()
|
||||
* @method static string getPredbIndex()
|
||||
* @method static void insertRelease(array $parameters)
|
||||
* @method static void updateRelease(int|string $releaseID)
|
||||
* @method static void deleteRelease(int $id)
|
||||
* @method static void insertPredb(array $parameters)
|
||||
* @method static void updatePreDb(array $parameters)
|
||||
* @method static void deletePreDb(int $id)
|
||||
* @method static array searchReleases(array|string $phrases, int $limit = 1000)
|
||||
* @method static array searchPredb(array|string $searchTerm)
|
||||
* @method static array autocomplete(string $query, ?string $index = null)
|
||||
* @method static array suggest(string $query, ?string $index = null)
|
||||
* @method static bool isAvailable()
|
||||
* @method static string getCurrentDriver()
|
||||
* @method static string escapeString(string $string)
|
||||
* @method static void truncateIndex(array|string $indexes)
|
||||
* @method static void optimizeIndex()
|
||||
* @method static array bulkInsertReleases(array $releases)
|
||||
*
|
||||
* @see \App\Services\Search\SearchService
|
||||
*/
|
||||
class Search extends Facade
|
||||
{
|
||||
/**
|
||||
* Get the registered name of the component.
|
||||
*/
|
||||
protected static function getFacadeAccessor(): string
|
||||
{
|
||||
return SearchService::class;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use App\Facades\Search;
|
||||
use Blacklight\ColorCLI;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laravel\Scout\Searchable;
|
||||
|
||||
@@ -199,12 +197,7 @@ class Predb extends Model
|
||||
->select('predb.*', 'releases.guid')
|
||||
->orderByDesc('predb.predate');
|
||||
if (! empty($search)) {
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$ids = app(ElasticSearchService::class)->predbIndexSearch($search);
|
||||
} else {
|
||||
$manticore = app(ManticoreSearchService::class);
|
||||
$ids = Arr::get($manticore->searchIndexes('predb_rt', $search, ['title']), 'id');
|
||||
}
|
||||
$ids = Search::searchPredb($search);
|
||||
$sql->whereIn('predb.id', $ids);
|
||||
}
|
||||
|
||||
|
||||
+4
-20
@@ -2,8 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use App\Facades\Search;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@@ -135,11 +134,7 @@ class Release extends Model
|
||||
]
|
||||
);
|
||||
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
app(ElasticSearchService::class)->insertRelease($parameters);
|
||||
} else {
|
||||
app(ManticoreSearchService::class)->insertRelease($parameters);
|
||||
}
|
||||
Search::insertRelease($parameters);
|
||||
|
||||
return $parameters['id'];
|
||||
}
|
||||
@@ -172,11 +167,7 @@ class Release extends Model
|
||||
]
|
||||
);
|
||||
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
app(ElasticSearchService::class)->updateRelease($id);
|
||||
} else {
|
||||
app(ManticoreSearchService::class)->updateRelease($id);
|
||||
}
|
||||
Search::updateRelease($id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -396,14 +387,7 @@ class Release extends Model
|
||||
preg_match('/(^\w+[-_. ].+?\.(\d+p)).+/i', $rel['searchname'], $similar);
|
||||
|
||||
if (! empty($similar)) {
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$searchResult = app(ElasticSearchService::class)->indexSearch($similar[1], 10);
|
||||
} else {
|
||||
$searchResult = app(ManticoreSearchService::class)->searchIndexes('releases_rt', $similar[1]);
|
||||
if (! empty($searchResult)) {
|
||||
$searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
|
||||
}
|
||||
}
|
||||
$searchResult = Search::searchReleases($similar[1], 10);
|
||||
|
||||
if (empty($searchResult)) {
|
||||
return false;
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use App\Facades\Search;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -125,11 +124,7 @@ class ReleaseFile extends Model
|
||||
if (\strlen($hash) === 32) {
|
||||
ParHash::insertOrIgnore(['releases_id' => $id, 'hash' => $hash]);
|
||||
}
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
app(ElasticSearchService::class)->updateRelease($id);
|
||||
} else {
|
||||
app(ManticoreSearchService::class)->updateRelease($id);
|
||||
}
|
||||
Search::updateRelease($id);
|
||||
}
|
||||
|
||||
return $insert ?? 0;
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Services\Search\Contracts\SearchDriverInterface;
|
||||
use App\Services\Search\Contracts\SearchServiceInterface;
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use App\Services\Search\Drivers\ElasticSearchDriver;
|
||||
use App\Services\Search\Drivers\ManticoreSearchDriver;
|
||||
use App\Services\Search\SearchService;
|
||||
use Illuminate\Contracts\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class SearchServiceProvider extends ServiceProvider
|
||||
@@ -14,27 +17,44 @@ class SearchServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
// Register ManticoreSearchService as singleton
|
||||
$this->app->singleton(ManticoreSearchService::class, function ($app) {
|
||||
return new ManticoreSearchService();
|
||||
// Merge the search configuration
|
||||
$this->mergeConfigFrom(
|
||||
base_path('config/search.php'),
|
||||
'search'
|
||||
);
|
||||
|
||||
// Register the SearchService as a singleton (Manager pattern)
|
||||
$this->app->singleton(SearchService::class, function (Application $app) {
|
||||
return new SearchService($app);
|
||||
});
|
||||
|
||||
// Register ElasticSearchService as singleton
|
||||
$this->app->singleton(ElasticSearchService::class, function ($app) {
|
||||
return new ElasticSearchService();
|
||||
// Bind the interface to the SearchService
|
||||
$this->app->singleton(SearchServiceInterface::class, function (Application $app) {
|
||||
return $app->make(SearchService::class);
|
||||
});
|
||||
|
||||
// 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);
|
||||
// Bind SearchDriverInterface to current driver
|
||||
$this->app->bind(SearchDriverInterface::class, function (Application $app) {
|
||||
return $app->make(SearchService::class)->driver();
|
||||
});
|
||||
|
||||
// Register alias for ManticoreSearch (for backward compatibility)
|
||||
$this->app->alias(ManticoreSearchService::class, 'manticore');
|
||||
// Register individual drivers as singletons for direct access
|
||||
$this->app->singleton(ManticoreSearchDriver::class, function (Application $app) {
|
||||
$config = config('search.drivers.manticore', []);
|
||||
|
||||
return new ManticoreSearchDriver($config);
|
||||
});
|
||||
|
||||
$this->app->singleton(ElasticSearchDriver::class, function (Application $app) {
|
||||
$config = config('search.drivers.elasticsearch', []);
|
||||
|
||||
return new ElasticSearchDriver($config);
|
||||
});
|
||||
|
||||
// Register aliases (using prefixed names to avoid conflicts with other packages)
|
||||
$this->app->alias(SearchService::class, 'search');
|
||||
$this->app->alias(ManticoreSearchDriver::class, 'search.manticore');
|
||||
$this->app->alias(ElasticSearchDriver::class, 'search.elasticsearch');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,14 +2,13 @@
|
||||
|
||||
namespace App\Services\AdditionalProcessing;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Category;
|
||||
use App\Models\Release;
|
||||
use App\Services\AdditionalProcessing\Config\ProcessingConfiguration;
|
||||
use App\Services\AdditionalProcessing\DTO\ReleaseProcessingContext;
|
||||
use App\Services\Categorization\CategorizationService;
|
||||
use App\Services\ReleaseImageService;
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use App\Services\ReleaseExtraService;
|
||||
use FFMpeg\Coordinate\Dimension;
|
||||
use FFMpeg\Coordinate\TimeCode;
|
||||
@@ -31,8 +30,6 @@ class MediaExtractionService
|
||||
private ?FFMpeg $ffmpeg = null;
|
||||
private ?FFProbe $ffprobe = null;
|
||||
private ?MediaInfo $mediaInfo = null;
|
||||
private ?ManticoreSearchService $manticore = null;
|
||||
private ?ElasticSearchService $elasticsearch = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly ProcessingConfiguration $config,
|
||||
@@ -323,11 +320,7 @@ class MediaExtractionService
|
||||
'proc_pp' => 1,
|
||||
]);
|
||||
|
||||
if ($this->config->elasticsearchEnabled) {
|
||||
$this->elasticsearch()->updateRelease($context->release->id);
|
||||
} else {
|
||||
$this->manticore()->updateRelease($context->release->id);
|
||||
}
|
||||
Search::updateRelease($context->release->id);
|
||||
|
||||
if ($this->config->echoCLI) {
|
||||
NameFixer::echoChangedReleaseName([
|
||||
|
||||
@@ -7,8 +7,6 @@ use App\Models\Predb;
|
||||
use App\Models\Release;
|
||||
use App\Models\ReleaseFile;
|
||||
use App\Services\ReleaseImageService;
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use App\Services\Releases\ReleaseBrowseService;
|
||||
use App\Services\AdditionalProcessing\Config\ProcessingConfiguration;
|
||||
use App\Services\AdditionalProcessing\DTO\ReleaseProcessingContext;
|
||||
@@ -29,8 +27,6 @@ use Illuminate\Support\Facades\Log;
|
||||
*/
|
||||
class ReleaseFileManager
|
||||
{
|
||||
private ?ManticoreSearchService $manticore = null;
|
||||
private ?ElasticSearchService $elasticsearch = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly ProcessingConfiguration $config,
|
||||
@@ -145,11 +141,7 @@ class ReleaseFileManager
|
||||
*/
|
||||
public function updateSearchIndex(int $releaseId): void
|
||||
{
|
||||
if ($this->config->elasticsearchEnabled) {
|
||||
$this->elasticsearch()->updateRelease($releaseId);
|
||||
} else {
|
||||
$this->manticore()->updateRelease($releaseId);
|
||||
}
|
||||
\App\Facades\Search::updateRelease($releaseId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -244,11 +236,7 @@ class ReleaseFileManager
|
||||
|
||||
// Delete from search index
|
||||
try {
|
||||
if ($this->config->elasticsearchEnabled) {
|
||||
$this->elasticsearch()->deleteRelease($id);
|
||||
} else {
|
||||
$this->manticore()->deleteRelease(['i' => $id, 'g' => $guid]);
|
||||
}
|
||||
\App\Facades\Search::deleteRelease($id);
|
||||
} catch (\Throwable) {
|
||||
// Ignore
|
||||
}
|
||||
@@ -590,21 +578,5 @@ class ReleaseFileManager
|
||||
|
||||
return $hasGroupSuffix || ($hasTV && $hasQuality) || ($hasYear && ($hasQuality || $hasTV)) || $hasXXX;
|
||||
}
|
||||
|
||||
private function manticore(): ManticoreSearchService
|
||||
{
|
||||
if ($this->manticore === null) {
|
||||
$this->manticore = app(ManticoreSearchService::class);
|
||||
}
|
||||
return $this->manticore;
|
||||
}
|
||||
|
||||
private function elasticsearch(): ElasticSearchService
|
||||
{
|
||||
if ($this->elasticsearch === null) {
|
||||
$this->elasticsearch = app(ElasticSearchService::class);
|
||||
}
|
||||
return $this->elasticsearch;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Facades\Search;
|
||||
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;
|
||||
|
||||
@@ -51,9 +50,6 @@ class IRCScraper extends IRCClient
|
||||
*/
|
||||
protected string|false $_titleIgnoreRegex = false;
|
||||
|
||||
protected ManticoreSearchService $manticoreSearch;
|
||||
|
||||
private ElasticSearchService $elasticsearch;
|
||||
|
||||
/**
|
||||
* Construct.
|
||||
@@ -108,8 +104,6 @@ class IRCScraper extends IRCClient
|
||||
$this->_titleIgnoreRegex = (string) config('irc_settings.scrape_irc_title_ignore');
|
||||
}
|
||||
|
||||
$this->elasticsearch = app(ElasticSearchService::class);
|
||||
$this->manticoreSearch = app(ManticoreSearchService::class);
|
||||
|
||||
$this->_groupList = [];
|
||||
$this->_silent = $silent;
|
||||
@@ -384,11 +378,7 @@ class IRCScraper extends IRCClient
|
||||
'source' => $this->_curPre['source'] ?? null,
|
||||
];
|
||||
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$this->elasticsearch->insertPreDb($parameters);
|
||||
} else {
|
||||
$this->manticoreSearch->insertPredb($parameters);
|
||||
}
|
||||
Search::insertPredb($parameters);
|
||||
|
||||
$this->_doEcho(true);
|
||||
} catch (\Exception $e) {
|
||||
@@ -448,11 +438,7 @@ class IRCScraper extends IRCClient
|
||||
'source' => $this->_curPre['source'] ?? null,
|
||||
];
|
||||
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$this->elasticsearch->updatePreDb($parameters);
|
||||
} else {
|
||||
$this->manticoreSearch->updatePreDb($parameters);
|
||||
}
|
||||
Search::updatePreDb($parameters);
|
||||
}
|
||||
|
||||
$this->_doEcho(false);
|
||||
|
||||
@@ -2,11 +2,10 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Category;
|
||||
use App\Models\Release;
|
||||
use App\Services\Categorization\CategorizationService;
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use FFMpeg\Coordinate\Dimension;
|
||||
use FFMpeg\Coordinate\TimeCode;
|
||||
use FFMpeg\FFMpeg;
|
||||
@@ -26,8 +25,6 @@ class MediaProcessingService
|
||||
private readonly MediaInfo $mediaInfo,
|
||||
private readonly ReleaseImageService $releaseImage,
|
||||
private readonly ReleaseExtraService $releaseExtra,
|
||||
private readonly ManticoreSearchService $manticore,
|
||||
private readonly ElasticSearchService $elasticsearch,
|
||||
private readonly CategorizationService $categorize,
|
||||
) {}
|
||||
|
||||
@@ -283,11 +280,7 @@ class MediaProcessingService
|
||||
'isrenamed' => 1,
|
||||
'proc_pp' => 1,
|
||||
]);
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$this->elasticsearch->updateRelease($release->id);
|
||||
} else {
|
||||
$this->manticore->updateRelease($release->id);
|
||||
}
|
||||
Search::updateRelease($release->id);
|
||||
}
|
||||
$this->releaseExtra->addFromXml($release->id, $xmlArray);
|
||||
$retVal = true;
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Services\NameFixing;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Category;
|
||||
use App\Models\Release;
|
||||
use App\Services\NameFixing\Contracts\NameSourceFixerInterface;
|
||||
@@ -11,8 +12,6 @@ use App\Services\NameFixing\DTO\NameFixResult;
|
||||
use App\Services\NameFixing\Extractors\NfoNameExtractor;
|
||||
use App\Services\NameFixing\Extractors\FileNameExtractor;
|
||||
use App\Services\NNTP\NNTPService;
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use Blacklight\ColorCLI;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
@@ -50,8 +49,6 @@ class NameFixingService
|
||||
protected FileNameExtractor $fileExtractor;
|
||||
protected FileNameCleaner $fileNameCleaner;
|
||||
protected FilePrioritizer $filePrioritizer;
|
||||
protected ManticoreSearchService $manticore;
|
||||
protected ElasticSearchService $elasticsearch;
|
||||
protected ColorCLI $colorCLI;
|
||||
protected bool $echoOutput;
|
||||
|
||||
@@ -70,8 +67,6 @@ class NameFixingService
|
||||
?FileNameExtractor $fileExtractor = null,
|
||||
?FileNameCleaner $fileNameCleaner = null,
|
||||
?FilePrioritizer $filePrioritizer = null,
|
||||
?ManticoreSearchService $manticore = null,
|
||||
?ElasticSearchService $elasticsearch = null,
|
||||
?ColorCLI $colorCLI = null
|
||||
) {
|
||||
$this->updateService = $updateService ?? new ReleaseUpdateService();
|
||||
@@ -80,8 +75,6 @@ class NameFixingService
|
||||
$this->fileExtractor = $fileExtractor ?? new FileNameExtractor();
|
||||
$this->fileNameCleaner = $fileNameCleaner ?? new FileNameCleaner();
|
||||
$this->filePrioritizer = $filePrioritizer ?? new FilePrioritizer();
|
||||
$this->manticore = $manticore ?? app(ManticoreSearchService::class);
|
||||
$this->elasticsearch = $elasticsearch ?? app(ElasticSearchService::class);
|
||||
$this->colorCLI = $colorCLI ?? new ColorCLI();
|
||||
$this->echoOutput = config('nntmux.echocli');
|
||||
|
||||
@@ -758,40 +751,21 @@ class NameFixingService
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$results = $this->elasticsearch->searchPreDb($fileName);
|
||||
foreach ($results as $hit) {
|
||||
if (!empty($hit)) {
|
||||
$this->updateService->updateRelease(
|
||||
$release,
|
||||
$hit['title'],
|
||||
'PreDb: Filename match',
|
||||
$echo,
|
||||
$type,
|
||||
$nameStatus,
|
||||
$show,
|
||||
$hit['id']
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$predbSearch = Arr::get($this->manticore->searchIndexes('predb_rt', $fileName, ['filename', 'title']), 'data');
|
||||
if (!empty($predbSearch)) {
|
||||
foreach ($predbSearch as $hit) {
|
||||
if (!empty($hit)) {
|
||||
$this->updateService->updateRelease(
|
||||
$release,
|
||||
$hit['title'],
|
||||
'PreDb: Filename match',
|
||||
$echo,
|
||||
$type,
|
||||
$nameStatus,
|
||||
$show
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$results = Search::searchPredb($fileName);
|
||||
foreach ($results as $hit) {
|
||||
if (!empty($hit)) {
|
||||
$hitData = is_array($hit) ? $hit : (array)$hit;
|
||||
$this->updateService->updateRelease(
|
||||
$release,
|
||||
$hitData['title'] ?? '',
|
||||
'PreDb: Filename match',
|
||||
$echo,
|
||||
$type,
|
||||
$nameStatus,
|
||||
$show,
|
||||
$hitData['id'] ?? null
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -809,40 +783,21 @@ class NameFixingService
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$results = $this->elasticsearch->searchPreDb($fileName);
|
||||
foreach ($results as $hit) {
|
||||
if (!empty($hit)) {
|
||||
$this->updateService->updateRelease(
|
||||
$release,
|
||||
$hit['title'],
|
||||
'PreDb: Title match',
|
||||
$echo,
|
||||
$type,
|
||||
$nameStatus,
|
||||
$show,
|
||||
$hit['id']
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$results = Arr::get($this->manticore->searchIndexes('predb_rt', $fileName, ['title']), 'data');
|
||||
if (!empty($results)) {
|
||||
foreach ($results as $hit) {
|
||||
if (!empty($hit)) {
|
||||
$this->updateService->updateRelease(
|
||||
$release,
|
||||
$hit['title'],
|
||||
'PreDb: Title match',
|
||||
$echo,
|
||||
$type,
|
||||
$nameStatus,
|
||||
$show
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
$results = Search::searchPredb($fileName);
|
||||
foreach ($results as $hit) {
|
||||
if (!empty($hit)) {
|
||||
$hitData = is_array($hit) ? $hit : (array)$hit;
|
||||
$this->updateService->updateRelease(
|
||||
$release,
|
||||
$hitData['title'] ?? '',
|
||||
'PreDb: Title match',
|
||||
$echo,
|
||||
$type,
|
||||
$nameStatus,
|
||||
$show,
|
||||
$hitData['id'] ?? null
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1265,21 +1220,18 @@ class NameFixingService
|
||||
|
||||
$preMatch = $this->preMatch($cleanedFileName);
|
||||
if ($preMatch[0] === true) {
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$results = $this->elasticsearch->searchPreDb($preMatch[1]);
|
||||
} else {
|
||||
$results = Arr::get($this->manticore->searchIndexes('predb_rt', $preMatch[1], ['filename', 'title']), 'data');
|
||||
}
|
||||
$results = Search::searchPredb($preMatch[1]);
|
||||
|
||||
if (!empty($results)) {
|
||||
foreach ($results as $result) {
|
||||
if (!empty($result)) {
|
||||
$preFtMatch = $this->preMatch($result['filename'] ?? '');
|
||||
$resultData = is_array($result) ? $result : (array)$result;
|
||||
$preFtMatch = $this->preMatch($resultData['filename'] ?? '');
|
||||
if ($preFtMatch[0] === true) {
|
||||
if ($result['title'] !== $release->searchname) {
|
||||
$this->updateService->updateRelease($release, $result['title'], 'file matched source: ' . $result['source'], $echo, 'PreDB file match, ', $nameStatus, $show);
|
||||
if ($resultData['title'] !== $release->searchname) {
|
||||
$this->updateService->updateRelease($release, $resultData['title'], 'file matched source: ' . ($resultData['source'] ?? ''), $echo, 'PreDB file match, ', $nameStatus, $show);
|
||||
} else {
|
||||
$this->updateService->updateSingleColumn('predb_id', $result['id'], $release->releases_id);
|
||||
$this->updateService->updateSingleColumn('predb_id', $resultData['id'] ?? 0, $release->releases_id);
|
||||
}
|
||||
$matching++;
|
||||
return $matching;
|
||||
|
||||
@@ -4,14 +4,13 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Services\NameFixing;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Category;
|
||||
use App\Models\Predb;
|
||||
use App\Models\Release;
|
||||
use App\Models\UsenetGroup;
|
||||
use App\Services\Categorization\CategorizationService;
|
||||
use App\Services\ReleaseCleaningService;
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use Blacklight\ColorCLI;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
@@ -49,8 +48,6 @@ class ReleaseUpdateService
|
||||
public const IS_RENAMED_DONE = 1;
|
||||
|
||||
protected CategorizationService $category;
|
||||
protected ManticoreSearchService $manticore;
|
||||
protected ElasticSearchService $elasticsearch;
|
||||
protected FileNameCleaner $fileNameCleaner;
|
||||
protected ColorCLI $colorCLI;
|
||||
protected bool $echoOutput;
|
||||
@@ -82,14 +79,10 @@ class ReleaseUpdateService
|
||||
|
||||
public function __construct(
|
||||
?CategorizationService $category = null,
|
||||
?ManticoreSearchService $manticore = null,
|
||||
?ElasticSearchService $elasticsearch = null,
|
||||
?FileNameCleaner $fileNameCleaner = null,
|
||||
?ColorCLI $colorCLI = null
|
||||
) {
|
||||
$this->category = $category ?? new CategorizationService();
|
||||
$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');
|
||||
@@ -298,11 +291,7 @@ class ReleaseUpdateService
|
||||
}
|
||||
|
||||
// Update search index
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$this->elasticsearch->updateRelease($release->releases_id);
|
||||
} else {
|
||||
$this->manticore->updateRelease($release->releases_id);
|
||||
}
|
||||
Search::updateRelease($release->releases_id);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -584,13 +584,7 @@ class ReleaseRemoverService
|
||||
*/
|
||||
private function performSearch(string $regexMatch): array|string
|
||||
{
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
return app(\App\Services\Search\ElasticSearchService::class)->indexSearch($regexMatch, 100);
|
||||
}
|
||||
|
||||
$searchResult = app(\App\Services\Search\ManticoreSearchService::class)->searchIndexes('releases_rt', $regexMatch, ['name,searchname']);
|
||||
|
||||
return ! empty($searchResult) ? Arr::wrap(Arr::get($searchResult, 'id')) : '';
|
||||
return \App\Facades\Search::searchReleases($regexMatch, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
namespace App\Services\Releases;
|
||||
|
||||
use App\Facades\Search;
|
||||
use App\Models\Release;
|
||||
use App\Services\ReleaseImageService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use Blacklight\NZB;
|
||||
use Elasticsearch;
|
||||
use Elasticsearch\Common\Exceptions\Missing404Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
@@ -16,12 +14,8 @@ use Illuminate\Support\Facades\File;
|
||||
*/
|
||||
class ReleaseManagementService
|
||||
{
|
||||
private ManticoreSearchService $manticoreSearch;
|
||||
|
||||
public function __construct(
|
||||
ManticoreSearchService $manticoreSearch
|
||||
) {
|
||||
$this->manticoreSearch = $manticoreSearch;
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,36 +52,17 @@ class ReleaseManagementService
|
||||
// Delete images.
|
||||
$releaseImage->delete($identifiers['g']);
|
||||
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
if ($identifiers['i'] === false) {
|
||||
$identifiers['i'] = Release::query()->where('guid', $identifiers['g'])->first(['id']);
|
||||
if ($identifiers['i'] !== null) {
|
||||
$identifiers['i'] = $identifiers['i']['id'];
|
||||
}
|
||||
// Get release ID if not provided
|
||||
if ($identifiers['i'] === false) {
|
||||
$release = Release::query()->where('guid', $identifiers['g'])->first(['id']);
|
||||
if ($release !== null) {
|
||||
$identifiers['i'] = $release->id;
|
||||
}
|
||||
if ($identifiers['i'] !== null) {
|
||||
$params = [
|
||||
'index' => 'releases',
|
||||
'id' => $identifiers['i'],
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
Elasticsearch::delete($params);
|
||||
} catch (Missing404Exception $e) {
|
||||
// we do nothing here just catch the error, we don't care if release is missing from ES, we are deleting it anyway
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Delete from Manticore
|
||||
if ($identifiers['i'] === false) {
|
||||
$release = Release::query()->where('guid', $identifiers['g'])->first(['id']);
|
||||
if ($release !== null) {
|
||||
$identifiers['i'] = $release->id;
|
||||
}
|
||||
}
|
||||
if (!empty($identifiers['i'])) {
|
||||
$this->manticoreSearch->deleteRelease((int) $identifiers['i']);
|
||||
}
|
||||
// Delete from search index
|
||||
if (!empty($identifiers['i'])) {
|
||||
Search::deleteRelease((int) $identifiers['i']);
|
||||
}
|
||||
|
||||
// Delete from DB.
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
namespace App\Services\Releases;
|
||||
|
||||
use App\Facades\Search;
|
||||
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 Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -25,15 +24,8 @@ class ReleaseSearchService
|
||||
public const PASSWD_NONE = 0;
|
||||
public const PASSWD_RAR = 1;
|
||||
|
||||
private ManticoreSearchService $manticoreSearch;
|
||||
private ElasticSearchService $elasticSearch;
|
||||
|
||||
public function __construct(
|
||||
ManticoreSearchService $manticoreSearch,
|
||||
ElasticSearchService $elasticSearch
|
||||
) {
|
||||
$this->manticoreSearch = $manticoreSearch;
|
||||
$this->elasticSearch = $elasticSearch;
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,26 +141,10 @@ class ReleaseSearchService
|
||||
// Early return if searching with no results
|
||||
$searchResult = [];
|
||||
if ($searchName !== -1 && $searchName !== '' && $searchName !== null) {
|
||||
// Try Elasticsearch first if enabled
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$searchResult = $this->elasticSearch->indexSearchApi($searchName, $limit);
|
||||
}
|
||||
// Use the unified Search facade
|
||||
$searchResult = Search::searchReleases($searchName, $limit);
|
||||
|
||||
// Fall back to Manticore if Elasticsearch didn't return results
|
||||
if (empty($searchResult)) {
|
||||
$searchResult = $this->manticoreSearch->searchIndexes('releases_rt', $searchName, ['searchname']);
|
||||
if (config('app.debug')) {
|
||||
Log::debug('apiSearch: Manticore searchIndexes result', [
|
||||
'searchName' => $searchName,
|
||||
'result' => $searchResult,
|
||||
]);
|
||||
}
|
||||
if (! empty($searchResult)) {
|
||||
$searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to MySQL if both Elasticsearch and Manticore failed (only if enabled)
|
||||
// Fall back to MySQL if search engine returned no results (only if enabled)
|
||||
if (empty($searchResult) && config('nntmux.mysql_search_fallback', false) === true) {
|
||||
if (config('app.debug')) {
|
||||
Log::debug('apiSearch: Falling back to MySQL search');
|
||||
@@ -588,20 +564,10 @@ class ReleaseSearchService
|
||||
{
|
||||
$searchResult = [];
|
||||
if (! empty($name)) {
|
||||
// Try Elasticsearch first if enabled
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$searchResult = $this->elasticSearch->indexSearchTMA($name, $limit);
|
||||
}
|
||||
// Use the unified Search facade
|
||||
$searchResult = Search::searchReleases($name, $limit);
|
||||
|
||||
// Fall back to Manticore if Elasticsearch didn't return results
|
||||
if (empty($searchResult)) {
|
||||
$searchResult = $this->manticoreSearch->searchIndexes('releases_rt', $name, ['searchname']);
|
||||
if (! empty($searchResult)) {
|
||||
$searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to MySQL if both Elasticsearch and Manticore failed (only if enabled)
|
||||
// Fall back to MySQL if search engine returned no results (only if enabled)
|
||||
if (empty($searchResult) && config('nntmux.mysql_search_fallback', false) === true) {
|
||||
$searchResult = $this->performMySQLSearch(['searchname' => $name], $limit);
|
||||
}
|
||||
@@ -666,20 +632,10 @@ class ReleaseSearchService
|
||||
// Early return if searching by name yields no results
|
||||
$searchResult = [];
|
||||
if (! empty($name)) {
|
||||
// Try Elasticsearch first if enabled
|
||||
if (config('nntmux.elasticsearch_enabled') === true) {
|
||||
$searchResult = $this->elasticSearch->indexSearchTMA($name, $limit);
|
||||
}
|
||||
// Use the unified Search facade
|
||||
$searchResult = Search::searchReleases($name, $limit);
|
||||
|
||||
// Fall back to Manticore if Elasticsearch didn't return results
|
||||
if (empty($searchResult)) {
|
||||
$searchResult = $this->manticoreSearch->searchIndexes('releases_rt', $name, ['searchname']);
|
||||
if (! empty($searchResult)) {
|
||||
$searchResult = Arr::wrap(Arr::get($searchResult, 'id'));
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to MySQL if both Elasticsearch and Manticore failed (only if enabled)
|
||||
// Fall back to MySQL if search engine returned no results (only if enabled)
|
||||
if (empty($searchResult) && config('nntmux.mysql_search_fallback', false) === true) {
|
||||
$searchResult = $this->performMySQLSearch(['searchname' => $name], $limit);
|
||||
}
|
||||
@@ -832,49 +788,28 @@ class ReleaseSearchService
|
||||
return [];
|
||||
}
|
||||
|
||||
$phrases = array_values($searchFields);
|
||||
|
||||
$esEnabled = config('nntmux.elasticsearch_enabled');
|
||||
if (config('app.debug')) {
|
||||
Log::debug('performIndexSearch: starting search', [
|
||||
'elasticsearch_enabled' => $esEnabled,
|
||||
'elasticsearch_enabled_type' => gettype($esEnabled),
|
||||
'searchFields' => $searchFields,
|
||||
'phrases' => $phrases,
|
||||
'limit' => $limit,
|
||||
]);
|
||||
}
|
||||
|
||||
// Try Elasticsearch first if enabled
|
||||
if ($esEnabled === true) {
|
||||
$result = $this->elasticSearch->indexSearch($phrases, $limit);
|
||||
if (config('app.debug')) {
|
||||
Log::debug('performIndexSearch: Elasticsearch result count', ['count' => count($result)]);
|
||||
}
|
||||
// If Elasticsearch returned results, use them
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
// Otherwise fall through to Manticore
|
||||
if (config('app.debug')) {
|
||||
Log::debug('performIndexSearch: Elasticsearch returned empty, falling back to Manticore');
|
||||
}
|
||||
}
|
||||
|
||||
// Try Manticore search
|
||||
$searchResult = $this->manticoreSearch->searchIndexes('releases_rt', '', [], $searchFields);
|
||||
|
||||
// Use the unified Search facade - pass searchFields with keys preserved
|
||||
$result = Search::searchReleases($searchFields, $limit);
|
||||
if (config('app.debug')) {
|
||||
Log::debug('performIndexSearch: Manticore result', [
|
||||
'result' => $searchResult,
|
||||
]);
|
||||
Log::debug('performIndexSearch: Search result count', ['count' => count($result)]);
|
||||
}
|
||||
|
||||
if (!empty($searchResult) && !empty($searchResult['id'])) {
|
||||
return Arr::wrap(Arr::get($searchResult, 'id'));
|
||||
// If search returned results, use them
|
||||
if (!empty($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Fallback to MySQL LIKE search when both Elasticsearch and Manticore are unavailable (only if enabled)
|
||||
// Fallback to MySQL LIKE search when search engine is unavailable (only if enabled)
|
||||
if (config('nntmux.mysql_search_fallback', false) === true) {
|
||||
if (config('app.debug')) {
|
||||
Log::debug('performIndexSearch: Falling back to MySQL search');
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Search\Contracts;
|
||||
|
||||
/**
|
||||
* Interface for search drivers.
|
||||
*
|
||||
* Extends SearchServiceInterface with additional driver-specific methods.
|
||||
*/
|
||||
interface SearchDriverInterface extends SearchServiceInterface
|
||||
{
|
||||
/**
|
||||
* Get the driver name.
|
||||
*/
|
||||
public function getDriverName(): string;
|
||||
|
||||
/**
|
||||
* Check if the search service is available.
|
||||
*/
|
||||
public function isAvailable(): bool;
|
||||
|
||||
/**
|
||||
* Escape a search string for the specific driver.
|
||||
*/
|
||||
public static function escapeString(string $string): string;
|
||||
}
|
||||
|
||||
@@ -98,5 +98,31 @@ interface SearchServiceInterface
|
||||
* @return array<array{suggest: string, distance: int, docs: int}>
|
||||
*/
|
||||
public function suggest(string $query, ?string $index = null): array;
|
||||
/**
|
||||
* Truncate/clear an index (remove all documents).
|
||||
*
|
||||
* @param array|string $indexes Index name(s) to truncate
|
||||
*/
|
||||
public function truncateIndex(array|string $indexes): void;
|
||||
|
||||
/**
|
||||
* Optimize index for better search performance.
|
||||
*/
|
||||
public function optimizeIndex(): void;
|
||||
|
||||
/**
|
||||
* Bulk insert multiple releases into the index.
|
||||
*
|
||||
* @param array $releases Array of release data arrays
|
||||
* @return array Results with 'success' and 'errors' counts
|
||||
*/
|
||||
public function bulkInsertReleases(array $releases): array;
|
||||
|
||||
/**
|
||||
* Delete a predb record from the index.
|
||||
*
|
||||
* @param int $id Predb ID
|
||||
*/
|
||||
public function deletePreDb(int $id): void;
|
||||
}
|
||||
|
||||
|
||||
+175
-42
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Search;
|
||||
namespace App\Services\Search\Drivers;
|
||||
|
||||
use App\Models\Release;
|
||||
use App\Services\Search\Contracts\SearchServiceInterface;
|
||||
use App\Services\Search\Contracts\SearchDriverInterface;
|
||||
use Elasticsearch\Client;
|
||||
use Elasticsearch\ClientBuilder;
|
||||
use Elasticsearch\Common\Exceptions\ElasticsearchException;
|
||||
@@ -16,12 +16,12 @@ use Illuminate\Support\Facades\Log;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Elasticsearch site search service for releases and predb data.
|
||||
* Elasticsearch driver for full-text search functionality.
|
||||
*
|
||||
* Provides search functionality for the releases and predb indices with
|
||||
* caching, connection pooling, and automatic reconnection.
|
||||
*/
|
||||
class ElasticSearchService implements SearchServiceInterface
|
||||
class ElasticSearchDriver implements SearchDriverInterface
|
||||
{
|
||||
private const CACHE_TTL_MINUTES = 5;
|
||||
|
||||
@@ -35,10 +35,6 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
|
||||
private const DEFAULT_CONNECT_TIMEOUT = 5;
|
||||
|
||||
private const INDEX_RELEASES = 'releases';
|
||||
|
||||
private const INDEX_PREDB = 'predb';
|
||||
|
||||
private const AUTOCOMPLETE_CACHE_MINUTES = 10;
|
||||
|
||||
private const AUTOCOMPLETE_MAX_RESULTS = 10;
|
||||
@@ -51,6 +47,29 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
|
||||
private static ?int $availabilityCacheTime = null;
|
||||
|
||||
protected array $config;
|
||||
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = ! empty($config) ? $config : config('search.drivers.elasticsearch');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the driver name.
|
||||
*/
|
||||
public function getDriverName(): string
|
||||
{
|
||||
return 'elasticsearch';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Elasticsearch is available.
|
||||
*/
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return $this->isElasticsearchAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create an Elasticsearch client with proper cURL configuration.
|
||||
*
|
||||
@@ -66,14 +85,12 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
throw new RuntimeException('cURL extension is not loaded');
|
||||
}
|
||||
|
||||
$config = config('elasticsearch.connections.default');
|
||||
|
||||
if (empty($config)) {
|
||||
if (empty($this->config)) {
|
||||
throw new RuntimeException('Elasticsearch configuration not found');
|
||||
}
|
||||
|
||||
$clientBuilder = ClientBuilder::create();
|
||||
$hosts = $this->buildHostsArray($config['hosts'] ?? []);
|
||||
$hosts = $this->buildHostsArray($this->config['hosts'] ?? []);
|
||||
|
||||
if (empty($hosts)) {
|
||||
throw new RuntimeException('No Elasticsearch hosts configured');
|
||||
@@ -88,12 +105,12 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
$clientBuilder->setHosts($hosts);
|
||||
$clientBuilder->setHandler(new CurlHandler);
|
||||
$clientBuilder->setConnectionParams([
|
||||
'timeout' => config('elasticsearch.connections.default.timeout', self::DEFAULT_TIMEOUT),
|
||||
'connect_timeout' => config('elasticsearch.connections.default.connect_timeout', self::DEFAULT_CONNECT_TIMEOUT),
|
||||
'timeout' => $this->config['timeout'] ?? self::DEFAULT_TIMEOUT,
|
||||
'connect_timeout' => $this->config['connect_timeout'] ?? self::DEFAULT_CONNECT_TIMEOUT,
|
||||
]);
|
||||
|
||||
// Enable retries for better resilience
|
||||
$clientBuilder->setRetries(2);
|
||||
$clientBuilder->setRetries($this->config['retries'] ?? 2);
|
||||
|
||||
self::$client = $clientBuilder->build();
|
||||
|
||||
@@ -198,7 +215,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
*/
|
||||
public function isAutocompleteEnabled(): bool
|
||||
{
|
||||
return config('elasticsearch.autocomplete.enabled', true) && $this->isElasticsearchAvailable();
|
||||
return ($this->config['autocomplete']['enabled'] ?? true) && $this->isElasticsearchAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,7 +223,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
*/
|
||||
public function isSuggestEnabled(): bool
|
||||
{
|
||||
return config('elasticsearch.suggest.enabled', true) && $this->isElasticsearchAvailable();
|
||||
return ($this->config['suggest']['enabled'] ?? true) && $this->isElasticsearchAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,7 +231,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
*/
|
||||
public function getReleasesIndex(): string
|
||||
{
|
||||
return self::INDEX_RELEASES;
|
||||
return $this->config['indexes']['releases'] ?? 'releases';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,7 +239,25 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
*/
|
||||
public function getPredbIndex(): string
|
||||
{
|
||||
return self::INDEX_PREDB;
|
||||
return $this->config['indexes']['predb'] ?? 'predb';
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special characters for Elasticsearch.
|
||||
*/
|
||||
public static function escapeString(string $string): string
|
||||
{
|
||||
if (empty($string) || $string === '*') {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Replace dots with spaces for release name searches
|
||||
$string = str_replace('.', ' ', $string);
|
||||
|
||||
// Remove multiple consecutive spaces
|
||||
$string = preg_replace('/\s+/', ' ', trim($string));
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,12 +275,12 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
}
|
||||
|
||||
$query = trim($query);
|
||||
$minLength = (int) config('elasticsearch.autocomplete.min_length', self::AUTOCOMPLETE_MIN_LENGTH);
|
||||
$minLength = (int) ($this->config['autocomplete']['min_length'] ?? self::AUTOCOMPLETE_MIN_LENGTH);
|
||||
if (strlen($query) < $minLength) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$index = $index ?? self::INDEX_RELEASES;
|
||||
$index = $index ?? $this->getReleasesIndex();
|
||||
$cacheKey = 'es:autocomplete:'.md5($index.$query);
|
||||
|
||||
$cached = Cache::get($cacheKey);
|
||||
@@ -254,7 +289,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
}
|
||||
|
||||
$suggestions = [];
|
||||
$maxResults = (int) config('elasticsearch.autocomplete.max_results', self::AUTOCOMPLETE_MAX_RESULTS);
|
||||
$maxResults = (int) ($this->config['autocomplete']['max_results'] ?? self::AUTOCOMPLETE_MAX_RESULTS);
|
||||
|
||||
try {
|
||||
$client = $this->getClient();
|
||||
@@ -333,7 +368,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
}
|
||||
|
||||
if (! empty($suggestions)) {
|
||||
$cacheMinutes = (int) config('elasticsearch.autocomplete.cache_minutes', self::AUTOCOMPLETE_CACHE_MINUTES);
|
||||
$cacheMinutes = (int) ($this->config['autocomplete']['cache_minutes'] ?? self::AUTOCOMPLETE_CACHE_MINUTES);
|
||||
Cache::put($cacheKey, $suggestions, now()->addMinutes($cacheMinutes));
|
||||
}
|
||||
|
||||
@@ -358,7 +393,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
return [];
|
||||
}
|
||||
|
||||
$index = $index ?? self::INDEX_RELEASES;
|
||||
$index = $index ?? $this->getReleasesIndex();
|
||||
$cacheKey = 'es:suggest:'.md5($index.$query);
|
||||
|
||||
$cached = Cache::get($cacheKey);
|
||||
@@ -424,7 +459,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
}
|
||||
|
||||
if (! empty($suggestions)) {
|
||||
Cache::put($cacheKey, $suggestions, now()->addMinutes(self::CACHE_TTL_MINUTES));
|
||||
Cache::put($cacheKey, $suggestions, now()->addMinutes($this->config['cache_minutes'] ?? self::CACHE_TTL_MINUTES));
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
@@ -552,13 +587,31 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
/**
|
||||
* Search releases index.
|
||||
*
|
||||
* @param array|string $phrases Search phrases
|
||||
* @param array|string $phrases Search phrases - can be a string, indexed array of terms, or associative array with field names
|
||||
* @param int $limit Maximum number of results
|
||||
* @return array Array of release IDs
|
||||
*/
|
||||
public function searchReleases(array|string $phrases, int $limit = 1000): array
|
||||
{
|
||||
$result = $this->indexSearch($phrases, $limit);
|
||||
// Normalize the input to a search string
|
||||
if (is_string($phrases)) {
|
||||
$searchString = $phrases;
|
||||
} elseif (is_array($phrases)) {
|
||||
// Check if it's an associative array (has string keys like 'searchname')
|
||||
$isAssociative = count(array_filter(array_keys($phrases), 'is_string')) > 0;
|
||||
|
||||
if ($isAssociative) {
|
||||
// Extract values from associative array
|
||||
$searchString = implode(' ', array_values($phrases));
|
||||
} else {
|
||||
// Indexed array - combine values
|
||||
$searchString = implode(' ', $phrases);
|
||||
}
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = $this->indexSearch($searchString, $limit);
|
||||
|
||||
return is_array($result) ? $result : $result->toArray();
|
||||
}
|
||||
@@ -621,14 +674,14 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
|
||||
try {
|
||||
$search = $this->buildSearchQuery(
|
||||
index: self::INDEX_RELEASES,
|
||||
index: $this->getReleasesIndex(),
|
||||
keywords: $keywords,
|
||||
fields: ['searchname^2', 'plainsearchname^1.5', 'fromname', 'filename', 'name^1.2'],
|
||||
limit: $limit
|
||||
);
|
||||
|
||||
$result = $this->executeSearch($search);
|
||||
Cache::put($cacheKey, $result, now()->addMinutes(self::CACHE_TTL_MINUTES));
|
||||
Cache::put($cacheKey, $result, now()->addMinutes($this->config['cache_minutes'] ?? self::CACHE_TTL_MINUTES));
|
||||
|
||||
return $result;
|
||||
|
||||
@@ -668,14 +721,14 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
|
||||
try {
|
||||
$search = $this->buildSearchQuery(
|
||||
index: self::INDEX_RELEASES,
|
||||
index: $this->getReleasesIndex(),
|
||||
keywords: $keywords,
|
||||
fields: ['searchname^2', 'plainsearchname^1.5', 'fromname', 'filename', 'name^1.2'],
|
||||
limit: $limit
|
||||
);
|
||||
|
||||
$result = $this->executeSearch($search);
|
||||
Cache::put($cacheKey, $result, now()->addMinutes(self::CACHE_TTL_MINUTES));
|
||||
Cache::put($cacheKey, $result, now()->addMinutes($this->config['cache_minutes'] ?? self::CACHE_TTL_MINUTES));
|
||||
|
||||
return $result;
|
||||
|
||||
@@ -715,7 +768,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
|
||||
try {
|
||||
$search = $this->buildSearchQuery(
|
||||
index: self::INDEX_RELEASES,
|
||||
index: $this->getReleasesIndex(),
|
||||
keywords: $keywords,
|
||||
fields: ['searchname^2', 'plainsearchname^1.5'],
|
||||
limit: $limit,
|
||||
@@ -725,7 +778,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
);
|
||||
|
||||
$result = $this->executeSearch($search);
|
||||
Cache::put($cacheKey, $result, now()->addMinutes(self::CACHE_TTL_MINUTES));
|
||||
Cache::put($cacheKey, $result, now()->addMinutes($this->config['cache_minutes'] ?? self::CACHE_TTL_MINUTES));
|
||||
|
||||
return $result;
|
||||
|
||||
@@ -764,7 +817,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
|
||||
try {
|
||||
$search = $this->buildSearchQuery(
|
||||
index: self::INDEX_PREDB,
|
||||
index: $this->getPredbIndex(),
|
||||
keywords: $keywords,
|
||||
fields: ['title^2', 'filename'],
|
||||
limit: 1000,
|
||||
@@ -775,7 +828,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
);
|
||||
|
||||
$result = $this->executeSearch($search, fullResults: true);
|
||||
Cache::put($cacheKey, $result, now()->addMinutes(self::CACHE_TTL_MINUTES));
|
||||
Cache::put($cacheKey, $result, now()->addMinutes($this->config['cache_minutes'] ?? self::CACHE_TTL_MINUTES));
|
||||
|
||||
return $result;
|
||||
|
||||
@@ -840,7 +893,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
|
||||
$params['body'][] = [
|
||||
'index' => [
|
||||
'_index' => self::INDEX_RELEASES,
|
||||
'_index' => $this->getReleasesIndex(),
|
||||
'_id' => $release['id'],
|
||||
],
|
||||
];
|
||||
@@ -916,7 +969,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
],
|
||||
'doc_as_upsert' => true,
|
||||
],
|
||||
'index' => self::INDEX_RELEASES,
|
||||
'index' => $this->getReleasesIndex(),
|
||||
'id' => $release->id,
|
||||
];
|
||||
|
||||
@@ -960,7 +1013,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
'source' => $parameters['source'] ?? '',
|
||||
'filename' => $parameters['filename'] ?? '',
|
||||
],
|
||||
'index' => self::INDEX_PREDB,
|
||||
'index' => $this->getPredbIndex(),
|
||||
'id' => $parameters['id'],
|
||||
];
|
||||
|
||||
@@ -1006,7 +1059,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
],
|
||||
'doc_as_upsert' => true,
|
||||
],
|
||||
'index' => self::INDEX_PREDB,
|
||||
'index' => $this->getPredbIndex(),
|
||||
'id' => $parameters['id'],
|
||||
];
|
||||
|
||||
@@ -1042,7 +1095,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
try {
|
||||
$client = $this->getClient();
|
||||
$client->delete([
|
||||
'index' => self::INDEX_RELEASES,
|
||||
'index' => $this->getReleasesIndex(),
|
||||
'id' => $id,
|
||||
]);
|
||||
|
||||
@@ -1080,7 +1133,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
try {
|
||||
$client = $this->getClient();
|
||||
$client->delete([
|
||||
'index' => self::INDEX_PREDB,
|
||||
'index' => $this->getPredbIndex(),
|
||||
'id' => $id,
|
||||
]);
|
||||
|
||||
@@ -1121,6 +1174,86 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate/clear an index (remove all documents).
|
||||
* Implements SearchServiceInterface::truncateIndex
|
||||
*
|
||||
* @param array|string $indexes Index name(s) to truncate
|
||||
*/
|
||||
public function truncateIndex(array|string $indexes): void
|
||||
{
|
||||
if (! $this->isElasticsearchAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$indexArray = is_array($indexes) ? $indexes : [$indexes];
|
||||
|
||||
foreach ($indexArray as $index) {
|
||||
try {
|
||||
$client = $this->getClient();
|
||||
|
||||
// Check if index exists
|
||||
if (! $client->indices()->exists(['index' => $index])) {
|
||||
Log::info("ElasticSearch truncateIndex: index {$index} does not exist, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Delete all documents from the index
|
||||
$client->deleteByQuery([
|
||||
'index' => $index,
|
||||
'body' => [
|
||||
'query' => ['match_all' => (object) []],
|
||||
],
|
||||
'conflicts' => 'proceed',
|
||||
]);
|
||||
|
||||
// Force refresh to ensure deletions are visible
|
||||
$client->indices()->refresh(['index' => $index]);
|
||||
|
||||
Log::info("ElasticSearch: Truncated index {$index}");
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("ElasticSearch truncateIndex error for {$index}: ".$e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize index for better search performance.
|
||||
* Implements SearchServiceInterface::optimizeIndex
|
||||
*/
|
||||
public function optimizeIndex(): void
|
||||
{
|
||||
if (! $this->isElasticsearchAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->getClient();
|
||||
|
||||
// Force merge the releases index
|
||||
$client->indices()->forcemerge([
|
||||
'index' => $this->getReleasesIndex(),
|
||||
'max_num_segments' => 1,
|
||||
]);
|
||||
|
||||
// Force merge the predb index
|
||||
$client->indices()->forcemerge([
|
||||
'index' => $this->getPredbIndex(),
|
||||
'max_num_segments' => 1,
|
||||
]);
|
||||
|
||||
// Refresh both indexes
|
||||
$client->indices()->refresh(['index' => $this->getReleasesIndex()]);
|
||||
$client->indices()->refresh(['index' => $this->getPredbIndex()]);
|
||||
|
||||
Log::info('ElasticSearch: Optimized indexes');
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('ElasticSearch optimizeIndex error: '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cluster health information.
|
||||
*
|
||||
@@ -1281,7 +1414,7 @@ class ElasticSearchService implements SearchServiceInterface
|
||||
'add_date' => now()->format('Y-m-d H:i:s'),
|
||||
'post_date' => $parameters['postdate'] ?? now()->format('Y-m-d H:i:s'),
|
||||
],
|
||||
'index' => self::INDEX_RELEASES,
|
||||
'index' => $this->getReleasesIndex(),
|
||||
'id' => $parameters['id'],
|
||||
];
|
||||
}
|
||||
+146
-11
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Search;
|
||||
namespace App\Services\Search\Drivers;
|
||||
|
||||
use App\Models\Release;
|
||||
use App\Services\Search\Contracts\SearchServiceInterface;
|
||||
use App\Services\Search\Contracts\SearchDriverInterface;
|
||||
use Blacklight\ColorCLI;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -14,11 +14,11 @@ use Manticoresearch\Exceptions\RuntimeException;
|
||||
use Manticoresearch\Search;
|
||||
|
||||
/**
|
||||
* ManticoreSearch service for full-text search functionality.
|
||||
* ManticoreSearch driver for full-text search functionality.
|
||||
*/
|
||||
class ManticoreSearchService implements SearchServiceInterface
|
||||
class ManticoreSearchDriver implements SearchDriverInterface
|
||||
{
|
||||
protected mixed $config;
|
||||
protected array $config;
|
||||
|
||||
protected array $connection;
|
||||
|
||||
@@ -31,15 +31,41 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
/**
|
||||
* Establishes a connection to ManticoreSearch HTTP port.
|
||||
*/
|
||||
public function __construct()
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
$this->config = config('manticoresearch');
|
||||
$this->config = ! empty($config) ? $config : config('search.drivers.manticore');
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the driver name.
|
||||
*/
|
||||
public function getDriverName(): string
|
||||
{
|
||||
return 'manticore';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ManticoreSearch is available.
|
||||
*/
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
try {
|
||||
$status = $this->manticoreSearch->nodes()->status();
|
||||
|
||||
return ! empty($status);
|
||||
} catch (\Throwable $e) {
|
||||
if (config('app.debug')) {
|
||||
Log::debug('ManticoreSearch not available: '.$e->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if autocomplete is enabled.
|
||||
*/
|
||||
@@ -171,6 +197,75 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a predb record from the index.
|
||||
*
|
||||
* @param int $id Predb ID
|
||||
*/
|
||||
public function deletePreDb(int $id): void
|
||||
{
|
||||
if (empty($id)) {
|
||||
Log::warning('ManticoreSearch: Cannot delete predb without ID');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->manticoreSearch->table($this->config['indexes']['predb'])
|
||||
->deleteDocument($id);
|
||||
} catch (ResponseException $e) {
|
||||
Log::error('ManticoreSearch deletePreDb error: '.$e->getMessage(), [
|
||||
'id' => $id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk insert multiple releases into the index.
|
||||
*
|
||||
* @param array $releases Array of release data arrays
|
||||
* @return array Results with 'success' and 'errors' counts
|
||||
*/
|
||||
public function bulkInsertReleases(array $releases): array
|
||||
{
|
||||
if (empty($releases)) {
|
||||
return ['success' => 0, 'errors' => 0];
|
||||
}
|
||||
|
||||
$success = 0;
|
||||
$errors = 0;
|
||||
|
||||
$documents = [];
|
||||
foreach ($releases as $release) {
|
||||
if (empty($release['id'])) {
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$documents[] = [
|
||||
'id' => $release['id'],
|
||||
'name' => $release['name'] ?? '',
|
||||
'searchname' => $release['searchname'] ?? '',
|
||||
'fromname' => $release['fromname'] ?? '',
|
||||
'categories_id' => (string) ($release['categories_id'] ?? ''),
|
||||
'filename' => $release['filename'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
if (! empty($documents)) {
|
||||
try {
|
||||
$this->manticoreSearch->table($this->config['indexes']['releases'])
|
||||
->replaceDocuments($documents);
|
||||
$success = count($documents);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('ManticoreSearch bulkInsertReleases error: '.$e->getMessage());
|
||||
$errors += count($documents);
|
||||
}
|
||||
}
|
||||
|
||||
return ['success' => $success, 'errors' => $errors];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete release from Manticore RT tables by GUID.
|
||||
*
|
||||
@@ -311,6 +406,18 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate/clear an index (remove all documents).
|
||||
* Implements SearchServiceInterface::truncateIndex
|
||||
*
|
||||
* @param array|string $indexes Index name(s) to truncate
|
||||
*/
|
||||
public function truncateIndex(array|string $indexes): void
|
||||
{
|
||||
$indexArray = is_array($indexes) ? $indexes : [$indexes];
|
||||
$this->truncateRTIndex($indexArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create index if it doesn't exist
|
||||
*/
|
||||
@@ -363,16 +470,41 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize index for better search performance.
|
||||
* Implements SearchServiceInterface::optimizeIndex
|
||||
*/
|
||||
public function optimizeIndex(): void
|
||||
{
|
||||
$this->optimizeRTIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search releases index.
|
||||
*
|
||||
* @param array|string $phrases Search phrases
|
||||
* @param array|string $phrases Search phrases - can be a string, indexed array of terms, or associative array with field names
|
||||
* @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];
|
||||
if (is_string($phrases)) {
|
||||
// Simple string search - search in searchname field
|
||||
$searchArray = ['searchname' => $phrases];
|
||||
} elseif (is_array($phrases)) {
|
||||
// Check if it's an associative array (has string keys like 'searchname')
|
||||
$isAssociative = count(array_filter(array_keys($phrases), 'is_string')) > 0;
|
||||
|
||||
if ($isAssociative) {
|
||||
// Already has field names as keys
|
||||
$searchArray = $phrases;
|
||||
} else {
|
||||
// Indexed array - combine values and search in searchname
|
||||
$searchArray = ['searchname' => implode(' ', $phrases)];
|
||||
}
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = $this->searchIndexes($this->getReleasesIndex(), '', [], $searchArray);
|
||||
|
||||
@@ -427,6 +559,7 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
'cached_ids_count' => count($cached['id'] ?? []),
|
||||
]);
|
||||
}
|
||||
|
||||
return $cached;
|
||||
}
|
||||
|
||||
@@ -448,6 +581,7 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
if (config('app.debug')) {
|
||||
Log::debug('ManticoreSearch::searchIndexes no terms after escaping searchArray');
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
} elseif (! empty($searchString)) {
|
||||
@@ -456,6 +590,7 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
if (config('app.debug')) {
|
||||
Log::debug('ManticoreSearch::searchIndexes escapedSearch is empty');
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -554,7 +689,7 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
];
|
||||
|
||||
// Cache results for 5 minutes
|
||||
Cache::put($cacheKey, $result, now()->addMinutes(5));
|
||||
Cache::put($cacheKey, $result, now()->addMinutes($this->config['cache_minutes'] ?? 5));
|
||||
|
||||
return $result;
|
||||
}
|
||||
@@ -773,7 +908,7 @@ class ManticoreSearchService implements SearchServiceInterface
|
||||
}
|
||||
|
||||
if (! empty($suggestions)) {
|
||||
Cache::put($cacheKey, $suggestions, now()->addMinutes(5));
|
||||
Cache::put($cacheKey, $suggestions, now()->addMinutes($this->config['cache_minutes'] ?? 5));
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
@@ -0,0 +1,232 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Search;
|
||||
|
||||
use App\Services\Search\Contracts\SearchDriverInterface;
|
||||
use App\Services\Search\Contracts\SearchServiceInterface;
|
||||
use App\Services\Search\Drivers\ElasticSearchDriver;
|
||||
use App\Services\Search\Drivers\ManticoreSearchDriver;
|
||||
use Illuminate\Support\Manager;
|
||||
|
||||
/**
|
||||
* Search Service Manager - handles driver resolution for search functionality.
|
||||
*
|
||||
* This service manager provides a unified interface for full-text search operations,
|
||||
* supporting multiple backends (ManticoreSearch, Elasticsearch) that can be configured
|
||||
* via the SEARCH_DRIVER environment variable.
|
||||
*/
|
||||
class SearchService extends Manager implements SearchServiceInterface
|
||||
{
|
||||
/**
|
||||
* Get the default driver name.
|
||||
*/
|
||||
public function getDefaultDriver(): string
|
||||
{
|
||||
return $this->config->get('search.default', 'manticore');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the ManticoreSearch driver instance.
|
||||
*/
|
||||
protected function createManticoreDriver(): SearchDriverInterface
|
||||
{
|
||||
$config = $this->config->get('search.drivers.manticore', []);
|
||||
|
||||
return new ManticoreSearchDriver($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the Elasticsearch driver instance.
|
||||
*/
|
||||
protected function createElasticsearchDriver(): SearchDriverInterface
|
||||
{
|
||||
$config = $this->config->get('search.drivers.elasticsearch', []);
|
||||
|
||||
return new ElasticSearchDriver($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a driver instance.
|
||||
*
|
||||
* @param string|null $driver
|
||||
* @return SearchDriverInterface
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function driver($driver = null): SearchDriverInterface
|
||||
{
|
||||
return parent::driver($driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current driver is available.
|
||||
*/
|
||||
public function isAvailable(): bool
|
||||
{
|
||||
return $this->driver()->isAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current driver name.
|
||||
*/
|
||||
public function getCurrentDriver(): string
|
||||
{
|
||||
return $this->driver()->getDriverName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape a search string using the current driver's escape method.
|
||||
*/
|
||||
public function escapeString(string $string): string
|
||||
{
|
||||
$driverClass = get_class($this->driver());
|
||||
|
||||
return $driverClass::escapeString($string);
|
||||
}
|
||||
|
||||
// Implement SearchServiceInterface methods by delegating to the current driver
|
||||
|
||||
/**
|
||||
* Check if autocomplete is enabled.
|
||||
*/
|
||||
public function isAutocompleteEnabled(): bool
|
||||
{
|
||||
return $this->driver()->isAutocompleteEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if suggest is enabled.
|
||||
*/
|
||||
public function isSuggestEnabled(): bool
|
||||
{
|
||||
return $this->driver()->isSuggestEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the releases index name.
|
||||
*/
|
||||
public function getReleasesIndex(): string
|
||||
{
|
||||
return $this->driver()->getReleasesIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the predb index name.
|
||||
*/
|
||||
public function getPredbIndex(): string
|
||||
{
|
||||
return $this->driver()->getPredbIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a release into the search index.
|
||||
*/
|
||||
public function insertRelease(array $parameters): void
|
||||
{
|
||||
$this->driver()->insertRelease($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a release in the search index.
|
||||
*/
|
||||
public function updateRelease(int|string $releaseID): void
|
||||
{
|
||||
$this->driver()->updateRelease($releaseID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a release from the search index.
|
||||
*/
|
||||
public function deleteRelease(int $id): void
|
||||
{
|
||||
$this->driver()->deleteRelease($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a predb record into the search index.
|
||||
*/
|
||||
public function insertPredb(array $parameters): void
|
||||
{
|
||||
$this->driver()->insertPredb($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a predb record in the search index.
|
||||
*/
|
||||
public function updatePreDb(array $parameters): void
|
||||
{
|
||||
$this->driver()->updatePreDb($parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the releases index.
|
||||
*/
|
||||
public function searchReleases(array|string $phrases, int $limit = 1000): array
|
||||
{
|
||||
return $this->driver()->searchReleases($phrases, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the predb index.
|
||||
*/
|
||||
public function searchPredb(array|string $searchTerm): array
|
||||
{
|
||||
return $this->driver()->searchPredb($searchTerm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get autocomplete suggestions for a search query.
|
||||
*/
|
||||
public function autocomplete(string $query, ?string $index = null): array
|
||||
{
|
||||
return $this->driver()->autocomplete($query, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get spell correction suggestions.
|
||||
*/
|
||||
public function suggest(string $query, ?string $index = null): array
|
||||
{
|
||||
return $this->driver()->suggest($query, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate/clear an index (remove all documents).
|
||||
*
|
||||
* @param array|string $indexes Index name(s) to truncate
|
||||
*/
|
||||
public function truncateIndex(array|string $indexes): void
|
||||
{
|
||||
$this->driver()->truncateIndex($indexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize index for better search performance.
|
||||
*/
|
||||
public function optimizeIndex(): void
|
||||
{
|
||||
$this->driver()->optimizeIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk insert multiple releases into the index.
|
||||
*
|
||||
* @param array $releases Array of release data arrays
|
||||
* @return array Results with 'success' and 'errors' counts
|
||||
*/
|
||||
public function bulkInsertReleases(array $releases): array
|
||||
{
|
||||
return $this->driver()->bulkInsertReleases($releases);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a predb record from the index.
|
||||
*
|
||||
* @param int $id Predb ID
|
||||
*/
|
||||
public function deletePreDb(int $id): void
|
||||
{
|
||||
$this->driver()->deletePreDb($id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Search Driver
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default search driver that will be used to
|
||||
| perform full-text search operations. Supported: "manticore", "elasticsearch"
|
||||
|
|
||||
*/
|
||||
'default' => env('SEARCH_DRIVER', 'manticore'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Search Driver Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure each search driver as needed.
|
||||
|
|
||||
*/
|
||||
'drivers' => [
|
||||
|
||||
'manticore' => [
|
||||
'driver' => 'manticore',
|
||||
'host' => env('MANTICORESEARCH_HOST', '127.0.0.1'),
|
||||
'port' => env('MANTICORESEARCH_PORT', 9308),
|
||||
'hosts' => env('MANTICORESEARCH_HOSTS', ''),
|
||||
'retries' => env('MANTICORESEARCH_RETRIES', 2),
|
||||
'indexes' => [
|
||||
'releases' => env('MANTICORESEARCH_INDEX_RELEASES', 'releases_rt'),
|
||||
'predb' => env('MANTICORESEARCH_INDEX_PREDB', 'predb_rt'),
|
||||
],
|
||||
'max_matches' => env('MANTICORESEARCH_MAX_MATCHES', 10000),
|
||||
'cache_minutes' => env('MANTICORESEARCH_CACHE_MINUTES', 5),
|
||||
'autocomplete' => [
|
||||
'enabled' => env('MANTICORESEARCH_AUTOCOMPLETE_ENABLED', true),
|
||||
'min_length' => env('MANTICORESEARCH_AUTOCOMPLETE_MIN_LENGTH', 2),
|
||||
'max_results' => env('MANTICORESEARCH_AUTOCOMPLETE_MAX_RESULTS', 10),
|
||||
'cache_minutes' => env('MANTICORESEARCH_AUTOCOMPLETE_CACHE_MINUTES', 10),
|
||||
],
|
||||
'suggest' => [
|
||||
'enabled' => env('MANTICORESEARCH_SUGGEST_ENABLED', true),
|
||||
'max_edits' => env('MANTICORESEARCH_SUGGEST_MAX_EDITS', 4),
|
||||
],
|
||||
],
|
||||
|
||||
'elasticsearch' => [
|
||||
'driver' => 'elasticsearch',
|
||||
'hosts' => [
|
||||
[
|
||||
'host' => env('ELASTICSEARCH_HOST', 'localhost'),
|
||||
'port' => env('ELASTICSEARCH_PORT', 9200),
|
||||
'scheme' => env('ELASTICSEARCH_SCHEME', null),
|
||||
'user' => env('ELASTICSEARCH_USER', null),
|
||||
'pass' => env('ELASTICSEARCH_PASS', null),
|
||||
],
|
||||
],
|
||||
'timeout' => env('ELASTICSEARCH_TIMEOUT', 10),
|
||||
'connect_timeout' => env('ELASTICSEARCH_CONNECT_TIMEOUT', 5),
|
||||
'retries' => env('ELASTICSEARCH_RETRIES', 2),
|
||||
'indexes' => [
|
||||
'releases' => 'releases',
|
||||
'predb' => 'predb',
|
||||
],
|
||||
'cache_minutes' => env('ELASTICSEARCH_CACHE_MINUTES', 5),
|
||||
'autocomplete' => [
|
||||
'enabled' => env('ELASTICSEARCH_AUTOCOMPLETE_ENABLED', true),
|
||||
'min_length' => env('ELASTICSEARCH_AUTOCOMPLETE_MIN_LENGTH', 2),
|
||||
'max_results' => env('ELASTICSEARCH_AUTOCOMPLETE_MAX_RESULTS', 10),
|
||||
'cache_minutes' => env('ELASTICSEARCH_AUTOCOMPLETE_CACHE_MINUTES', 10),
|
||||
],
|
||||
'suggest' => [
|
||||
'enabled' => env('ELASTICSEARCH_SUGGEST_ENABLED', true),
|
||||
],
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -5,9 +5,7 @@ namespace Tests\Unit;
|
||||
use App\Models\Release as ReleaseModel;
|
||||
use App\Services\Categorization\CategorizationService;
|
||||
use App\Services\MediaProcessingService;
|
||||
use App\Services\Search\ElasticSearchService;
|
||||
use App\Services\Search\ManticoreSearchService;
|
||||
use Blacklight\ReleaseExtra;
|
||||
use App\Services\ReleaseExtraService;
|
||||
use App\Services\ReleaseImageService;
|
||||
use FFMpeg\FFMpeg;
|
||||
use FFMpeg\FFProbe;
|
||||
@@ -50,21 +48,17 @@ class MediaProcessingServiceTest extends TestCase
|
||||
?FFProbe $ffprobe = null,
|
||||
?MediaInfo $mediaInfo = null,
|
||||
?ReleaseImageService $releaseImage = null,
|
||||
?ReleaseExtra $releaseExtra = null,
|
||||
?ManticoreSearchService $manticore = null,
|
||||
?ElasticSearchService $elastic = null,
|
||||
?ReleaseExtraService $releaseExtra = null,
|
||||
?CategorizationService $categorize = null
|
||||
): MediaProcessingService {
|
||||
$ffmpeg ??= Mockery::mock(FFMpeg::class);
|
||||
$ffprobe ??= Mockery::mock(FFProbe::class);
|
||||
$mediaInfo ??= Mockery::mock(MediaInfo::class);
|
||||
$releaseImage ??= Mockery::mock(ReleaseImageService::class);
|
||||
$releaseExtra ??= Mockery::mock(ReleaseExtra::class);
|
||||
$manticore ??= Mockery::mock(ManticoreSearchService::class);
|
||||
$elastic ??= Mockery::mock(ElasticSearchService::class);
|
||||
$releaseExtra ??= Mockery::mock(ReleaseExtraService::class);
|
||||
$categorize ??= Mockery::mock(CategorizationService::class);
|
||||
|
||||
return new MediaProcessingService($ffmpeg, $ffprobe, $mediaInfo, $releaseImage, $releaseExtra, $manticore, $elastic, $categorize);
|
||||
return new MediaProcessingService($ffmpeg, $ffprobe, $mediaInfo, $releaseImage, $releaseExtra, $categorize);
|
||||
}
|
||||
|
||||
#[WithoutErrorHandler]
|
||||
@@ -121,7 +115,7 @@ class MediaProcessingServiceTest extends TestCase
|
||||
$ffmpeg = Mockery::mock(FFMpeg::class);
|
||||
$ffmpeg->shouldReceive('open')->andReturn($openMock);
|
||||
|
||||
$releaseImage = Mockery::mock(ReleaseImage::class);
|
||||
$releaseImage = Mockery::mock(ReleaseImageService::class);
|
||||
$releaseImage->imgSavePath = $this->tmpDir;
|
||||
$releaseImage->shouldReceive('saveImage')->andReturn(1);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user