Files
nexusphp/app/Repositories/SearchRepository.php
2022-11-05 01:39:20 +08:00

913 lines
34 KiB
PHP

<?php
namespace App\Repositories;
use App\Models\Bookmark;
use App\Models\Setting;
use App\Models\Torrent;
use App\Models\TorrentTag;
use App\Models\User;
use Elasticsearch\Client;
use Elasticsearch\ClientBuilder;
use Illuminate\Support\Arr;
use Nexus\Database\NexusDB;
class SearchRepository extends BaseRepository
{
private ?Client $es = null;
private bool $enabled = false;
const INDEX_NAME = 'nexus_torrents';
const DOC_TYPE_TORRENT = 'torrent';
const DOC_TYPE_TAG = 'tag';
const DOC_TYPE_BOOKMARK = 'bookmark';
const DOC_TYPE_USER = 'user';
const SEARCH_MODE_AND = '0';
const SEARCH_MODE_OR = '1';
const SEARCH_MODE_EXACT = '2';
const SEARCH_MODES = [
self::SEARCH_MODE_AND => ['text' => 'and'],
self::SEARCH_MODE_OR => ['text' => 'or'],
self::SEARCH_MODE_EXACT => ['text' => 'exact'],
];
const SEARCH_AREA_TITLE = '0';
const SEARCH_AREA_DESC = '1';
const SEARCH_AREA_OWNER = '3';
const SEARCH_AREA_IMDB = '4';
const SEARCH_AREAS = [
self::SEARCH_AREA_TITLE => ['text' => 'title'],
self::SEARCH_AREA_DESC => ['text' => 'desc'],
self::SEARCH_AREA_OWNER => ['text' => 'owner'],
self::SEARCH_AREA_IMDB => ['text' => 'imdb'],
];
private array $indexSetting = [
'index' => self::INDEX_NAME,
'body' => [
'settings' => [
'number_of_shards' => 1,
'number_of_replicas' => 0,
],
'mappings' => [
'properties' => [
'_doc_type' => ['type' => 'keyword'],
//torrent
'torrent_id' => ['type' => 'long', ],
//user
'username' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
//bookmark + user + tag
'user_id' => ['type' => 'long', ],
//tag
'tag_id' => ['type' => 'long', ],
//use for category.mode
'mode' => ['type' => 'long', ],
//relations
'torrent_relations' => [
'type' => 'join',
'eager_global_ordinals' => true,
'relations' => [
'user' => ['torrent'],
'torrent' => ['bookmark', 'tag'],
],
],
],
]
],
];
//cat401=1&source1=1&medium1=1&codec1=1&audiocodec1=1&standard1=1&processing1=1&team1=1&incldead=1&spstate=2&inclbookmarked=1&search=tr&search_area=1&search_mode=1
private static array $queryFieldToTorrentFieldMaps = [
'cat' => 'category',
'source' => 'source',
'medium' => 'medium',
'codec' => 'codec',
'audiocodec' => 'audiocodec',
'standard' => 'standard',
'processing' => 'processing',
'team' => 'team',
];
private static array $sortFieldMaps = [
'1' => 'name',
'2' => 'numfiles',
'3' => 'comments',
'4' => 'added',
'5' => 'size',
'6' => 'times_completed',
'7' => 'seeders',
'8' => 'leechers',
'9' => 'owner',
];
public function __construct()
{
$elasticsearchEnabled = nexus_env('ELASTICSEARCH_ENABLED');
if ($elasticsearchEnabled) {
$this->enabled = true;
} else {
$this->enabled = false;
}
}
private function getEs(): Client
{
if (is_null($this->es)) {
$config = nexus_config('nexus.elasticsearch');
$builder = ClientBuilder::create()->setHosts($config['hosts']);
if (!empty($config['ssl_verification'])) {
$builder->setSSLVerification($config['ssl_verification']);
}
$this->es = $builder->build();
}
return $this->es;
}
private function getTorrentRawMappingFields(): array
{
return [
'name' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
'descr' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
'small_descr' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
'category' => ['type' => 'long', ],
'source' => ['type' => 'long', ],
'medium' => ['type' => 'long', ],
'codec' => ['type' => 'long', ],
'standard' => ['type' => 'long', ],
'processing' => ['type' => 'long', ],
'team' => ['type' => 'long', ],
'audiocodec' => ['type' => 'long', ],
'size' => ['type' => 'long', ],
'added' => ['type' => 'date', 'format' => 'yyyy-MM-dd HH:mm:ss'],
'numfiles' => ['type' => 'long', ],
'comments' => ['type' => 'long', ],
'views' => ['type' => 'long', ],
'hits' => ['type' => 'long', ],
'times_completed' => ['type' => 'long', ],
'leechers' => ['type' => 'long', ],
'seeders' => ['type' => 'long', ],
'last_action' => ['type' => 'date', 'format' => 'yyyy-MM-dd HH:mm:ss'],
'visible' => ['type' => 'keyword', ],
'banned' => ['type' => 'keyword', ],
'owner' => ['type' => 'long', ],
'sp_state' => ['type' => 'long', ],
'url' => ['type' => 'text', 'analyzer' => 'ik_max_word', 'fields' => ['keyword' => ['type' => 'keyword', 'ignore_above' => 256]]],
'pos_state' => ['type' => 'keyword', ],
'picktype' => ['type' => 'keyword', ],
'hr' => ['type' => 'long', ],
];
}
public function getEsInfo(): callable|array
{
return $this->getEs()->info();
}
public function createIndex()
{
$params = $this->indexSetting;
$properties = $params['body']['mappings']['properties'];
$properties = array_merge($properties, $this->getTorrentRawMappingFields());
$params['body']['mappings']['properties'] = $properties;
return $this->getEs()->indices()->create($params);
}
public function deleteIndex()
{
$params = ['index' => self::INDEX_NAME];
return $this->getEs()->indices()->delete($params);
}
public function import($torrentId = null)
{
$page = 1;
$size = 1000;
$fields = $this->getTorrentBaseFields();
array_unshift($fields, 'id');
$query = Torrent::query()
->with(['user', 'torrent_tags', 'bookmarks', 'basic_category'])
->select($fields);
if (!is_null($torrentId)) {
$idArr = preg_split('/[,\s]+/', $torrentId);
$query->whereIn('id', $idArr);
}
while (true) {
$log = "page: $page, size: $size";
$torrentResults = (clone $query)->forPage($page, $size)->get();
if ($torrentResults->isEmpty()) {
do_log("$log, no more data...", 'info', true);
break;
}
do_log("$log, get counts: " . $torrentResults->count(), 'info', true);
$torrentBodyBulk = $userBodyBulk = $tagBodyBulk = $bookmarkBodyBulk = ['body' => []];
foreach ($torrentResults as $torrent) {
$body = $this->buildUserBody($torrent->user, true);
$userBodyBulk['body'][] = ['index' => $body['index']];
$userBodyBulk['body'][] = $body['body'];
$body = $this->buildTorrentBody($torrent, true);
$torrentBodyBulk['body'][] = ['index' => $body['index']];
$torrentBodyBulk['body'][] = $body['body'];
foreach ($torrent->torrent_tags as $torrentTag) {
$body = $this->buildTorrentTagBody($torrent, $torrentTag, true);
$tagBodyBulk['body'][] = ['index' => $body['index']];
$tagBodyBulk['body'][] = $body['body'];
}
foreach ($torrent->bookmarks as $bookmark) {
$body = $this->buildBookmarkBody($torrent, $bookmark, true);
$bookmarkBodyBulk['body'][] = ['index' => $body['index']];
$bookmarkBodyBulk['body'][] = $body['body'];
}
}
//index user
$result = $this->getEs()->bulk($userBodyBulk);
$this->logEsResponse("$log, bulk index user done!", $result);
//index torrent
$result = $this->getEs()->bulk($torrentBodyBulk);
$this->logEsResponse("$log, bulk index torrent done!", $result);
//index tag
$result = $this->getEs()->bulk($tagBodyBulk);
$this->logEsResponse("$log, bulk index tag done!", $result);
//index bookmark
$result = $this->getEs()->bulk($bookmarkBodyBulk);
$this->logEsResponse("$log, bulk index bookmark done!", $result);
$page++;
}
}
private function buildUserBody(User $user, bool $underlinePrefix = false)
{
$docType = self::DOC_TYPE_USER;
$indexName = 'index';
$idName = 'id';
if ($underlinePrefix) {
$indexName = "_$indexName";
$idName = "_$idName";
}
$index = [
$indexName => self::INDEX_NAME,
$idName => $this->getUserId($user->id),
'routing' => $user->id,
];
$body = [
'_doc_type' => $docType,
'user_id' => $user->id,
'username' => $user->username,
'torrent_relations' => [
'name' => $docType,
],
];
return compact('index', 'body');
}
private function buildTorrentBody($torrent, bool $underlinePrefix = false): array
{
$baseFields = $this->getTorrentBaseFields();
if (!$torrent instanceof Torrent) {
$torrent = Torrent::query()->findOrFail((int)$torrent, array_merge(['id'], $baseFields));
}
$docType = self::DOC_TYPE_TORRENT;
$indexName = 'index';
$idName = 'id';
if ($underlinePrefix) {
$indexName = "_$indexName";
$idName = "_$idName";
}
$index = [
$indexName => self::INDEX_NAME,
$idName => $this->getTorrentId($torrent->id),
'routing' => $torrent->owner,
];
$searchBoxId = $torrent->basic_category->mode ?? 0;
if ($searchBoxId == 0) {
do_log(sprintf('[INVALID_CATEGORY], Torrent: %s', $torrent->id), 'error');
}
$data = Arr::only($torrent->toArray(), $baseFields);
$data['mode'] = $searchBoxId;
$body = array_merge($data, [
'_doc_type' => $docType,
'torrent_id' => $torrent->id,
'torrent_relations' => [
'name' => $docType,
'parent' => 'user_' . $torrent->owner,
],
]);
return compact('index', 'body');
}
private function buildTorrentTagBody(Torrent $torrent, TorrentTag $torrentTag, bool $underlinePrefix = false)
{
$docType = self::DOC_TYPE_TAG;
$indexName = 'index';
$idName = 'id';
if ($underlinePrefix) {
$indexName = "_$indexName";
$idName = "_$idName";
}
$index = [
$indexName => self::INDEX_NAME,
$idName => $this->getTorrentTagId($torrentTag->id),
'routing' => $torrent->owner,
];
$body = [
'_doc_type' => $docType,
'torrent_id' => $torrentTag->torrent_id,
'tag_id' => $torrentTag->tag_id,
'torrent_relations' => [
'name' => $docType,
'parent' => 'torrent_' . $torrent->id,
],
];
return compact('index', 'body');
}
private function buildBookmarkBody(Torrent $torrent, Bookmark $bookmark, bool $underlinePrefix = false)
{
$docType = self::DOC_TYPE_BOOKMARK;
$indexName = 'index';
$idName = 'id';
if ($underlinePrefix) {
$indexName = "_$indexName";
$idName = "_$idName";
}
$index = [
$indexName => self::INDEX_NAME,
$idName => $this->getBookmarkId($bookmark->id),
'routing' => $torrent->owner,
];
$body = [
'_doc_type' => $docType,
'torrent_id' => $bookmark->torrentid,
'user_id' => $bookmark->userid,
'torrent_relations' => [
'name' => $docType,
'parent' => 'torrent_' . $torrent->id,
],
];
return compact('index', 'body');
}
private function logEsResponse($msg, $response)
{
if (isset($response['errors']) && $response['errors'] == true) {
$msg .= var_export($response, true);
}
do_log($msg, 'info', isRunningInConsole());
}
private function getTorrentId($id): string
{
return "torrent_" . intval($id);
}
private function getTorrentTagId($id): string
{
return "torrent_tag_" . intval($id);
}
private function getUserId($id): string
{
return "user_" . intval($id);
}
private function getBookmarkId($id): string
{
return "bookmark_" . intval($id);
}
/**
* detect elastic response has error or not
*
* @param $esResponse
* @return bool
*/
private function isEsResponseError($esResponse)
{
if (isset($esResponse['error'])) {
return true;
}
//bulk insert
if (isset($esResponse['errors']) && $esResponse['errors']) {
return true;
}
//update by query
if (!empty($esResponse['failures'])) {
return true;
}
return false;
}
/**
* build es query
*
* @param array $params
* @param $user
* @param string $queryString cat401=1&cat404=1&source2=1&medium2=1&medium3=1&codec3=1&audiocodec3=1&standard2=1&standard3=1&processing2=1&team3=1&team4=1&incldead=1&spstate=0&inclbookmarked=0&search=&search_area=0&search_mode=0
* @return array
*/
public function buildQuery(array $params, $user, string $queryString)
{
if (!($user instanceof User) || !$user->torrentsperpage || !$user->notifs) {
$user = User::query()->findOrFail(intval($user));
}
//[cat401][cat404][sou1][med1][cod1][sta2][sta3][pro2][tea2][aud2][incldead=0][spstate=3][inclbookmarked=2]
$userSetting = $user->notifs;
$must = $must_not = [];
$mustBoolShould = [];
$must[] = ['match' => ['_doc_type' => self::DOC_TYPE_TORRENT]];
if (!empty($params['mode'])) {
$must[] = ['match' => ['mode' => $params['mode']]];
}
foreach (self::$queryFieldToTorrentFieldMaps as $queryField => $torrentField) {
if (isset($params[$queryField]) && $params[$queryField] !== '') {
$mustBoolShould[$torrentField][] = ['match' => [$torrentField => $params[$queryField]]];
do_log("get mustBoolShould for $torrentField from params through $queryField: {$params[$queryField]}");
} elseif (preg_match_all("/{$queryField}([\d]+)=/", $queryString, $matches)) {
if (count($matches) == 2 && !empty($matches[1])) {
foreach ($matches[1] as $match) {
$mustBoolShould[$torrentField][] = ['match' => [$torrentField => $match]];
do_log("get mustBoolShould for $torrentField from params through $queryField: $match");
}
}
} else {
//get user setting
$pattern = sprintf("/\[%s([\d]+)\]/", substr($queryField, 0, 3));
if (preg_match($pattern, $userSetting, $matches)) {
if (count($matches) == 2 && !empty($matches[1])) {
foreach ($matches[1] as $match) {
$mustBoolShould[$torrentField][] = ['match' => [$torrentField => $match]];
do_log("get mustBoolShould for $torrentField from user setting through $queryField: $match");
}
}
}
}
}
$includeDead = 1;
if (isset($params['incldead'])) {
$includeDead = (int)$params['incldead'];
do_log("maybe get must for visible from params");
} elseif (preg_match("/\[incldead=([\d]+)\]/", $userSetting, $matches)) {
$includeDead = $matches[1];
do_log("maybe get must for visible from user setting");
}
if ($includeDead == 1) {
//active torrent
$must[] = ['match' => ['visible' => 'yes']];
do_log("get must for visible = yes through incldead: $includeDead");
} elseif ($includeDead == 2) {
//dead torrent
$must[] = ['match' => ['visible' => 'no']];
do_log("get must for visible = no through incldead: $includeDead");
}
$includeBookmarked = 0;
if (isset($params['inclbookmarked'])) {
$includeBookmarked = (int)$params['inclbookmarked'];
do_log("maybe get must or must_not for has_child.bookmark from params");
} elseif (preg_match("/\[inclbookmarked=([\d]+)\]/", $userSetting, $matches)) {
$includeBookmarked = $matches[1];
do_log("maybe get must or must_not for has_child.bookmark from user setting");
}
if ($includeBookmarked == 1) {
//only bookmark
$must[] = ['has_child' => ['type' => 'bookmark', 'query' => ['match' => ['user_id' => $user->id]]]];
do_log("get must for has_child.bookmark through inclbookmarked: $includeBookmarked");
} elseif ($includeBookmarked == 2) {
//only not bookmark
$must_not[] = ['has_child' => ['type' => 'bookmark', 'query' => ['match' => ['user_id' => $user->id]]]];
do_log("get must_not for has_child.bookmark through inclbookmarked: $includeBookmarked");
}
$spState = 0;
if (isset($params['spstate'])) {
$spState = (int)$params['spstate'];
do_log("maybe get must for spstate from params");
} elseif (preg_match("/\[spstate=([\d]+)\]/", $userSetting, $matches)) {
$spState = $matches[1];
do_log("maybe get must for spstate from user setting");
}
if ($spState > 0) {
$must[] = ['match' => ['sp_state' => $spState]];
do_log("get must for sp_state = $spState through spstate: $spState");
}
if (!empty($params['tag_id'])) {
$must[] = ['has_child' => ['type' => 'tag', 'query' => ['match' => ['tag_id' => $params['tag_id']]]]];
do_log("get must for has_child.tag through params.tag_id: {$params['tag_id']}");
}
if (!empty($params['search'])) {
$searchMode = isset($params['search_mode']) && isset(self::SEARCH_MODES[$params['search_mode']]) ? $params['search_mode'] : self::SEARCH_MODE_AND;
if (in_array($searchMode, [self::SEARCH_MODE_AND, self::SEARCH_MODE_OR])) {
//and, or
$keywordsArr = preg_split("/[\.\s]+/", trim($params['search']));
} else {
$keywordsArr = [trim($params['search'])];
}
$keywordsArr = array_slice($keywordsArr, 0, 10);
$searchArea = isset($params['search_area']) && isset(self::SEARCH_AREAS[$params['search_area']]) ? $params['search_area'] : self::SEARCH_AREA_TITLE;
if ($searchMode == self::SEARCH_MODE_AND || $searchMode == self::SEARCH_MODE_EXACT) {
$keywordFlag = $searchMode == self::SEARCH_MODE_EXACT ? ".keyword" : "";
if ($searchArea == self::SEARCH_AREA_TITLE) {
foreach ($keywordsArr as $keyword) {
$tmpMustBoolShould = [];
$tmpMustBoolShould[] = ['match' => ["name{$keywordFlag}" => $keyword]];
$tmpMustBoolShould[] = ['match' => ["small_descr{$keywordFlag}" => $keyword]];
$must[]['bool']['should'] = $tmpMustBoolShould;
do_log("get must bool should [SEARCH_MODE_AND + SEARCH_MODE_EXACT] for name+small_descr match '$keyword' through search");
}
} elseif ($searchArea == self::SEARCH_AREA_DESC) {
foreach ($keywordsArr as $keyword) {
$must[] = ['match' => ["descr{$keywordFlag}" => $keyword]];
do_log("get must [SEARCH_MODE_AND + SEARCH_MODE_EXACT] for descr match '$keyword' through search");
}
} elseif ($searchArea == self::SEARCH_AREA_IMDB) {
foreach ($keywordsArr as $keyword) {
$must[] = ['match' => ["url{$keywordFlag}" => $keyword]];
do_log("get must [SEARCH_MODE_AND + SEARCH_MODE_EXACT] for url match '$keyword' through search");
}
} elseif ($searchArea == self::SEARCH_AREA_OWNER) {
foreach ($keywordsArr as $keyword) {
$must[] = ['has_parent' => ['parent_type' => 'user', 'query' => ['match' => ["username{$keywordFlag}" => $keyword]]]];
do_log("get must [SEARCH_MODE_AND + SEARCH_MODE_EXACT] has_parent.user match '$keyword' through search");
}
}
} elseif ($searchMode == self::SEARCH_MODE_OR) {
if ($searchArea == self::SEARCH_AREA_TITLE) {
$tmpMustBoolShould = [];
foreach ($keywordsArr as $keyword) {
$tmpMustBoolShould[] = ['match' => ['name' => $keyword]];
$tmpMustBoolShould[] = ['match' => ['small_descr' => $keyword]];
do_log("get must bool should [SEARCH_MODE_OR] for name+small_descr match '$keyword' through search");
}
$must[]['bool']['should'] = $tmpMustBoolShould;
} elseif ($searchArea == self::SEARCH_AREA_DESC) {
$tmpMustBoolShould = [];
foreach ($keywordsArr as $keyword) {
$tmpMustBoolShould[] = ['match' => ['descr' => $keyword]];
do_log("get must bool should [SEARCH_MODE_OR] for descr match '$keyword' through search");
}
$must[]['bool']['should'] = $tmpMustBoolShould;
} elseif ($searchArea == self::SEARCH_AREA_IMDB) {
$tmpMustBoolShould = [];
foreach ($keywordsArr as $keyword) {
$tmpMustBoolShould[] = ['match' => ['url' => $keyword]];
do_log("get must bool should [SEARCH_MODE_OR] for url match '$keyword' through search");
}
$must[]['bool']['should'] = $tmpMustBoolShould;
} elseif ($searchArea == self::SEARCH_AREA_OWNER) {
$tmpMustBoolShould = [];
foreach ($keywordsArr as $keyword) {
$tmpMustBoolShould[] = ['has_parent' => ['parent_type' => 'user', 'query' => ['match' => ['username' => $keyword]]]];
do_log("get must bool should [SEARCH_MODE_OR] has_parent.user match '$keyword' through search");
}
$must[]['bool']['should'] = $tmpMustBoolShould;
}
}
}
$query = [
'bool' => [
'must' => $must
]
];
foreach ($mustBoolShould as $torrentField => $boolShoulds) {
$query['bool']['must'][]['bool']['should'] = $boolShoulds;
}
if (!empty($must_not)) {
$query['bool']['must_not'] = $must_not;
}
$sort = [];
$sort[] = ['pos_state' => ['order' => 'desc']];
$hasAddSetSortField = false;
if (!empty($params['sort'])) {
$direction = isset($params['type']) && in_array($params['type'], ['asc', 'desc']) ? $params['type'] : 'desc';
foreach (self::$sortFieldMaps as $key => $value) {
if ($key == $params['sort']) {
$hasAddSetSortField = true;
$sort[] = [$value => ['order' => $direction]];
}
}
}
if (!$hasAddSetSortField) {
$sort[] = ['torrent_id' => ['order' => 'desc']];
}
$page = isset($params['page']) && is_numeric($params['page']) ? $params['page'] : 0;
if ($user->torrentsperpage) {
$size = $user->torrentsperpage;
} elseif (($sizeFromConfig = Setting::get('main.torrentsperpage')) > 0) {
$size = $sizeFromConfig;
} else {
$size = 50;
}
$size = min($size, 200);
$offset = $page * $size;
$result = [
'query' => $query,
'sort' => $sort,
'from' => $offset,
'size' => $size,
'_source' => ['torrent_id', 'name', 'small_descr', 'owner']
];
do_log(sprintf(
"params: %s, user: %s, queryString: %s, result: %s",
nexus_json_encode($params), $user->id, $queryString, nexus_json_encode($result)
));
return $result;
}
public function listTorrentFromEs(array $params, $user, string $queryString)
{
$query = $this->buildQuery($params, $user, $queryString);
$esParams = [
'index' => self::INDEX_NAME,
'body' => $query,
];
$response = $this->getEs()->search($esParams);
$result = [
'total' => 0,
'data' => [],
];
if ($this->isEsResponseError($response)) {
do_log("error response: " . nexus_json_encode($response), 'error');
return $result;
}
if (empty($response['hits'])) {
do_log("empty response hits: " . nexus_json_encode($response), 'error');
return $result;
}
if ($response['hits']['total']['value'] == 0) {
do_log("total = 0, " . nexus_json_encode($response));
return $result;
}
$result['total'] = $response['hits']['total']['value'];
$torrentIdArr = [];
foreach ($response['hits']['hits'] as $value) {
$torrentIdArr[] = $value['_source']['torrent_id'];
}
// $fieldStr = 'id, sp_state, promotion_time_type, promotion_until, banned, picktype, pos_state, category, source, medium, codec, standard, processing, team, audiocodec, leechers, seeders, name, small_descr, times_completed, size, added, comments,anonymous,owner,url,cache_stamp, pt_gen, hr';
$fields = Torrent::getFieldsForList();
$idStr = implode(',', $torrentIdArr);
$result['data'] = Torrent::query()
->select($fields)
->whereIn('id', $torrentIdArr)
->orderByRaw("field(id,$idStr)")
->get()
->toArray()
;
return $result;
}
private function getTorrentBaseFields()
{
return array_keys($this->getTorrentRawMappingFields());
}
public function updateTorrent(int $id): bool
{
if (!$this->enabled) {
return true;
}
$log = "[UPDATE_TORRENT]: $id";
$result = $this->getTorrent($id);
if ($this->isEsResponseError($result)) {
do_log("$log, fail: " . nexus_json_encode($result), 'error');
return false;
}
if ($result['found'] === false) {
do_log("$log, not exists, do insert");
return $this->addTorrent($id);
}
$baseFields = $this->getTorrentBaseFields();
$torrent = Torrent::query()->findOrFail($id, array_merge(['id'], $baseFields));
$data = $this->buildTorrentBody($torrent);
$params = $data['index'];
$params['body']['doc'] = $data['body'];
$result = $this->getEs()->update($params);
if ($this->isEsResponseError($result)) {
do_log("$log, fail: " . nexus_json_encode($result), 'error');
return false;
}
do_log("$log, success: " . nexus_json_encode($result));
return $this->syncTorrentTags($torrent);
}
public function addTorrent(int $id): bool
{
if (!$this->enabled) {
return true;
}
$log = "[ADD_TORRENT]: $id";
$baseFields = $this->getTorrentBaseFields();
$torrent = Torrent::query()->findOrFail($id, array_merge(['id'], $baseFields));
$data = $this->buildTorrentBody($torrent, true);
$params = ['body' => []];
$params['body'][] = ['index' => $data['index']];
$params['body'][] = $data['body'];
$result = $this->getEs()->bulk($params);
if ($this->isEsResponseError($result)) {
do_log("$log, fail: " . nexus_json_encode($result), 'error');
return false;
}
do_log("$log, success: " . nexus_json_encode($result));
return $this->syncTorrentTags($torrent);
}
public function getTorrent($id): callable|bool|array
{
if (!$this->enabled) {
return false;
}
$params = [
'index' => self::INDEX_NAME,
'id' => $this->getTorrentId($id),
];
return $this->getEs()->get($params);
}
public function deleteTorrent(int $id): bool
{
if (!$this->enabled) {
return true;
}
$log = "[DELETE_TORRENT]: $id";
$params = [
'index' => self::INDEX_NAME,
'id' => $this->getTorrentId($id),
];
$result = $this->getEs()->delete($params);
if ($this->isEsResponseError($result)) {
do_log("$log, fail: " . nexus_json_encode($result), 'error');
return false;
}
do_log("$log, success: " . nexus_json_encode($result));
return $this->syncTorrentTags($id, true);
}
public function syncTorrentTags($torrent, $onlyDelete = false): bool
{
if (!$this->enabled) {
return true;
}
if (!$torrent instanceof Torrent) {
$torrent = Torrent::query()->findOrFail((int)$torrent, ['id']);
}
$log = "sync torrent tags, torrent: " . $torrent->id;
//remove first
$params = [
'index' => self::INDEX_NAME,
'body' => [
'query' => [
'bool' => [
'must' => [
['match' => ['_doc_type' => self::DOC_TYPE_TAG]],
['has_parent' => ['parent_type' => 'torrent', 'query' => ['match' => ['torrent_id' => $torrent->id]]]]
]
]
]
]
];
$result = $this->getEs()->deleteByQuery($params);
if ($this->isEsResponseError($result)) {
do_log("$log, delete torrent tag fail: " . nexus_json_encode($result), 'error');
return false;
}
do_log("$log, delete torrent tag success: " . nexus_json_encode($result));
if ($onlyDelete) {
do_log("$log, only delete, return true");
return true;
}
//then insert new
$bulk = ['body' => []];
foreach ($torrent->torrent_tags as $torrentTag) {
$body = $this->buildTorrentTagBody($torrent, $torrentTag, true);
$bulk['body'][] = ['index' => $body['index']];
$bulk['body'][] = $body['body'];
}
if (empty($bulk['body'])) {
do_log("$log, no tags, return true");
return true;
}
$result = $this->getEs()->bulk($bulk);
if ($this->isEsResponseError($result)) {
do_log("$log, insert torrent tag fail: " . nexus_json_encode($result), 'error');
return false;
}
do_log("$log, insert torrent tag success: " . nexus_json_encode($result));
return true;
}
public function updateUser($user): bool
{
if (!$this->enabled) {
return true;
}
if (!$user instanceof User) {
$user = User::query()->findOrFail((int)$user, ['id', 'username']);
}
$log = "[UPDATE_USER]: " . $user->id;
$data = $this->buildUserBody($user);
$params = $data['index'];
$params['body']['doc'] = $data['body'];
$result = $this->getEs()->update($params);
if ($this->isEsResponseError($result)) {
do_log("$log, fail: " . nexus_json_encode($result), 'error');
return false;
}
do_log("$log, success: " . nexus_json_encode($result));
return true;
}
public function addBookmark($bookmark): bool
{
if (!$this->enabled) {
return true;
}
if (!$bookmark instanceof Bookmark) {
$bookmark = Bookmark::query()->with([
'torrent' => function ($query) {$query->select(['id', 'owner']);}
])->findOrFail((int)$bookmark);
}
$log = "[ADD_BOOKMARK]: " . $bookmark->toJson();
$bulk = ['body' => []];
$body = $this->buildBookmarkBody($bookmark->torrent, $bookmark, true);
$bulk['body'][] = ['index' => $body['index']];
$bulk['body'][] = $body['body'];
$result = $this->getEs()->bulk($bulk);
if ($this->isEsResponseError($result)) {
do_log("$log, fail: " . nexus_json_encode($result), 'error');
return false;
}
do_log("$log, success: " . nexus_json_encode($result));
return true;
}
public function deleteBookmark(int $id): bool
{
if (!$this->enabled) {
return true;
}
$log = "[DELETE_BOOKMARK]: $id";
$params = [
'index' => self::INDEX_NAME,
'id' => $this->getBookmarkId($id),
];
$result = $this->getEs()->delete($params);
if ($this->isEsResponseError($result)) {
do_log("$log, fail: " . nexus_json_encode($result), 'error');
return false;
}
do_log("$log, success: " . nexus_json_encode($result));
return true;
}
}