From c0be4ae42934a20981bc3a5a030224ac592a4731 Mon Sep 17 00:00:00 2001 From: "Abdulmhsen B. A. A" Date: Sun, 13 Feb 2022 19:12:58 +0300 Subject: [PATCH] Replace guzzlephp with Symfony/http-client to support http2. Closes #1 --- README.md | 4 +- composer.json | 7 +- composer.lock | 783 +++++++++-------------- config/config.php | 20 +- config/services.php | 12 + public/index.php | 28 +- src/Commands/State/ExportCommand.php | 75 ++- src/Commands/State/ImportCommand.php | 27 +- src/Libs/Extends/Request.php | 16 - src/Libs/KernelConsole.php | 12 +- src/Libs/Mappers/Export/ExportMapper.php | 6 +- src/Libs/Mappers/ExportInterface.php | 6 +- src/Libs/Mappers/ImportInterface.php | 3 +- src/Libs/Scheduler/Scheduler.php | 6 +- src/Libs/Servers/EmbyServer.php | 4 +- src/Libs/Servers/JellyfinServer.php | 104 ++- src/Libs/Servers/PlexServer.php | 104 ++- src/Libs/Servers/ServerInterface.php | 12 +- src/Libs/helpers.php | 34 +- 19 files changed, 594 insertions(+), 669 deletions(-) delete mode 100644 src/Libs/Extends/Request.php diff --git a/README.md b/README.md index 675287f7..84e4b2dc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Warning -This is an early release version, expect bugs and edge cases that we haven't encountered. Please keep that in mind that -before running the tool. while its works for me, it might not work for your setup. +This is an early release version, expect bugs and edge cases that we haven't encountered. Please keep that in mind +before running this tool. while its works for me, it might not work for your setup. # Watch State Sync (Early Preview) diff --git a/composer.json b/composer.json index 2ea48f3f..edf5aa4e 100644 --- a/composer.json +++ b/composer.json @@ -15,14 +15,17 @@ "ext-pdo": "*", "ext-mbstring": "*", "ext-ctype": "*", + "ext-curl": "*", "ext-sqlite3": "*", "monolog/monolog": "^2.3", "symfony/console": "^6.0", "symfony/yaml": "^6.0", "symfony/process": "^6.0", + "symfony/http-client": "^6.0", "league/container": "^4.0", - "guzzlehttp/guzzle": "^7.0", - "laminas/laminas-diactoros": "^2.0", + "psr/http-client": "^1.0", + "nyholm/psr7": "^1.5", + "nyholm/psr7-server": "^1.0", "laminas/laminas-httphandlerrunner": "^2.0", "dragonmantank/cron-expression": "^3.0" }, diff --git a/composer.lock b/composer.lock index c32561e3..c911bf51 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "88f26706b74e61345a7e258a344a57fb", + "content-hash": "18f5d09d9f78ef4af2bf08aa9a01dd30", "packages": [ { "name": "dragonmantank/cron-expression", @@ -67,428 +67,6 @@ ], "time": "2022-01-18T15:43:28+00:00" }, - { - "name": "guzzlehttp/guzzle", - "version": "7.4.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ee0a041b1760e6a53d2a39c8c34115adc2af2c79", - "reference": "ee0a041b1760e6a53d2a39c8c34115adc2af2c79", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.8.3 || ^2.1", - "php": "^7.2.5 || ^8.0", - "psr/http-client": "^1.0", - "symfony/deprecation-contracts": "^2.2 || ^3.0" - }, - "provide": { - "psr/http-client-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "ext-curl": "*", - "php-http/client-integration-tests": "^3.0", - "phpunit/phpunit": "^8.5.5 || ^9.3.5", - "psr/log": "^1.1 || ^2.0 || ^3.0" - }, - "suggest": { - "ext-curl": "Required for CURL handler support", - "ext-intl": "Required for Internationalized Domain Name (IDN) support", - "psr/log": "Required for using the Log middleware" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "7.4-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle is a PHP HTTP client library", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "psr-18", - "psr-7", - "rest", - "web service" - ], - "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.4.1" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", - "type": "tidelift" - } - ], - "time": "2021-12-06T18:43:05+00:00" - }, - { - "name": "guzzlehttp/promises", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/promises.git", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/fe752aedc9fd8fcca3fe7ad05d419d32998a06da", - "reference": "fe752aedc9fd8fcca3fe7ad05d419d32998a06da", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "symfony/phpunit-bridge": "^4.4 || ^5.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - } - ], - "description": "Guzzle promises library", - "keywords": [ - "promise" - ], - "support": { - "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.5.1" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", - "type": "tidelift" - } - ], - "time": "2021-10-22T20:56:57+00:00" - }, - { - "name": "guzzlehttp/psr7", - "version": "2.1.0", - "source": { - "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/089edd38f5b8abba6cb01567c2a8aaa47cec4c72", - "reference": "089edd38f5b8abba6cb01567c2a8aaa47cec4c72", - "shasum": "" - }, - "require": { - "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "ralouphie/getallheaders": "^3.0" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.8 || ^9.3.10" - }, - "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.1-dev" - } - }, - "autoload": { - "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Graham Campbell", - "email": "hello@gjcampbell.co.uk", - "homepage": "https://github.com/GrahamCampbell" - }, - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, - { - "name": "George Mponos", - "email": "gmponos@gmail.com", - "homepage": "https://github.com/gmponos" - }, - { - "name": "Tobias Nyholm", - "email": "tobias.nyholm@gmail.com", - "homepage": "https://github.com/Nyholm" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://github.com/sagikazarmark" - }, - { - "name": "Tobias Schultze", - "email": "webmaster@tubo-world.de", - "homepage": "https://github.com/Tobion" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" - } - ], - "description": "PSR-7 message implementation that also provides common utility methods", - "keywords": [ - "http", - "message", - "psr-7", - "request", - "response", - "stream", - "uri", - "url" - ], - "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.1.0" - }, - "funding": [ - { - "url": "https://github.com/GrahamCampbell", - "type": "github" - }, - { - "url": "https://github.com/Nyholm", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", - "type": "tidelift" - } - ], - "time": "2021-10-06T17:43:30+00:00" - }, - { - "name": "laminas/laminas-diactoros", - "version": "2.8.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-diactoros.git", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "reference": "0c26ef1d95b6d7e6e3943a243ba3dc0797227199", - "shasum": "" - }, - "require": { - "php": "^7.3 || ~8.0.0 || ~8.1.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0" - }, - "conflict": { - "phpspec/prophecy": "<1.9.0", - "zendframework/zend-diactoros": "*" - }, - "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" - }, - "require-dev": { - "ext-curl": "*", - "ext-dom": "*", - "ext-gd": "*", - "ext-libxml": "*", - "http-interop/http-factory-tests": "^0.8.0", - "laminas/laminas-coding-standard": "~1.0.0", - "php-http/psr7-integration-tests": "^1.1", - "phpspec/prophecy-phpunit": "^2.0", - "phpunit/phpunit": "^9.1", - "psalm/plugin-phpunit": "^0.14.0", - "vimeo/psalm": "^4.3" - }, - "type": "library", - "extra": { - "laminas": { - "config-provider": "Laminas\\Diactoros\\ConfigProvider", - "module": "Laminas\\Diactoros" - } - }, - "autoload": { - "files": [ - "src/functions/create_uploaded_file.php", - "src/functions/marshal_headers_from_sapi.php", - "src/functions/marshal_method_from_sapi.php", - "src/functions/marshal_protocol_version_from_sapi.php", - "src/functions/marshal_uri_from_sapi.php", - "src/functions/normalize_server.php", - "src/functions/normalize_uploaded_files.php", - "src/functions/parse_cookie_header.php", - "src/functions/create_uploaded_file.legacy.php", - "src/functions/marshal_headers_from_sapi.legacy.php", - "src/functions/marshal_method_from_sapi.legacy.php", - "src/functions/marshal_protocol_version_from_sapi.legacy.php", - "src/functions/marshal_uri_from_sapi.legacy.php", - "src/functions/normalize_server.legacy.php", - "src/functions/normalize_uploaded_files.legacy.php", - "src/functions/parse_cookie_header.legacy.php" - ], - "psr-4": { - "Laminas\\Diactoros\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "PSR HTTP Message implementations", - "homepage": "https://laminas.dev", - "keywords": [ - "http", - "laminas", - "psr", - "psr-17", - "psr-7" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-diactoros/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-diactoros/issues", - "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", - "source": "https://github.com/laminas/laminas-diactoros" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2021-09-22T03:54:36+00:00" - }, { "name": "laminas/laminas-httphandlerrunner", "version": "2.1.0", @@ -737,6 +315,203 @@ ], "time": "2021-10-01T21:08:31+00:00" }, + { + "name": "nyholm/psr7", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7.git", + "reference": "1461e07a0f2a975a52082ca3b769ca912b816226" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7/zipball/1461e07a0f2a975a52082ca3b769ca912b816226", + "reference": "1461e07a0f2a975a52082ca3b769ca912b816226", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "php-http/message-factory": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "php-http/psr7-integration-tests": "^1.0", + "phpunit/phpunit": "^7.5 || 8.5 || 9.4", + "symfony/error-handler": "^4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "Nyholm\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "A fast PHP7 implementation of PSR-7", + "homepage": "https://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7/issues", + "source": "https://github.com/Nyholm/psr7/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2022-02-02T18:37:57+00:00" + }, + { + "name": "nyholm/psr7-server", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/Nyholm/psr7-server.git", + "reference": "b846a689844cef114e8079d8c80f0afd96745ae3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Nyholm/psr7-server/zipball/b846a689844cef114e8079d8c80f0afd96745ae3", + "reference": "b846a689844cef114e8079d8c80f0afd96745ae3", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "nyholm/nsa": "^1.1", + "nyholm/psr7": "^1.3", + "phpunit/phpunit": "^7.0 || ^8.5 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nyholm\\Psr7Server\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com" + }, + { + "name": "Martijn van der Ven", + "email": "martijn@vanderven.se" + } + ], + "description": "Helper classes to handle PSR-7 server requests", + "homepage": "http://tnyholm.se", + "keywords": [ + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/Nyholm/psr7-server/issues", + "source": "https://github.com/Nyholm/psr7-server/tree/1.0.2" + }, + "funding": [ + { + "url": "https://github.com/Zegnat", + "type": "github" + }, + { + "url": "https://github.com/nyholm", + "type": "github" + } + ], + "time": "2021-05-12T11:11:27+00:00" + }, + { + "name": "php-http/message-factory", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/master" + }, + "time": "2015-12-19T14:08:53+00:00" + }, { "name": "psr/container", "version": "2.0.2", @@ -1057,50 +832,6 @@ }, "time": "2021-07-14T16:46:02+00:00" }, - { - "name": "ralouphie/getallheaders", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/getallheaders.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" - } - ], - "description": "A polyfill for getallheaders.", - "support": { - "issues": "https://github.com/ralouphie/getallheaders/issues", - "source": "https://github.com/ralouphie/getallheaders/tree/develop" - }, - "time": "2019-03-08T08:55:37+00:00" - }, { "name": "symfony/console", "version": "v6.0.3", @@ -1197,22 +928,109 @@ "time": "2022-01-26T17:23:29+00:00" }, { - "name": "symfony/deprecation-contracts", + "name": "symfony/http-client", + "version": "v6.0.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client.git", + "reference": "45b95017f6a20d564584bdee6a376c9a79caa316" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client/zipball/45b95017f6a20d564584bdee6a376c9a79caa316", + "reference": "45b95017f6a20d564584bdee6a376c9a79caa316", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/log": "^1|^2|^3", + "symfony/http-client-contracts": "^3", + "symfony/service-contracts": "^1.0|^2|^3" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/amp": "^2.5", + "amphp/http-client": "^4.2.1", + "amphp/http-tunnel": "^1.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/http-kernel": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/stopwatch": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-client/tree/v6.0.3" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-22T06:58:00+00:00" + }, + { + "name": "symfony/http-client-contracts", "version": "v3.0.0", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced" + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "265f03fed057044a8e4dc159aa33596d0f48ed3f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", - "reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/265f03fed057044a8e4dc159aa33596d0f48ed3f", + "reference": "265f03fed057044a8e4dc159aa33596d0f48ed3f", "shasum": "" }, "require": { "php": ">=8.0.2" }, + "suggest": { + "symfony/http-client-implementation": "" + }, "type": "library", "extra": { "branch-alias": { @@ -1224,9 +1042,9 @@ } }, "autoload": { - "files": [ - "function.php" - ] + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -1242,10 +1060,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Generic abstractions related to HTTP clients", "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.0.0" }, "funding": [ { @@ -1261,7 +1087,7 @@ "type": "tidelift" } ], - "time": "2021-11-01T23:48:49+00:00" + "time": "2021-11-03T13:44:55+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2569,6 +2395,7 @@ "ext-pdo": "*", "ext-mbstring": "*", "ext-ctype": "*", + "ext-curl": "*", "ext-sqlite3": "*" }, "platform-dev": [], diff --git a/config/config.php b/config/config.php index 7bb0bb53..becf68df 100644 --- a/config/config.php +++ b/config/config.php @@ -2,7 +2,6 @@ declare(strict_types=1); -use App\Libs\Config; use App\Libs\Mappers\Export\ExportMapper; use App\Libs\Mappers\Import\MemoryMapper; use App\Libs\Scheduler\Task; @@ -10,7 +9,6 @@ use App\Libs\Servers\JellyfinServer; use App\Libs\Servers\PlexServer; use App\Libs\Storage\PDO\PDOAdapter; -use GuzzleHttp\RequestOptions; use Monolog\Logger; return (function () { @@ -59,20 +57,22 @@ ], ]; - $config['request'] = [ + $config['http'] = [ 'default' => [ 'options' => [ - RequestOptions::FORCE_IP_RESOLVE => 'v4', - RequestOptions::HEADERS => [ - 'User-Agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'WatchState/' . Config::get('version'), + 'headers' => [ + 'User-Agent' => 'WatchState/' . ag($config, 'version'), ], - ] - ], - 'export' => [ - 'concurrency' => 75 + 'extra' => [ + 'curl' => [ + CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, + ], + ], + ], ], ]; + $config['debug'] = [ 'profiler' => [ 'options' => [ diff --git a/config/services.php b/config/services.php index 0763a0c9..21a29324 100644 --- a/config/services.php +++ b/config/services.php @@ -2,13 +2,25 @@ declare(strict_types=1); +use App\Libs\Config; use Monolog\Logger; use Psr\Log\LoggerInterface; +use Symfony\Component\HttpClient\CurlHttpClient; +use Symfony\Contracts\HttpClient\HttpClientInterface; return (function (): array { return [ LoggerInterface::class => [ 'class' => fn() => new Logger('logger') ], + HttpClientInterface::class => [ + 'class' => function (): HttpClientInterface { + return new CurlHttpClient( + defaultOptions: Config::get('http.default.options', []), + maxHostConnections: Config::get('http.default.maxHostConnections', 25), + maxPendingPushes: Config::get('http.default.maxPendingPushes', 50), + ); + } + ], ]; })(); diff --git a/public/index.php b/public/index.php index 653c593f..9e34872c 100644 --- a/public/index.php +++ b/public/index.php @@ -7,8 +7,7 @@ use App\Libs\HttpException; use App\Libs\Servers\ServerInterface; use App\Libs\Storage\StorageInterface; -use Laminas\Diactoros\Response\EmptyResponse; -use Laminas\Diactoros\Response\JsonResponse; +use Nyholm\Psr7\Response; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; @@ -99,43 +98,38 @@ $entity = $backend::parseWebhook($request); if (null === $entity || !$entity->hasGuids()) { - return new EmptyResponse(200, ['X-Status' => 'No GUIDs.']); + return new Response(status: 200, headers: ['X-Status' => 'No GUIDs.']); } $storage = Container::get(StorageInterface::class); if (null === ($backend = $storage->get($entity))) { $storage->insert($entity); - return new JsonResponse($entity->getAll(), 200); + return jsonResponse(status: 200, body: $entity->getAll()); } if ($backend->updated > $entity->updated) { - return new EmptyResponse(200, ['X-Status' => 'Entity date is older than what available in storage.']); + return new Response( + status: 200, + headers: ['X-Status' => 'Entity date is older than what available in storage.'] + ); } if ($backend->apply($entity)->isChanged()) { $backend = $storage->update($backend); - return new JsonResponse($backend->getAll(), 200); + return jsonResponse(status: 200, body: $backend->getAll()); } - return new EmptyResponse(200, ['X-Status' => 'Entity is unchanged.']); + return new Response(status: 200, headers: ['X-Status' => 'Entity is unchanged.']); } catch (HttpException $e) { Container::get(LoggerInterface::class)->error($e->getMessage()); if (200 === $e->getCode()) { - return new EmptyResponse($e->getCode(), [ - 'X-Status' => $e->getMessage(), - ]); + return new Response(status: $e->getCode(), headers: ['X-Status' => $e->getMessage()]); } - return new JsonResponse( - [ - 'error' => true, - 'message' => $e->getMessage(), - ], - $e->getCode() - ); + return jsonResponse(status: $e->getCode(), body: ['error' => true, 'message' => $e->getMessage()]); } }; diff --git a/src/Commands/State/ExportCommand.php b/src/Commands/State/ExportCommand.php index e80975e6..061c041a 100644 --- a/src/Commands/State/ExportCommand.php +++ b/src/Commands/State/ExportCommand.php @@ -9,25 +9,21 @@ use App\Libs\Container; use App\Libs\Data; use App\Libs\Extends\CliLogger; -use App\Libs\Extends\Request; use App\Libs\Mappers\ExportInterface; use App\Libs\Servers\ServerInterface; -use Generator; -use GuzzleHttp\Pool; -use GuzzleHttp\Promise\Utils; -use GuzzleHttp\Psr7\Uri; -use Psr\Http\Message\ResponseInterface; +use Nyholm\Psr7\Uri; use Psr\Log\LoggerInterface; use RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\HttpClient\Exception\ServerException; use Symfony\Component\Yaml\Yaml; -use Throwable; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; class ExportCommand extends Command { - public function __construct(private ExportInterface $mapper, private Request $http, private LoggerInterface $logger) + public function __construct(private ExportInterface $mapper, private LoggerInterface $logger) { set_time_limit(0); ini_set('memory_limit', '-1'); @@ -43,13 +39,6 @@ protected function configure(): void ->addOption('redirect-logger', 'r', InputOption::VALUE_NONE, 'Redirect logger to stdout.') ->addOption('memory-usage', 'm', InputOption::VALUE_NONE, 'Show memory usage.') ->addOption('force-full', 'f', InputOption::VALUE_NONE, 'Force full export.') - ->addOption( - 'concurrency', - null, - InputOption::VALUE_OPTIONAL, - 'How many parallel requests to send.', - (int)Config::get('request.export.concurrency') - ) ->addOption( 'servers-filter', 's', @@ -126,7 +115,7 @@ protected function runCommand(InputInterface $input, OutputInterface $output): i $logger = new CliLogger($output, (bool)$input->getOption('memory-usage')); } - $promises = []; + $requests = []; if (count($list) >= 1) { $this->mapper->loadData(); @@ -168,7 +157,7 @@ protected function runCommand(InputInterface $input, OutputInterface $output): i ); } - array_push($promises, ...$class->push($this->mapper, $after)); + array_push($requests, ...$class->push($this->mapper, $after)); if (true === Data::get(sprintf('%s.no_export_update', $name))) { $this->logger->notice( @@ -179,9 +168,20 @@ protected function runCommand(InputInterface $input, OutputInterface $output): i } } - $this->logger->notice(sprintf('Waiting on (%d) (Compare State) Requests.', count($promises))); - Utils::settle($promises)->wait(); - $this->logger->notice(sprintf('Finished waiting on (%d) Requests.', count($promises))); + $this->logger->notice(sprintf('Waiting on (%d) (Compare State) Requests.', count($requests))); + foreach ($requests as $response) { + $requestData = $response->getInfo('user_data'); + try { + if (200 === $response->getStatusCode()) { + $requestData['ok']($response); + } else { + $requestData['error']($response); + } + } catch (ExceptionInterface $e) { + $requestData['error']($e); + } + } + $this->logger->notice(sprintf('Finished waiting on (%d) Requests.', count($requests))); $changes = $this->mapper->getQueue(); @@ -190,25 +190,24 @@ protected function runCommand(InputInterface $input, OutputInterface $output): i return self::SUCCESS; } - $pool = new Pool( - $this->http, - (function () use ($changes): Generator { - foreach ($changes as $request) { - yield $request; - } - })(), - [ - 'concurrency' => $input->getOption('concurrency'), - 'fulfilled' => function (ResponseInterface $response) { - }, - 'rejected' => function (Throwable $reason) { - $this->logger->error($reason->getMessage()); - }, - ] - ); - $this->logger->notice(sprintf('Waiting on (%d) (Stats Change) Requests.', count($changes))); - $pool->promise()->wait(); + foreach ($changes as $response) { + $requestData = $response->getInfo('user_data'); + try { + if (200 !== $response->getStatusCode()) { + throw new ServerException($response); + } + $this->logger->debug( + sprintf( + 'Processed: State (%s) - %s', + ag($requestData, 'state', '??'), + ag($requestData, 'itemName', '??'), + ) + ); + } catch (ExceptionInterface $e) { + $this->logger->error($e->getMessage()); + } + } $this->logger->notice(sprintf('Finished waiting on (%d) Requests.', count($changes))); // -- Update Server.yaml with new lastSync date. diff --git a/src/Commands/State/ImportCommand.php b/src/Commands/State/ImportCommand.php index 76cfee30..ee287387 100644 --- a/src/Commands/State/ImportCommand.php +++ b/src/Commands/State/ImportCommand.php @@ -12,14 +12,15 @@ use App\Libs\Extends\CliLogger; use App\Libs\Mappers\ImportInterface; use App\Libs\Servers\ServerInterface; -use GuzzleHttp\Promise\Utils; -use GuzzleHttp\Psr7\Uri; +use Nyholm\Psr7\Uri; use Psr\Log\LoggerInterface; use RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Yaml\Yaml; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; class ImportCommand extends Command { @@ -115,7 +116,8 @@ protected function runCommand(InputInterface $input, OutputInterface $output): i $logger = new CliLogger($output, (bool)$input->getOption('memory-usage')); } - $promises = []; + /** @var array $requests */ + $requests = []; if (count($list) >= 1) { $this->mapper->loadData(); @@ -157,7 +159,7 @@ protected function runCommand(InputInterface $input, OutputInterface $output): i ); } - array_push($promises, ...$class->pull($this->mapper, $after)); + array_push($requests, ...$class->pull($this->mapper, $after)); if (true === Data::get(sprintf('%s.no_import_update', $name))) { $this->logger->notice( @@ -168,9 +170,20 @@ protected function runCommand(InputInterface $input, OutputInterface $output): i } } - $this->logger->notice(sprintf('Waiting on (%d) HTTP Requests.', count($promises))); - Utils::settle($promises)->wait(); - $this->logger->notice(sprintf('Finished waiting on (%d) HTTP Requests.', count($promises))); + $this->logger->notice(sprintf('Waiting on (%d) HTTP Requests.', count($requests))); + foreach ($requests as $response) { + $requestData = $response->getInfo('user_data'); + try { + if (200 === $response->getStatusCode()) { + $requestData['ok']($response); + } else { + $requestData['error']($response); + } + } catch (ExceptionInterface $e) { + $requestData['error']($e); + } + } + $this->logger->notice(sprintf('Finished waiting on (%d) HTTP Requests.', count($requests))); $this->logger->notice(sprintf('Committing (%d) Changes.', count($this->mapper))); $operations = $this->mapper->commit(); diff --git a/src/Libs/Extends/Request.php b/src/Libs/Extends/Request.php deleted file mode 100644 index 82f91928..00000000 --- a/src/Libs/Extends/Request.php +++ /dev/null @@ -1,16 +0,0 @@ -fromGlobals(); + } try { $response = $fn($request); } catch (Throwable $e) { Container::get(LoggerInterface::class)->error($e->getMessage()); - $response = new EmptyResponse(500); + $response = new Response(500); } $emitter->emit($response); diff --git a/src/Libs/Mappers/Export/ExportMapper.php b/src/Libs/Mappers/Export/ExportMapper.php index 8895b05b..020783ca 100644 --- a/src/Libs/Mappers/Export/ExportMapper.php +++ b/src/Libs/Mappers/Export/ExportMapper.php @@ -9,8 +9,8 @@ use App\Libs\Mappers\ExportInterface; use App\Libs\Storage\StorageInterface; use DateTimeInterface; -use Psr\Http\Message\RequestInterface; use Psr\Log\LoggerInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; final class ExportMapper implements ExportInterface { @@ -25,7 +25,7 @@ final class ExportMapper implements ExportInterface private array $guids = []; /** - * @var array Queued Requests. + * @var array Queued Requests. */ private array $queue = []; @@ -79,7 +79,7 @@ public function getQueue(): array return $this->queue; } - public function queue(RequestInterface $request): self + public function queue(ResponseInterface $request): self { $this->queue[] = $request; diff --git a/src/Libs/Mappers/ExportInterface.php b/src/Libs/Mappers/ExportInterface.php index 4462b4b3..800b455b 100644 --- a/src/Libs/Mappers/ExportInterface.php +++ b/src/Libs/Mappers/ExportInterface.php @@ -7,8 +7,8 @@ use App\Libs\Entity\StateEntity; use App\Libs\Storage\StorageInterface; use DateTimeInterface; -use Psr\Http\Message\RequestInterface; use Psr\Log\LoggerInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; interface ExportInterface { @@ -40,11 +40,11 @@ public function getQueue(): array; /** * Queue State change request. * - * @param RequestInterface $request + * @param ResponseInterface $request * * @return self */ - public function queue(RequestInterface $request): self; + public function queue(ResponseInterface $request): self; /** * Inject Logger. diff --git a/src/Libs/Mappers/ImportInterface.php b/src/Libs/Mappers/ImportInterface.php index e1fb93c3..884b681f 100644 --- a/src/Libs/Mappers/ImportInterface.php +++ b/src/Libs/Mappers/ImportInterface.php @@ -6,10 +6,11 @@ use App\Libs\Entity\StateEntity; use App\Libs\Storage\StorageInterface; +use Countable; use DateTimeImmutable; use Psr\Log\LoggerInterface; -interface ImportInterface extends \Countable +interface ImportInterface extends Countable { /** * Initiate Mapper. diff --git a/src/Libs/Scheduler/Scheduler.php b/src/Libs/Scheduler/Scheduler.php index ff0f6467..8e460ff0 100644 --- a/src/Libs/Scheduler/Scheduler.php +++ b/src/Libs/Scheduler/Scheduler.php @@ -4,6 +4,8 @@ namespace App\Libs\Scheduler; +use DateTimeInterface; + final class Scheduler { /** @@ -48,10 +50,10 @@ private function getQueuedTasks(): array /** * Run the scheduler. * - * @param \DateTimeInterface $runTime Run at specific moment. + * @param DateTimeInterface $runTime Run at specific moment. * @return array Executed tasks */ - public function run(\DateTimeInterface $runTime): array + public function run(DateTimeInterface $runTime): array { foreach ($this->getQueuedTasks() as $task) { if (!$task->isDue($runTime)) { diff --git a/src/Libs/Servers/EmbyServer.php b/src/Libs/Servers/EmbyServer.php index 526e0f81..fb984552 100644 --- a/src/Libs/Servers/EmbyServer.php +++ b/src/Libs/Servers/EmbyServer.php @@ -7,8 +7,8 @@ use App\Libs\Config; use App\Libs\Entity\StateEntity; use App\Libs\HttpException; -use GuzzleHttp\Psr7\Uri; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UriInterface; class EmbyServer extends JellyfinServer { @@ -23,7 +23,7 @@ class EmbyServer extends JellyfinServer 'playback.scrobble', ]; - public function setUp(string $name, Uri $url, string|int|null $token = null, array $options = []): ServerInterface + public function setUp(string $name, UriInterface $url, string|int|null $token = null, array $options = []): ServerInterface { $options['emby'] = true; diff --git a/src/Libs/Servers/JellyfinServer.php b/src/Libs/Servers/JellyfinServer.php index 2e733337..f2f0b978 100644 --- a/src/Libs/Servers/JellyfinServer.php +++ b/src/Libs/Servers/JellyfinServer.php @@ -7,21 +7,20 @@ use App\Libs\Config; use App\Libs\Data; use App\Libs\Entity\StateEntity; -use App\Libs\Extends\Request; use App\Libs\Guid; use App\Libs\HttpException; use App\Libs\Mappers\ExportInterface; use App\Libs\Mappers\ImportInterface; use Closure; use DateTimeInterface; -use GuzzleHttp\Exception\GuzzleException; -use GuzzleHttp\Psr7\Uri; -use GuzzleHttp\RequestOptions; use JsonException; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; use RuntimeException; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; use Throwable; class JellyfinServer implements ServerInterface @@ -46,7 +45,7 @@ class JellyfinServer implements ServerInterface 'UserDataSaved', ]; - protected Uri|null $url = null; + protected UriInterface|null $url = null; protected string|null $token = null; protected string|null $user = null; protected array $options = []; @@ -54,12 +53,16 @@ class JellyfinServer implements ServerInterface protected bool $loaded = false; protected bool $isEmby = false; - public function __construct(protected Request $http, protected LoggerInterface $logger) + public function __construct(protected HttpClientInterface $http, protected LoggerInterface $logger) { } - public function setUp(string $name, Uri $url, string|int|null $token = null, array $options = []): ServerInterface - { + public function setUp( + string $name, + UriInterface $url, + string|int|null $token = null, + array $options = [] + ): ServerInterface { return (new self($this->http, $this->logger))->setState($name, $url, $token, $options); } @@ -143,18 +146,23 @@ public static function parseWebhook(ServerRequestInterface $request): StateEntit private function getHeaders(): array { $opts = [ - RequestOptions::HTTP_ERRORS => false, - RequestOptions::TIMEOUT => $this->options['timeout'] ?? 0, - RequestOptions::CONNECT_TIMEOUT => $this->options['connect_timeout'] ?? 0, - RequestOptions::HEADERS => [ + 'headers' => [ 'Accept' => 'application/json', ], ]; + if (null !== ($this->options['timeout'] ?? null)) { + $opts['timeout'] = $this->options['timeout']; + } + + if (null !== ($this->options['max_duration'] ?? null)) { + $opts['max_duration'] = $this->options['max_duration']; + } + if (true === $this->isEmby) { - $opts[RequestOptions::HEADERS]['X-MediaBrowser-Token'] = $this->token; + $opts['headers']['X-MediaBrowser-Token'] = $this->token; } else { - $opts[RequestOptions::HEADERS]['X-Emby-Authorization'] = sprintf( + $opts['headers']['X-Emby-Authorization'] = sprintf( 'MediaBrowser Client="%s", Device="script", DeviceId="", Version="%s", Token="%s"', Config::get('name'), Config::get('version'), @@ -162,12 +170,19 @@ private function getHeaders(): array ); } + if (true === ($this->options['http2'] ?? false)) { + $opts['http_version'] = '2.0'; + } + return $opts; } + /** + * @throws ExceptionInterface + */ protected function getLibraries(Closure $ok, Closure $error): array { - if (!($this->url instanceof Uri)) { + if (!($this->url instanceof UriInterface)) { throw new RuntimeException('No host was set.'); } @@ -196,9 +211,9 @@ protected function getLibraries(Closure $ok, Closure $error): array ) ); - $response = $this->http->request('GET', $url, $this->getHeaders()); + $response = $this->http->request('GET', (string)$url, $this->getHeaders()); - $content = $response->getBody()->getContents(); + $content = $response->getContent(false); $this->logger->debug(sprintf('===[ Sample from %s List library response ]===', $this->name)); $this->logger->debug(!empty($content) ? mb_substr($content, 0, 200) : 'Empty response body'); @@ -226,7 +241,7 @@ protected function getLibraries(Closure $ok, Closure $error): array Data::add($this->name, 'no_import_update', true); return []; } - } catch (GuzzleException $e) { + } catch (ExceptionInterface $e) { $this->logger->error($e->getMessage()); Data::add($this->name, 'no_import_update', true); return []; @@ -285,9 +300,18 @@ protected function getLibraries(Closure $ok, Closure $error): array $this->logger->debug(sprintf('Requesting %s - %s library content.', $this->name, $cName), ['url' => $url]); - $promises[] = $this->http->requestAsync('GET', $url, $this->getHeaders())->then( - $ok($cName, $type, $url), - $error($cName, $type, $url) + $promises[] = $this->http->request( + 'GET', + (string)$url, + array_replace_recursive( + $this->getHeaders(), + [ + 'user_data' => [ + 'ok' => $ok($cName, $type, $url), + 'error' => $error($cName, $type, $url), + ], + ] + ) ); } @@ -308,6 +332,9 @@ protected function getLibraries(Closure $ok, Closure $error): array return $promises; } + /** + * @throws ExceptionInterface + */ public function pull(ImportInterface $mapper, DateTimeInterface|null $after = null): array { return $this->getLibraries( @@ -326,7 +353,7 @@ function (string $cName, string $type) use ($after, $mapper) { } try { - $content = $response->getBody()->getContents(); + $content = $response->getContent(false); $this->logger->debug( sprintf('===[ Sample from %s - %s - response ]===', $this->name, $cName) @@ -352,7 +379,7 @@ function (string $cName, string $type) use ($after, $mapper) { $this->processImport($mapper, $type, $cName, $payload['Items'] ?? [], $after); }; }, - function (string $cName, string $type, Uri|string $url) { + function (string $cName, string $type, UriInterface|string $url) { return fn(Throwable $e) => $this->logger->error( sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()), ['url' => $url] @@ -361,6 +388,9 @@ function (string $cName, string $type, Uri|string $url) { ); } + /** + * @throws ExceptionInterface + */ public function push(ExportInterface $mapper, DateTimeInterface|null $after = null): array { return $this->getLibraries( @@ -379,7 +409,7 @@ function (string $cName, string $type) use ($mapper) { } try { - $content = $response->getBody()->getContents(); + $content = $response->getContent(false); $this->logger->debug( sprintf('===[ Sample from %s - %s - response ]===', $this->name, $cName) @@ -405,7 +435,7 @@ function (string $cName, string $type) use ($mapper) { $this->processExport($mapper, $type, $cName, $payload['Items'] ?? []); }; }, - function (string $cName, string $type, Uri|string $url) { + function (string $cName, string $type, UriInterface|string $url) { return fn(Throwable $e) => $this->logger->error( sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()), ['url' => $url] @@ -498,10 +528,18 @@ protected function processExport(ExportInterface $mapper, string $type, string $ $this->logger->debug(sprintf('(%d/%d) Queuing %s.', $total, $x, $iName), ['url' => $this->url]); $mapper->queue( - new \GuzzleHttp\Psr7\Request( + $this->http->request( 1 === $entity->watched ? 'POST' : 'DELETE', - $this->url->withPath(sprintf('/Users/%s/PlayedItems/%s', $this->user, $item['Id'])), - $this->getHeaders()['headers'] ?? [] + (string)$this->url->withPath(sprintf('/Users/%s/PlayedItems/%s', $this->user, $item['Id'])), + array_replace_recursive( + $this->getHeaders(), + [ + 'user_data' => [ + 'state' => 1 === $entity->watched ? 'Watched' : 'Unwatched', + 'itemName' => $iName, + ], + ] + ) ) ); } catch (Throwable $e) { @@ -648,8 +686,12 @@ protected function hasSupportedIds(array $ids): bool return false; } - public function setState(string $name, Uri $url, string|int|null $token = null, array $opts = []): ServerInterface - { + public function setState( + string $name, + UriInterface $url, + string|int|null $token = null, + array $opts = [] + ): ServerInterface { if (true === $this->loaded) { throw new RuntimeException('setState: already called once'); } diff --git a/src/Libs/Servers/PlexServer.php b/src/Libs/Servers/PlexServer.php index a39c8a35..82b0b988 100644 --- a/src/Libs/Servers/PlexServer.php +++ b/src/Libs/Servers/PlexServer.php @@ -7,21 +7,20 @@ use App\Libs\Config; use App\Libs\Data; use App\Libs\Entity\StateEntity; -use App\Libs\Extends\Request; use App\Libs\Guid; use App\Libs\HttpException; use App\Libs\Mappers\ExportInterface; use App\Libs\Mappers\ImportInterface; use Closure; use DateTimeInterface; -use GuzzleHttp\Exception\GuzzleException; -use GuzzleHttp\Psr7\Uri; -use GuzzleHttp\RequestOptions; use JsonException; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; use RuntimeException; +use Symfony\Contracts\HttpClient\Exception\ExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; use Throwable; class PlexServer implements ServerInterface @@ -46,18 +45,22 @@ class PlexServer implements ServerInterface 'media.scrobble', ]; - protected Uri|null $url = null; + protected UriInterface|null $url = null; protected string|null $token = null; protected array $options = []; protected string $name = ''; protected bool $loaded = false; - public function __construct(protected Request $http, protected LoggerInterface $logger) + public function __construct(protected HttpClientInterface $http, protected LoggerInterface $logger) { } - public function setUp(string $name, Uri $url, string|int|null $token = null, array $options = []): ServerInterface - { + public function setUp( + string $name, + UriInterface $url, + string|int|null $token = null, + array $options = [] + ): ServerInterface { return (new self($this->http, $this->logger))->setState($name, $url, $token, $options); } @@ -143,17 +146,31 @@ public static function parseWebhook(ServerRequestInterface $request): StateEntit private function getHeaders(): array { - return [ - RequestOptions::HTTP_ERRORS => false, - RequestOptions::TIMEOUT => $this->options['timeout'] ?? 0, - RequestOptions::CONNECT_TIMEOUT => $this->options['connect_timeout'] ?? 0, - RequestOptions::HEADERS => [ + $opts = [ + 'headers' => [ 'Accept' => 'application/json', 'X-Plex-Token' => $this->token, ], ]; + + if (null !== ($this->options['timeout'] ?? null)) { + $opts['timeout'] = $this->options['timeout']; + } + + if (null !== ($this->options['max_duration'] ?? null)) { + $opts['max_duration'] = $this->options['max_duration']; + } + + if (true === ($this->options['http2'] ?? false)) { + $opts['http_version'] = '2.0'; + } + + return $opts; } + /** + * @throws ExceptionInterface + */ protected function getLibraries(Closure $ok, Closure $error): array { if (null === $this->url) { @@ -172,9 +189,9 @@ protected function getLibraries(Closure $ok, Closure $error): array $url = $this->url->withPath('/library/sections'); - $response = $this->http->request('GET', $url, $this->getHeaders()); + $response = $this->http->request('GET', (string)$url, $this->getHeaders()); - $content = $response->getBody()->getContents(); + $content = $response->getContent(false); $this->logger->debug(sprintf('===[ Sample from %s List library response ]===', $this->name)); $this->logger->debug(!empty($content) ? mb_substr($content, 0, 200) : 'Empty response body'); @@ -202,7 +219,7 @@ protected function getLibraries(Closure $ok, Closure $error): array Data::add($this->name, 'no_import_update', true); return []; } - } catch (GuzzleException $e) { + } catch (ExceptionInterface $e) { $this->logger->error($e->getMessage()); Data::add($this->name, 'no_import_update', true); return []; @@ -257,9 +274,18 @@ protected function getLibraries(Closure $ok, Closure $error): array $this->logger->debug(sprintf('Requesting %s - %s library content.', $this->name, $cName), ['url' => $url]); - $promises[] = $this->http->requestAsync('GET', $url, $this->getHeaders())->then( - $ok($cName, $type, $url), - $error($cName, $type, $url) + $promises[] = $this->http->request( + 'GET', + (string)$url, + array_replace_recursive( + $this->getHeaders(), + [ + 'user_data' => [ + 'ok' => $ok($cName, $type, $url), + 'error' => $error($cName, $type, $url), + ], + ], + ) ); } @@ -280,6 +306,9 @@ protected function getLibraries(Closure $ok, Closure $error): array return $promises; } + /** + * @throws ExceptionInterface + */ public function pull(ImportInterface $mapper, DateTimeInterface|null $after = null): array { return $this->getLibraries( @@ -298,7 +327,7 @@ function (string $cName, string $type) use ($after, $mapper) { } try { - $content = $response->getBody()->getContents(); + $content = $response->getContent(false); $this->logger->debug( sprintf('===[ Sample from %s - %s - response ]===', $this->name, $cName) @@ -324,7 +353,7 @@ function (string $cName, string $type) use ($after, $mapper) { $this->processImport($mapper, $type, $cName, $payload['MediaContainer']['Metadata'] ?? [], $after); }; }, - function (string $cName, string $type, Uri|string $url) { + function (string $cName, string $type, UriInterface|string $url) { return fn(Throwable $e) => $this->logger->error( sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()), ['url' => $url] @@ -333,6 +362,9 @@ function (string $cName, string $type, Uri|string $url) { ); } + /** + * @throws ExceptionInterface + */ public function push(ExportInterface $mapper, DateTimeInterface|null $after = null): array { return $this->getLibraries( @@ -351,7 +383,7 @@ function (string $cName, string $type) use ($mapper) { } try { - $content = $response->getBody()->getContents(); + $content = $response->getContent(false); $this->logger->debug( sprintf('===[ Sample from %s - %s - response ]===', $this->name, $cName) @@ -377,7 +409,7 @@ function (string $cName, string $type) use ($mapper) { $this->processExport($mapper, $type, $cName, $payload['MediaContainer']['Metadata'] ?? []); }; }, - function (string $cName, string $type, Uri|string $url) { + function (string $cName, string $type, UriInterface|string $url) { return fn(Throwable $e) => $this->logger->error( sprintf('Request to %s - %s - failed. Reason: \'%s\'.', $this->name, $cName, $e->getMessage()), ['url' => $url] @@ -483,7 +515,21 @@ protected function processExport(ExportInterface $mapper, string $type, string $ ) ); - $mapper->queue(new \GuzzleHttp\Psr7\Request('GET', $url, $this->getHeaders()['headers'] ?? [])); + $mapper->queue( + $this->http->request( + 'GET', + (string)$url, + array_replace_recursive( + $this->getHeaders(), + [ + 'user_data' => [ + 'state' => 1 === $entity->watched ? 'Watched' : 'Unwatched', + 'itemName' => $iName, + ], + ] + ) + ) + ); } catch (Throwable $e) { $this->logger->error($e->getMessage()); } @@ -641,8 +687,12 @@ protected function hasSupportedIds(array $guids): bool return false; } - public function setState(string $name, Uri $url, string|int|null $token = null, array $opts = []): ServerInterface - { + public function setState( + string $name, + UriInterface $url, + string|int|null $token = null, + array $opts = [] + ): ServerInterface { if (true === $this->loaded) { throw new RuntimeException('setState: already called once'); } diff --git a/src/Libs/Servers/ServerInterface.php b/src/Libs/Servers/ServerInterface.php index 362e78e1..c2ca73b1 100644 --- a/src/Libs/Servers/ServerInterface.php +++ b/src/Libs/Servers/ServerInterface.php @@ -8,10 +8,10 @@ use App\Libs\Mappers\ExportInterface; use App\Libs\Mappers\ImportInterface; use DateTimeInterface; -use GuzzleHttp\Promise\PromiseInterface; -use GuzzleHttp\Psr7\Uri; use Psr\Http\Message\ServerRequestInterface; +use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; +use Symfony\Contracts\HttpClient\ResponseInterface; interface ServerInterface { @@ -19,13 +19,13 @@ interface ServerInterface * Initiate Server. It should return **NEW OBJECT** * * @param string $name - * @param Uri $url + * @param UriInterface $url * @param null|int|string $token * @param array $options * * @return self */ - public function setUp(string $name, Uri $url, null|string|int $token = null, array $options = []): self; + public function setUp(string $name, UriInterface $url, null|string|int $token = null, array $options = []): self; /** * Inject Logger. @@ -50,7 +50,7 @@ public static function parseWebhook(ServerRequestInterface $request): StateEntit * @param ImportInterface $mapper * @param DateTimeInterface|null $after * - * @return array + * @return array */ public function pull(ImportInterface $mapper, DateTimeInterface|null $after = null): array; @@ -60,7 +60,7 @@ public function pull(ImportInterface $mapper, DateTimeInterface|null $after = nu * @param ExportInterface $mapper * @param DateTimeInterface|null $after * - * @return array + * @return array */ public function push(ExportInterface $mapper, DateTimeInterface|null $after = null): array; } diff --git a/src/Libs/helpers.php b/src/Libs/helpers.php index 590d0ce3..3b3e9c3b 100644 --- a/src/Libs/helpers.php +++ b/src/Libs/helpers.php @@ -4,6 +4,8 @@ use App\Libs\Config; use App\Libs\Extends\Date; +use Nyholm\Psr7\Response; +use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; if (!function_exists('env')) { @@ -233,26 +235,18 @@ function saveWebhookPayload(ServerRequestInterface $request, string $name, array } } -if (!function_exists('array_merge_recursive_distinct')) { - function array_merge_recursive_distinct(array &$array1, array &$array2): array +if (!function_exists('jsonResponse')) { + function jsonResponse(int $status, array $body, $headers = []): ResponseInterface { - $merged = $array1; - foreach ($array2 as $key => &$value) { - if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { - if (is_int($key)) { - $merged[] = array_merge_recursive_distinct($merged[$key], $value); - } else { - $merged[$key] = array_merge_recursive_distinct($merged[$key], $value); - } - } else { - if (is_int($key)) { - $merged[] = $value; - } else { - $merged[$key] = $value; - } - } - } - - return $merged; + $headers['Content-Type'] = 'application/json'; + + return new Response( + status: $status, + headers: $headers, + body: json_encode( + $body, + JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES + ) + ); } }