Passed
Push — master ( de512f...06cfc7 )
by Darko
06:05
created

ManticoreSearch::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 7
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Blacklight;
4
5
use App\Models\Release;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Facades\DB;
8
use Illuminate\Support\Facades\Log;
9
use Illuminate\Support\Str;
10
use Manticoresearch\Client;
11
use Manticoresearch\Exceptions\ResponseException;
12
use Manticoresearch\Exceptions\RuntimeException;
13
use Manticoresearch\Search;
14
15
/**
16
 * Class ManticoreSearch.
17
 */
18
class ManticoreSearch
19
{
20
    /**
21
     * @var \Illuminate\Config\Repository|mixed
22
     */
23
    protected mixed $config;
24
25
    protected array $connection;
26
27
    public Client $manticoresearch;
28
29
    public Search $search;
30
31
    private ColorCLI $cli;
32
33
    /**
34
     * Establishes a connection to ManticoreSearch HTTP port.
35
     */
36
    public function __construct()
37
    {
38
        $this->config = config('sphinxsearch');
39
        $this->connection = ['host' => $this->config['host'], 'port' => $this->config['port']];
40
        $this->manticoresearch = new Client($this->connection);
41
        $this->search = new Search($this->manticoresearch);
42
        $this->cli = new ColorCLI();
43
    }
44
45
    /**
46
     * Insert release into ManticoreSearch releases_rt realtime index
47
     */
48
    public function insertRelease(array $parameters): void
49
    {
50
        if ($parameters['id']) {
51
            try {
52
                $this->manticoresearch->index($this->config['indexes']['releases'])
53
                    ->replaceDocument(
54
                        [
55
                            'name' => $parameters['name'],
56
                            'searchname' => $parameters['searchname'],
57
                            'fromname' => $parameters['fromname'],
58
                            'categories_id' => (string) $parameters['categories_id'],
59
                            'filename' => empty($parameters['filename']) ? "''" : $parameters['filename'],
60
                        ], $parameters['id']);
61
            } catch (ResponseException $re) {
62
                Log::error($re->getMessage());
63
            } catch (\RuntimeException $ru) {
64
                Log::error($ru->getMessage());
65
            } catch (\Throwable $exception) {
66
                Log::error($exception->getMessage());
67
            }
68
69
        }
70
    }
71
72
    /**
73
     * Insert release into Manticore RT table.
74
     */
75
    public function insertPredb(array $parameters): void
76
    {
77
        try {
78
            if ($parameters['id']) {
79
                $this->manticoresearch->index($this->config['indexes']['predb'])
80
                    ->replaceDocument(['title' => $parameters['title'], 'filename' => empty($parameters['filename']) ? "''" : $parameters['filename'], 'source' => $parameters['source']], $parameters['id']);
81
            }
82
        } catch (ResponseException $re) {
83
            Log::error($re->getMessage());
84
        } catch (\RuntimeException $ru) {
85
            Log::error($ru->getMessage());
86
        } catch (\Throwable $exception) {
87
            Log::error($exception->getMessage());
88
        }
89
90
    }
91
92
    /**
93
     * Delete release from Manticore RT tables.
94
     *
95
     * @param  array  $identifiers  ['g' => Release GUID(mandatory), 'id' => ReleaseID(optional, pass false)]
96
     */
97
    public function deleteRelease(array $identifiers): void
98
    {
99
        if ($identifiers['i'] === false) {
100
            $identifiers['i'] = Release::query()->where('guid', $identifiers['g'])->first(['id']);
101
            if ($identifiers['i'] !== null) {
102
                $identifiers['i'] = $identifiers['i']['id'];
103
            }
104
        }
105
        if ($identifiers['i'] !== false) {
106
            $this->manticoresearch->index($this->config['indexes']['releases'])->deleteDocument($identifiers['i']);
107
        }
108
    }
109
110
    /**
111
     * Escapes characters that are treated as special operators by the query language parser.
112
     *
113
     * @param  string  $string  unescaped string
114
     * @return string Escaped string.
115
     */
116
    public static function escapeString(string $string): string
117
    {
118
        if ($string === '*') {
119
            return '';
120
        }
121
122
        $from = ['\\', '(', ')', '@', '~', '"', '&', '/', '$', '=', "'", '--', '[', ']'];
123
        $to = ['\\\\', '\(', '\)', '\@', '\~', '\"', '\&', '\/', '\$', '\=', "\', '\--", '\[', '\]'];
124
125
        $string = str_replace($from, $to, $string);
126
        // Remove these characaters if they are the last chars in $string
127
        $string = Str::of($string)->rtrim('-');
128
        $string = Str::of($string)->rtrim('!');
129
130
        return $string;
131
    }
132
133
    public function updateRelease(int|string $releaseID): void
134
    {
135
        $new = Release::query()
136
            ->where('releases.id', $releaseID)
137
            ->leftJoin('release_files as rf', 'releases.id', '=', 'rf.releases_id')
138
            ->select(['releases.id', 'releases.name', 'releases.searchname', 'releases.fromname', 'releases.categories_id', DB::raw('IFNULL(GROUP_CONCAT(rf.name SEPARATOR " "),"") filename')])
139
            ->groupBy('releases.id')
140
            ->first();
141
142
        if ($new !== null) {
143
            $release = $new->toArray();
144
            $this->insertRelease($release);
145
        }
146
    }
147
148
    /**
149
     * Update Manticore Predb index for given predb_id.
150
     *
151
     *
152
     * @throws \Exception
153
     */
154
    public function updatePreDb(array $parameters): void
155
    {
156
        if (! empty($parameters)) {
157
            $this->insertPredb($parameters);
158
        }
159
    }
160
161
    public function truncateRTIndex(array $indexes = []): bool
162
    {
163
        if (empty($indexes)) {
164
            $this->cli->error('You need to provide index name to truncate');
165
166
            return false;
167
        }
168
        foreach ($indexes as $index) {
169
            if (\in_array($index, $this->config['indexes'], true)) {
170
                try {
171
                    $this->manticoresearch->index($index)->truncate();
172
                    $this->cli->info('Truncating index '.$index.' finished.');
173
                } catch (ResponseException $e) {
174
                    if ($e->getMessage() === 'Invalid index') {
175
                        if ($index === 'releases_rt') {
176
                            $this->manticoresearch->index($index)->create([
177
                                'name' => ['type' => 'string'],
178
                                'searchname' => ['type' => 'string'],
179
                                'fromname' => ['type' => 'string'],
180
                                'filename' => ['type' => 'string'],
181
                                'categories_id' => ['type' => 'integer'],
182
                            ]);
183
                        } elseif ($index === 'predb_rt') {
184
                            $this->manticoresearch->index($index)->create([
185
                                'title' => ['type' => 'string'],
186
                                'filename' => ['type' => 'string'],
187
                                'source' => ['type' => 'string'],
188
                            ]);
189
                        }
190
                    }
191
                }
192
            } else {
193
                $this->cli->error('Unsupported index: '.$index);
194
            }
195
        }
196
197
        return true;
198
    }
199
200
    /**
201
     * Optimize the RT index.
202
     */
203
    public function optimizeRTIndex(): void
204
    {
205
        foreach ($this->config['indexes'] as $index) {
206
            $this->manticoresearch->index($index)->flush();
207
            $this->manticoresearch->index($index)->optimize();
208
        }
209
    }
210
211
    public function searchIndexes(string $rt_index, string $searchString = '', array $column = [], array $searchArray = []): array
212
    {
213
        $resultId = [];
214
        $resultData = [];
215
        try {
216
            $query = $this->search->setIndex($rt_index)->option('ranker', 'sph04')->option('sort_method', 'pq')->maxMatches(10000)->limit(10000)->sort('id', 'desc')->stripBadUtf8(true)->trackScores(true);
217
            if (! empty($searchArray)) {
218
                foreach ($searchArray as $key => $value) {
219
                    $query->search('@@relaxed @'.$key.' '.self::escapeString($value));
220
                }
221
            } elseif (! empty($searchString)) {
222
                // If $column is an array and has more than one item, implode it and wrap in parentheses.
223
                if (! empty($column) && \count($column) > 1) {
224
                    $searchColumns = '@('.implode(',', $column).')';
225
                } elseif (! empty($column) && \count($column) == 1) { // If $column is an array and has only one item, use as is.
226
                    $searchColumns = '@'.$column[0];
227
                } else {
228
                    $searchColumns = ''; // Careful, this will search all columns.
229
                }
230
231
                $query->search('@@relaxed '.$searchColumns.' '.self::escapeString($searchString));
232
            } else {
233
                return [];
234
            }
235
            $results = $query->get();
236
            foreach ($results as $doc) {
237
                $resultId[] = [
238
                    'id' => $doc->getId(),
239
                ];
240
                $resultData[] = [
241
                    'data' => $doc->getData(),
242
                ];
243
            }
244
245
            return [
246
                'id' => Arr::pluck($resultId, 'id'),
247
                'data' => Arr::pluck($resultData, 'data'),
248
            ];
249
        } catch (ResponseException $exception) {
250
            Log::error($exception->getMessage());
251
252
            return [];
253
        } catch (RuntimeException $exception) {
254
            Log::error($exception->getMessage());
255
256
            return [];
257
        } catch (\Throwable $exception) {
258
            Log::error($exception->getMessage());
259
260
            return [];
261
        }
262
    }
263
}
264