diff --git a/FAQ.md b/FAQ.md index d0f7db9f..95c26e87 100644 --- a/FAQ.md +++ b/FAQ.md @@ -308,25 +308,26 @@ it's slower than `MemoryMapper`. ### Q: What environment variables supported? -| Key | Type | Description | Default | -|-----------------------|--------|---------------------------------------------------------------------------------|-------------------------------| -| WS_DATA_PATH | string | Where to store main data. (config, db). | `${BASE_PATH}/var` | -| WS_TMP_DIR | string | Where to store temp data. (logs, cache) | `${WS_DATA_PATH}` | -| WS_TZ | string | Set timezone. | `UTC` | -| WS_CRON_IMPORT | bool | Enable import scheduled task. Value casted to bool. | `false` | -| WS_CRON_IMPORT_AT | string | When to run import scheduled task. Valid Cron Expression Expected. | `0 */1 * * *` (Every 1h) | -| WS_CRON_IMPORT_ARGS | string | Flags to pass to the import command. | `-v` | -| WS_CRON_EXPORT | bool | Enable export scheduled task. Value casted to bool. | `false` | -| WS_CRON_EXPORT_AT | string | When to run export scheduled task. Valid Cron Expression Expected. | `30 */1 * * *` (Every 1h 30m) | -| WS_CRON_EXPORT_ARGS | string | Flags to pass to the export command. | `-v` | -| WS_CRON_PUSH | bool | Enable push scheduled task. Value casted to bool. | `false` | -| WS_CRON_PUSH_AT | string | When to run push scheduled task. Valid Cron Expression Expected. | `*/10 * * * *` (Every 10m) | -| WS_CRON_PUSH_ARGS | string | Flags to pass to the push command. | `-v` | -| WS_LOGS_PRUNE_AFTER | string | Delete logs older than specified time. Set to `disable` to disable the pruning. | `-3 DAYS` | -| WS_LOGS_CONTEXT | bool | Add context to console output messages. | `false` | -| WS_LOGGER_FILE_ENABLE | bool | Save logs to file. | `true` | -| WS_LOGGER_FILE_LEVEL | string | File Logger Level. | `ERROR` | -| WS_WEBHOOK_DEBUG | bool | If enabled, allow dumping request/webhook using `rdump` & `wdump` parameters. | `false` | +| Key | Type | Description | Default | +|--------------------------|--------|---------------------------------------------------------------------------------|-------------------------------| +| WS_DATA_PATH | string | Where to store main data. (config, db). | `${BASE_PATH}/var` | +| WS_TMP_DIR | string | Where to store temp data. (logs, cache) | `${WS_DATA_PATH}` | +| WS_TZ | string | Set timezone. | `UTC` | +| WS_CRON_IMPORT | bool | Enable import scheduled task. Value casted to bool. | `false` | +| WS_CRON_IMPORT_AT | string | When to run import scheduled task. Valid Cron Expression Expected. | `0 */1 * * *` (Every 1h) | +| WS_CRON_IMPORT_ARGS | string | Flags to pass to the import command. | `-v` | +| WS_CRON_EXPORT | bool | Enable export scheduled task. Value casted to bool. | `false` | +| WS_CRON_EXPORT_AT | string | When to run export scheduled task. Valid Cron Expression Expected. | `30 */1 * * *` (Every 1h 30m) | +| WS_CRON_EXPORT_ARGS | string | Flags to pass to the export command. | `-v` | +| WS_CRON_PUSH | bool | Enable push scheduled task. Value casted to bool. | `false` | +| WS_CRON_PUSH_AT | string | When to run push scheduled task. Valid Cron Expression Expected. | `*/10 * * * *` (Every 10m) | +| WS_CRON_PUSH_ARGS | string | Flags to pass to the push command. | `-v` | +| WS_LOGS_PRUNE_AFTER | string | Delete logs older than specified time. Set to `disable` to disable the pruning. | `-3 DAYS` | +| WS_LOGS_CONTEXT | bool | Add context to console output messages. | `false` | +| WS_LOGGER_FILE_ENABLE | bool | Save logs to file. | `true` | +| WS_LOGGER_FILE_LEVEL | string | File Logger Level. | `ERROR` | +| WS_WEBHOOK_DEBUG | bool | If enabled, allow dumping request/webhook using `rdump` & `wdump` parameters. | `false` | +| WS_EPISODES_DISABLE_GUID | bool | Disable external id parsing for episodes and rely on relative ids. | `false` | #### Container specific environment variables. diff --git a/config/config.php b/config/config.php index 7a50dc33..866fadc0 100644 --- a/config/config.php +++ b/config/config.php @@ -32,6 +32,11 @@ return (function () { // -- Trigger full export mode if changes exceed X number. 'threshold' => env('WS_EXPORT_THRESHOLD', 1000), ], + 'episodes' => [ + 'disable' => [ + 'guid' => (bool)env('WS_EPISODES_DISABLE_GUID', false), + ] + ], 'ignore' => [], ]; diff --git a/src/Backends/Emby/Action/ParseWebhook.php b/src/Backends/Emby/Action/ParseWebhook.php index cf338dac..73ef181c 100644 --- a/src/Backends/Emby/Action/ParseWebhook.php +++ b/src/Backends/Emby/Action/ParseWebhook.php @@ -13,8 +13,10 @@ use App\Backends\Common\Response; use App\Backends\Emby\EmbyActionTrait; use App\Backends\Emby\EmbyClient; use App\Backends\Jellyfin\JellyfinActionTrait; -use App\Libs\Entity\StateInterface as iFace; +use App\Libs\Config; +use App\Libs\Entity\StateInterface as iState; use App\Libs\Guid; +use App\Libs\Options; use Psr\Http\Message\ServerRequestInterface as iRequest; use Throwable; @@ -92,6 +94,8 @@ final class ParseWebhook } try { + $obj = $this->getItemDetails(context: $context, id: $id); + if ('item.markplayed' === $event || 'playback.scrobble' === $event) { $isPlayed = 1; $lastPlayedAt = time(); @@ -112,31 +116,7 @@ final class ParseWebhook $lastPlayedAt = (0 === $isPlayed) ? makeDate(ag($json, 'Item.DateCreated'))->getTimestamp() : time(); } - $fields = [ - iFace::COLUMN_EXTRA => [ - $context->backendName => [ - iFace::COLUMN_EXTRA_EVENT => $event, - iFace::COLUMN_EXTRA_DATE => makeDate('now'), - ], - ], - ]; - - if (false === in_array($event, self::WEBHOOK_TAINTED_EVENTS)) { - $fields += [ - iFace::COLUMN_WATCHED => $isPlayed, - iFace::COLUMN_UPDATED => $lastPlayedAt, - iFace::COLUMN_META_DATA => [ - $context->backendName => [ - iFace::COLUMN_WATCHED => (string)$isPlayed, - iFace::COLUMN_META_DATA_PLAYED_AT => (string)$lastPlayedAt, - ] - ], - ]; - } - - $obj = $this->getItemDetails(context: $context, id: $id); - - $guids = $guid->get(guids: ag($json, 'Item.ProviderIds', []), context: [ + $logContext = [ 'item' => [ 'id' => ag($obj, 'Id'), 'type' => ag($obj, 'Type'), @@ -157,19 +137,54 @@ final class ParseWebhook }, 'year' => ag($obj, 'ProductionYear'), ], - ]); + ]; - if (count($guids) >= 1) { - $guids += Guid::makeVirtualGuid($context->backendName, (string)$id); - $fields[iFace::COLUMN_GUIDS] = $guids; - $fields[iFace::COLUMN_META_DATA][$context->backendName][iFace::COLUMN_GUIDS] = $fields[iFace::COLUMN_GUIDS]; + $disableGuid = (bool)Config::get('episodes.disable.guid'); + + if (EmbyClient::TYPE_EPISODE === $type && true === $disableGuid) { + $guids = []; + } else { + $guids = $guid->get(guids: ag($json, 'Item.ProviderIds', []), context: $logContext); + } + + $guids += Guid::makeVirtualGuid($context->backendName, (string)$id); + + $fields = [ + iState::COLUMN_GUIDS => $guids, + iState::COLUMN_META_DATA => [ + $context->backendName => [ + iState::COLUMN_GUIDS => $guid->parse( + guids: ag($json, 'Item.ProviderIds', []), + context: $logContext + ), + ] + ], + iState::COLUMN_EXTRA => [ + $context->backendName => [ + iState::COLUMN_EXTRA_EVENT => $event, + iState::COLUMN_EXTRA_DATE => makeDate('now'), + ], + ], + ]; + + if (false === in_array($event, self::WEBHOOK_TAINTED_EVENTS)) { + $fields = array_replace_recursive($fields, [ + iState::COLUMN_WATCHED => $isPlayed, + iState::COLUMN_UPDATED => $lastPlayedAt, + iState::COLUMN_META_DATA => [ + $context->backendName => [ + iState::COLUMN_WATCHED => (string)$isPlayed, + iState::COLUMN_META_DATA_PLAYED_AT => (string)$lastPlayedAt, + ] + ], + ]); } $entity = $this->createEntity( context: $context, guid: $guid, item: $obj, - opts: ['override' => $fields], + opts: ['override' => $fields, Options::DISABLE_GUID => $disableGuid], )->setIsTainted(isTainted: true === in_array($event, self::WEBHOOK_TAINTED_EVENTS)); if (false === $entity->hasGuids() && false === $entity->hasRelativeGuid()) { diff --git a/src/Backends/Jellyfin/Action/Export.php b/src/Backends/Jellyfin/Action/Export.php index 0fd6ae6b..845fa8ce 100644 --- a/src/Backends/Jellyfin/Action/Export.php +++ b/src/Backends/Jellyfin/Action/Export.php @@ -193,7 +193,7 @@ class Export extends Import if (true === (bool)ag($context->options, Options::DRY_RUN, false)) { return; } - + $queue->add( $this->http->request( $entity->isWatched() ? 'POST' : 'DELETE', diff --git a/src/Backends/Jellyfin/Action/ParseWebhook.php b/src/Backends/Jellyfin/Action/ParseWebhook.php index a03381b7..a2e2c1e3 100644 --- a/src/Backends/Jellyfin/Action/ParseWebhook.php +++ b/src/Backends/Jellyfin/Action/ParseWebhook.php @@ -11,9 +11,11 @@ use App\Backends\Common\GuidInterface as iGuid; use App\Backends\Common\Levels; use App\Backends\Common\Response; use App\Backends\Jellyfin\JellyfinActionTrait; -use App\Backends\Jellyfin\JellyfinClient; -use App\Libs\Entity\StateInterface as iFace; +use App\Backends\Jellyfin\JellyfinClient as JFC; +use App\Libs\Config; +use App\Libs\Entity\StateInterface as iState; use App\Libs\Guid; +use App\Libs\Options; use Psr\Http\Message\ServerRequestInterface as iRequest; use Throwable; @@ -22,8 +24,8 @@ final class ParseWebhook use CommonTrait, JellyfinActionTrait; protected const WEBHOOK_ALLOWED_TYPES = [ - JellyfinClient::TYPE_MOVIE, - JellyfinClient::TYPE_EPISODE, + JFC::TYPE_MOVIE, + JFC::TYPE_EPISODE, ]; protected const WEBHOOK_ALLOWED_EVENTS = [ @@ -87,58 +89,22 @@ final class ParseWebhook } try { + $obj = $this->getItemDetails(context: $context, id: $id); + $isPlayed = (bool)ag($json, 'Played'); $lastPlayedAt = true === $isPlayed ? ag($json, 'LastPlayedDate') : null; - $fields = [ - iFace::COLUMN_WATCHED => (int)$isPlayed, - iFace::COLUMN_META_DATA => [ - $context->backendName => [ - iFace::COLUMN_WATCHED => true === $isPlayed ? '1' : '0', - ] - ], - iFace::COLUMN_EXTRA => [ - $context->backendName => [ - iFace::COLUMN_EXTRA_EVENT => $event, - iFace::COLUMN_EXTRA_DATE => makeDate('now'), - ], - ], - ]; - - if (true === $isPlayed && null !== $lastPlayedAt) { - $lastPlayedAt = makeDate($lastPlayedAt)->getTimestamp(); - $fields = array_replace_recursive($fields, [ - iFace::COLUMN_UPDATED => $lastPlayedAt, - iFace::COLUMN_META_DATA => [ - $context->backendName => [ - iFace::COLUMN_META_DATA_PLAYED_AT => (string)$lastPlayedAt, - ] - ], - ]); - } - - $obj = $this->getItemDetails(context: $context, id: $id); - - $providersId = []; - - foreach (array_change_key_case($json, CASE_LOWER) as $key => $val) { - if (false === str_starts_with($key, 'provider_')) { - continue; - } - $providersId[after($key, 'provider_')] = $val; - } - - $guids = $guid->get(guids: $providersId, context: [ + $logContext = [ 'item' => [ 'id' => ag($obj, 'Id'), 'type' => ag($obj, 'Type'), 'title' => match (ag($obj, 'Type')) { - JellyfinClient::TYPE_MOVIE => sprintf( + JFC::TYPE_MOVIE => sprintf( '%s (%s)', ag($obj, ['Name', 'OriginalTitle'], '??'), ag($obj, 'ProductionYear', '0000') ), - JellyfinClient::TYPE_EPISODE => trim( + JFC::TYPE_EPISODE => trim( sprintf( '%s - (%sx%s)', ag($obj, 'SeriesName', '??'), @@ -149,19 +115,64 @@ final class ParseWebhook }, 'year' => ag($obj, 'ProductionYear'), ], - ]); + ]; - if (count($guids) >= 1) { - $guids += Guid::makeVirtualGuid($context->backendName, (string)$id); - $fields[iFace::COLUMN_GUIDS] = $guids; - $fields[iFace::COLUMN_META_DATA][$context->backendName][iFace::COLUMN_GUIDS] = $fields[iFace::COLUMN_GUIDS]; + $disableGuid = (bool)Config::get('episodes.disable.guid'); + + $providersId = []; + + foreach (array_change_key_case($json, CASE_LOWER) as $key => $val) { + if (false === str_starts_with($key, 'provider_')) { + continue; + } + $providersId[after($key, 'provider_')] = $val; + } + + if (JFC::TYPE_EPISODE === $type && true === $disableGuid) { + $guids = []; + } else { + $guids = $guid->get(guids: $providersId, context: $logContext); + } + + $guids += Guid::makeVirtualGuid($context->backendName, (string)$id); + + $fields = [ + iState::COLUMN_WATCHED => (int)$isPlayed, + iState::COLUMN_GUIDS => $guids, + iState::COLUMN_META_DATA => [ + $context->backendName => [ + iState::COLUMN_WATCHED => true === $isPlayed ? '1' : '0', + iState::COLUMN_GUIDS => $guid->parse( + guids: $providersId, + context: $logContext + ), + ] + ], + iState::COLUMN_EXTRA => [ + $context->backendName => [ + iState::COLUMN_EXTRA_EVENT => $event, + iState::COLUMN_EXTRA_DATE => makeDate('now'), + ], + ], + ]; + + if (true === $isPlayed && null !== $lastPlayedAt) { + $lastPlayedAt = makeDate($lastPlayedAt)->getTimestamp(); + $fields = array_replace_recursive($fields, [ + iState::COLUMN_UPDATED => $lastPlayedAt, + iState::COLUMN_META_DATA => [ + $context->backendName => [ + iState::COLUMN_META_DATA_PLAYED_AT => (string)$lastPlayedAt, + ] + ], + ]); } $entity = $this->createEntity( context: $context, guid: $guid, item: $obj, - opts: ['override' => $fields], + opts: ['override' => $fields, Options::DISABLE_GUID => $disableGuid], )->setIsTainted(isTainted: true === in_array($event, self::WEBHOOK_TAINTED_EVENTS)); if (false === $entity->hasGuids() && false === $entity->hasRelativeGuid()) { diff --git a/src/Backends/Jellyfin/JellyfinActionTrait.php b/src/Backends/Jellyfin/JellyfinActionTrait.php index c60a645f..16fa3cf9 100644 --- a/src/Backends/Jellyfin/JellyfinActionTrait.php +++ b/src/Backends/Jellyfin/JellyfinActionTrait.php @@ -44,6 +44,7 @@ trait JellyfinActionTrait $type = JellyfinClient::TYPE_MAPPER[ag($item, 'Type')] ?? ag($item, 'Type'); $logContext = [ + 'backend' => $context->backendName, 'item' => [ 'id' => (string)ag($item, 'Id'), 'type' => ag($item, 'Type'), @@ -64,7 +65,12 @@ trait JellyfinActionTrait ], ]; - $guids = $guid->get(guids: ag($item, 'ProviderIds', []), context: $logContext); + if (iState::TYPE_EPISODE === $type && true === (bool)ag($opts, Options::DISABLE_GUID, false)) { + $guids = []; + } else { + $guids = $guid->get(guids: ag($item, 'ProviderIds', []), context: $logContext); + } + $guids += Guid::makeVirtualGuid($context->backendName, (string)ag($item, 'Id')); $builder = [ @@ -147,6 +153,13 @@ trait JellyfinActionTrait $builder = array_replace_recursive($builder, $opts['override'] ?? []); } + if (true === is_array($builder[iState::COLUMN_GUIDS] ?? false)) { + $builder[iState::COLUMN_GUIDS] = Guid::fromArray( + payload: $builder[iState::COLUMN_GUIDS], + context: $logContext, + )->getAll(); + } + return Container::get(iState::class)::fromArray($builder); } diff --git a/src/Backends/Jellyfin/JellyfinGuid.php b/src/Backends/Jellyfin/JellyfinGuid.php index 412218bb..264c1e33 100644 --- a/src/Backends/Jellyfin/JellyfinGuid.php +++ b/src/Backends/Jellyfin/JellyfinGuid.php @@ -124,21 +124,23 @@ class JellyfinGuid implements iGuid $guid[self::GUID_MAPPER[$key]] = $value; } catch (Throwable $e) { - $this->logger->error( - 'Unhandled exception was thrown in parsing of [%(backend)] [%(agent)] identifier.', - [ - 'backend' => $this->context->backendName, - 'agent' => $value, - 'exception' => [ - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'kind' => get_class($e), - 'message' => $e->getMessage(), - ], - 'trace' => $this->context->trace ? $e->getTrace() : [], - ...$context, - ] - ); + if (true === $log) { + $this->logger->error( + 'Unhandled exception was thrown in parsing of [%(backend)] [%(agent)] identifier.', + [ + 'backend' => $this->context->backendName, + 'agent' => $value, + 'exception' => [ + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'kind' => get_class($e), + 'message' => $e->getMessage(), + ], + 'trace' => $this->context->trace ? $e->getTrace() : [], + ...$context, + ] + ); + } continue; } } diff --git a/src/Backends/Plex/Action/ParseWebhook.php b/src/Backends/Plex/Action/ParseWebhook.php index 41a7da32..969e6ac3 100644 --- a/src/Backends/Plex/Action/ParseWebhook.php +++ b/src/Backends/Plex/Action/ParseWebhook.php @@ -12,8 +12,10 @@ use App\Backends\Common\Levels; use App\Backends\Common\Response; use App\Backends\Plex\PlexActionTrait; use App\Backends\Plex\PlexClient; -use App\Libs\Entity\StateInterface as iFace; +use App\Libs\Config; +use App\Libs\Entity\StateInterface as iState; use App\Libs\Guid; +use App\Libs\Options; use Psr\Http\Message\ServerRequestInterface as iRequest; use Throwable; @@ -104,53 +106,27 @@ final class ParseWebhook } try { + $obj = ag($this->getItemDetails(context: $context, id: $id), 'MediaContainer.Metadata.0', []); + $isPlayed = (bool)ag($item, 'viewCount', false); $lastPlayedAt = true === $isPlayed ? ag($item, 'lastViewedAt') : null; - $fields = [ - iFace::COLUMN_WATCHED => (int)$isPlayed, - iFace::COLUMN_META_DATA => [ - $context->backendName => [ - iFace::COLUMN_WATCHED => true === $isPlayed ? '1' : '0', - ] - ], - iFace::COLUMN_EXTRA => [ - $context->backendName => [ - iFace::COLUMN_EXTRA_EVENT => $event, - iFace::COLUMN_EXTRA_DATE => makeDate('now'), - ], - ], - ]; - - if (true === $isPlayed && null !== $lastPlayedAt) { - $fields = array_replace_recursive($fields, [ - iFace::COLUMN_UPDATED => (int)$lastPlayedAt, - iFace::COLUMN_META_DATA => [ - $context->backendName => [ - iFace::COLUMN_META_DATA_PLAYED_AT => (string)$lastPlayedAt, - ] - ], - ]); - } - - $obj = ag($this->getItemDetails(context: $context, id: $id), 'MediaContainer.Metadata.0', []); - $year = (int)ag($obj, ['grandParentYear', 'parentYear', 'year'], 0); if (0 === $year && null !== ($airDate = ag($obj, 'originallyAvailableAt'))) { $year = (int)makeDate($airDate)->format('Y'); } - $guids = $guid->get(guids: ag($item, 'Guid', []), context: [ + $logContext = [ 'item' => [ 'id' => ag($item, 'ratingKey'), 'type' => ag($item, 'type'), 'title' => match ($type) { - iFace::TYPE_MOVIE => sprintf( + iState::TYPE_MOVIE => sprintf( '%s (%s)', ag($item, ['title', 'originalTitle'], '??'), 0 === $year ? '0000' : $year, ), - iFace::TYPE_EPISODE => sprintf( + iState::TYPE_EPISODE => sprintf( '%s - (%sx%s)', ag($item, ['grandparentTitle', 'originalTitle', 'title'], '??'), str_pad((string)ag($item, 'parentIndex', 0), 2, '0', STR_PAD_LEFT), @@ -160,19 +136,54 @@ final class ParseWebhook 'year' => 0 === $year ? '0000' : $year, 'plex_id' => str_starts_with(ag($item, 'guid', ''), 'plex://') ? ag($item, 'guid') : 'none', ], - ]); + ]; - if (count($guids) >= 1) { - $guids += Guid::makeVirtualGuid($context->backendName, (string)$id); - $fields[iFace::COLUMN_GUIDS] = $guids; - $fields[iFace::COLUMN_META_DATA][$context->backendName][iFace::COLUMN_GUIDS] = $fields[iFace::COLUMN_GUIDS]; + $disableGuid = (bool)Config::get('episodes.disable.guid'); + + if (PlexClient::TYPE_EPISODE === $type && true === $disableGuid) { + $guids = []; + } else { + $guids = $guid->get(guids: ag($item, 'Guid', []), context: $logContext); + } + + $guids += Guid::makeVirtualGuid($context->backendName, (string)$id); + + $fields = [ + iState::COLUMN_WATCHED => (int)$isPlayed, + iState::COLUMN_GUIDS => $guids, + iState::COLUMN_META_DATA => [ + $context->backendName => [ + iState::COLUMN_WATCHED => true === $isPlayed ? '1' : '0', + iState::COLUMN_GUIDS => $guid->parse( + guids: ag($item, 'Guid', []), + context: $logContext + ), + ] + ], + iState::COLUMN_EXTRA => [ + $context->backendName => [ + iState::COLUMN_EXTRA_EVENT => $event, + iState::COLUMN_EXTRA_DATE => makeDate('now'), + ], + ], + ]; + + if (true === $isPlayed && null !== $lastPlayedAt) { + $fields = array_replace_recursive($fields, [ + iState::COLUMN_UPDATED => (int)$lastPlayedAt, + iState::COLUMN_META_DATA => [ + $context->backendName => [ + iState::COLUMN_META_DATA_PLAYED_AT => (string)$lastPlayedAt, + ] + ], + ]); } $entity = $this->createEntity( context: $context, guid: $guid, item: $obj, - opts: ['override' => $fields], + opts: ['override' => $fields, Options::DISABLE_GUID => $disableGuid], )->setIsTainted(isTainted: true === in_array($event, self::WEBHOOK_TAINTED_EVENTS)); if (false === $entity->hasGuids() && false === $entity->hasRelativeGuid()) { diff --git a/src/Backends/Plex/PlexActionTrait.php b/src/Backends/Plex/PlexActionTrait.php index ab6f0589..9c8a1f24 100644 --- a/src/Backends/Plex/PlexActionTrait.php +++ b/src/Backends/Plex/PlexActionTrait.php @@ -62,6 +62,7 @@ trait PlexActionTrait $type = $this->typeMapper[ag($item, 'type')] ?? ag($item, 'type'); $logContext = [ + 'backend' => $context->backendName, 'item' => [ 'id' => ag($item, 'ratingKey'), 'type' => ag($item, 'type'), @@ -83,7 +84,12 @@ trait PlexActionTrait ], ]; - $guids = $guid->get(guids: ag($item, 'Guid', []), context: $logContext); + if (iState::TYPE_EPISODE === $type && true === (bool)ag($opts, Options::DISABLE_GUID, false)) { + $guids = []; + } else { + $guids = $guid->get(guids: ag($item, 'Guid', []), context: $logContext); + } + $guids += Guid::makeVirtualGuid($context->backendName, (string)ag($item, 'ratingKey')); $builder = [ @@ -163,6 +169,13 @@ trait PlexActionTrait $builder = array_replace_recursive($builder, $opts['override'] ?? []); } + if (true === is_array($builder[iState::COLUMN_GUIDS] ?? false)) { + $builder[iState::COLUMN_GUIDS] = Guid::fromArray( + payload: $builder[iState::COLUMN_GUIDS], + context: $logContext, + )->getAll(); + } + return Container::get(iState::class)::fromArray($builder); } diff --git a/src/Backends/Plex/PlexGuid.php b/src/Backends/Plex/PlexGuid.php index 22ca74a8..5fda48cb 100644 --- a/src/Backends/Plex/PlexGuid.php +++ b/src/Backends/Plex/PlexGuid.php @@ -186,21 +186,23 @@ final class PlexGuid implements GuidInterface $guid[self::GUID_MAPPER[$key]] = $value; } catch (Throwable $e) { - $this->logger->error( - 'Unhandled exception was thrown in parsing of [%(backend)] [%(agent)] identifier.', - [ - 'backend' => $this->context->backendName, - 'agent' => $val ?? null, - 'exception' => [ - 'file' => $e->getFile(), - 'line' => $e->getLine(), - 'kind' => get_class($e), - 'message' => $e->getMessage(), - ], - 'trace' => $this->context->trace ? $e->getTrace() : [], - ...$context, - ] - ); + if (true === $log) { + $this->logger->error( + 'Unhandled exception was thrown in parsing of [%(backend)] [%(agent)] identifier.', + [ + 'backend' => $this->context->backendName, + 'agent' => $val ?? null, + 'exception' => [ + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'kind' => get_class($e), + 'message' => $e->getMessage(), + ], + 'trace' => $this->context->trace ? $e->getTrace() : [], + ...$context, + ] + ); + } continue; } } diff --git a/src/Commands/State/ExportCommand.php b/src/Commands/State/ExportCommand.php index aee24e73..31684752 100644 --- a/src/Commands/State/ExportCommand.php +++ b/src/Commands/State/ExportCommand.php @@ -150,6 +150,10 @@ class ExportCommand extends Command $opts[Options::IGNORE_DATE] = true; } + if ($input->getOption('trace')) { + $opts[Options::DEBUG_TRACE] = true; + } + if ($input->getOption('dry-run')) { $opts[Options::DRY_RUN] = true; } diff --git a/src/Libs/Guid.php b/src/Libs/Guid.php index 2dd3dd8e..3cf0cece 100644 --- a/src/Libs/Guid.php +++ b/src/Libs/Guid.php @@ -5,10 +5,12 @@ declare(strict_types=1); namespace App\Libs; use InvalidArgumentException; +use JsonSerializable; use Psr\Log\LoggerInterface; use RuntimeException; +use Stringable; -final class Guid +final class Guid implements JsonSerializable, Stringable { public const GUID_IMDB = 'guid_imdb'; public const GUID_TVDB = 'guid_tvdb'; @@ -268,4 +270,14 @@ final class Guid return self::$logger; } + + public function jsonSerialize(): mixed + { + return $this->getAll(); + } + + public function __toString(): string + { + return json_encode($this->getAll()); + } } diff --git a/src/Libs/Options.php b/src/Libs/Options.php index 5ea08812..0cd06111 100644 --- a/src/Libs/Options.php +++ b/src/Libs/Options.php @@ -18,6 +18,7 @@ final class Options public const MAPPER_DISABLE_AUTOCOMMIT = 'DISABLE_AUTOCOMMIT'; public const IMPORT_METADATA_ONLY = 'IMPORT_METADATA_ONLY'; public const MISMATCH_DEEP_SCAN = 'MISMATCH_DEEP_SCAN'; + public const DISABLE_GUID = 'DISABLE_GUID'; private function __construct() { diff --git a/src/Libs/Servers/JellyfinServer.php b/src/Libs/Servers/JellyfinServer.php index 77855651..f3d1a266 100644 --- a/src/Libs/Servers/JellyfinServer.php +++ b/src/Libs/Servers/JellyfinServer.php @@ -19,6 +19,7 @@ use App\Backends\Jellyfin\Action\SearchId; use App\Backends\Jellyfin\Action\SearchQuery; use App\Backends\Jellyfin\JellyfinActionTrait; use App\Backends\Jellyfin\JellyfinGuid; +use App\Libs\Config; use App\Libs\Container; use App\Libs\Entity\StateInterface as iFace; use App\Libs\HttpException; @@ -260,7 +261,10 @@ class JellyfinServer implements ServerInterface context: $this->context, guid: $this->guid, mapper: $mapper, - after: $after + after: $after, + opts: [ + Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'), + ] ); if ($response->hasError()) { @@ -281,7 +285,10 @@ class JellyfinServer implements ServerInterface guid: $this->guid, mapper: $mapper, after: $after, - opts: ['queue' => $queue] + opts: [ + 'queue' => $queue, + Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'), + ] ); if ($response->hasError()) { diff --git a/src/Libs/Servers/PlexServer.php b/src/Libs/Servers/PlexServer.php index 75ac8678..46c052b1 100644 --- a/src/Libs/Servers/PlexServer.php +++ b/src/Libs/Servers/PlexServer.php @@ -19,6 +19,7 @@ use App\Backends\Plex\Action\SearchId; use App\Backends\Plex\Action\SearchQuery; use App\Backends\Plex\PlexActionTrait; use App\Backends\Plex\PlexGuid; +use App\Libs\Config; use App\Libs\Container; use App\Libs\Entity\StateInterface as iFace; use App\Libs\HttpException; @@ -252,7 +253,10 @@ class PlexServer implements ServerInterface context: $this->context, guid: $this->guid, mapper: $mapper, - after: $after + after: $after, + opts: [ + Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'), + ] ); if ($response->hasError()) { @@ -273,7 +277,10 @@ class PlexServer implements ServerInterface guid: $this->guid, mapper: $mapper, after: $after, - opts: ['queue' => $queue], + opts: [ + 'queue' => $queue, + Options::DISABLE_GUID => (bool)Config::get('episodes.disable.guid'), + ], ); if ($response->hasError()) {