diff --git a/.env b/.env index 9a8bb987..9bafdaf6 100644 --- a/.env +++ b/.env @@ -73,6 +73,7 @@ GA_TRACKING= ###< google analytics ### ###> storage ### +STORAGE_SOURCE=storage.local PROXY_DIST_DIR=%kernel.project_dir%/var/proxy PACKAGES_DIST_DIR=%kernel.project_dir%/var/repo SECURITY_ADVISORIES_DB_DIR=%kernel.project_dir%/var/security-advisories diff --git a/.env.docker b/.env.docker index f7592966..452180e9 100644 --- a/.env.docker +++ b/.env.docker @@ -77,6 +77,7 @@ GA_TRACKING= ###< google analytics ### ###> storage ### +STORAGE_SOURCE=storage.local PROXY_DIST_DIR=%kernel.project_dir%/var/proxy PACKAGES_DIST_DIR=%kernel.project_dir%/var/repo SECURITY_ADVISORIES_DB_DIR=%kernel.project_dir%/var/security-advisories diff --git a/ansible/roles/cron/tasks/main.yml b/ansible/roles/cron/tasks/main.yml index 61c806aa..a0f1dbfd 100644 --- a/ansible/roles/cron/tasks/main.yml +++ b/ansible/roles/cron/tasks/main.yml @@ -5,6 +5,13 @@ minute="*/5" job="{{ app_root }}/bin/console repman:proxy:sync-releases >>{{ app_root }}/var/log/cron.log 2>&1" +- name: Add sync proxy metadata command to cron + cron: + name="Sync proxy metadata with origins" + user="{{ system_user }}" + minute="*/6" + job="{{ app_root }}/bin/console repman:proxy:sync-metadata >>{{ app_root }}/var/log/cron.log 2>&1" + - name: Add security update-db command to cron cron: name="Update security advisories database" diff --git a/composer.json b/composer.json index 0c346d04..918bce40 100644 --- a/composer.json +++ b/composer.json @@ -23,11 +23,13 @@ "doctrine/orm": "^2.7", "knplabs/github-api": "^2.12", "knpuniversity/oauth2-client-bundle": "^2.0", + "league/flysystem-bundle": "^1.5", "league/oauth2-github": "^2.0", "m4tthumphrey/php-gitlab-api": "^9.17", "munusphp/munus": "^0.2.1", "omines/oauth2-gitlab": "^3.2", "ramsey/uuid-doctrine": "^1.5", + "react/http": "^1.0", "sensio/framework-extra-bundle": "^5.5", "sentry/sentry-symfony": "^3.4", "stevenmaguire/oauth2-bitbucket": "^3.0", @@ -70,6 +72,7 @@ "ekino/phpstan-banned-code": "^0.3.1", "friendsofphp/php-cs-fixer": "^2.16", "fzaninotto/faker": "^1.9", + "league/flysystem-memory": "^1.0", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^0.12.18", "phpstan/phpstan-deprecation-rules": "^0.12.2", diff --git a/composer.lock b/composer.lock index 0da49f33..363c4c01 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": "0f679e9733bd2ec24aef029ff10233e9", + "content-hash": "0504084363ce800615d768122053d5b0", "packages": [ { "name": "bitbucket/client", @@ -2059,6 +2059,59 @@ ], "time": "2020-06-16T20:11:17+00:00" }, + { + "name": "evenement/evenement", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Evenement": "src" + } + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/master" + }, + "time": "2017-07-23T21:35:13+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "6.5.5", @@ -2776,6 +2829,180 @@ ], "time": "2020-05-20T16:45:56+00:00" }, + { + "name": "league/flysystem", + "version": "1.0.69", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "7106f78428a344bc4f643c233a94e48795f10967" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/7106f78428a344bc4f643c233a94e48795f10967", + "reference": "7106f78428a344bc4f643c233a94e48795f10967", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": ">=5.5.9" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7.26" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.0.69" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2020-05-18T15:13:39+00:00" + }, + { + "name": "league/flysystem-bundle", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-bundle.git", + "reference": "d485109f8130d938bf08caf25933a94468becf60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-bundle/zipball/d485109f8130d938bf08caf25933a94468becf60", + "reference": "d485109f8130d938bf08caf25933a94468becf60", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "^1.0.40", + "php": ">=7.1", + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^4.2|^5.0", + "symfony/http-kernel": "^4.2|^5.0", + "symfony/options-resolver": "^4.2|^5.0" + }, + "conflict": { + "league/flysystem-aws-s3-v3": "<1.0.22", + "league/flysystem-cached-adapter": "<1.0.9" + }, + "require-dev": { + "async-aws/flysystem-s3": "^0.3", + "league/flysystem-aws-s3-v3": "^1.0.22", + "league/flysystem-azure-blob-storage": "^0.1.5", + "league/flysystem-cached-adapter": "^1.0.9", + "league/flysystem-memory": "^1.0", + "league/flysystem-rackspace": "^1.0", + "league/flysystem-replicate-adapter": "^1.0", + "league/flysystem-sftp": "^1.0", + "league/flysystem-webdav": "^1.0", + "league/flysystem-ziparchive": "^1.0", + "phpunit/phpunit": "^7.4", + "spatie/flysystem-dropbox": "^1.0", + "superbalist/flysystem-google-storage": "^7.2", + "symfony/dotenv": "^4.2|^5.0", + "symfony/framework-bundle": "^4.2|^5.0", + "symfony/var-dumper": "^4.1|^5.0", + "symfony/yaml": "^4.2|^5.0" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "League\\FlysystemBundle\\": "src" + } + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Titouan Galopin", + "email": "galopintitouan@gmail.com" + } + ], + "description": "Symfony bundle integrating Flysystem into Symfony 4.2+ applications", + "support": { + "issues": "https://github.com/thephpleague/flysystem-bundle/issues", + "source": "https://github.com/thephpleague/flysystem-bundle/tree/1.5.0" + }, + "time": "2020-04-04T22:09:59+00:00" + }, { "name": "league/oauth2-client", "version": "2.4.1", @@ -4248,17 +4475,624 @@ "time": "2016-08-06T14:39:51+00:00" }, { - "name": "psr/log", - "version": "1.1.3", + "name": "psr/log", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2020-03-23T09:12:05+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": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "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.", + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca", + "reference": "925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", + "fzaninotto/faker": "^1.5", + "jakub-onderka/php-parallel-lint": "^1", + "jangregor/phpstan-prophecy": "^0.6", + "mockery/mockery": "^1.3", + "phpstan/extension-installer": "^1", + "phpstan/phpdoc-parser": "0.4.1", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", + "slevomat/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP 7.2+ library for representing and manipulating collections.", + "homepage": "https://github.com/ramsey/collection", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "time": "2020-01-05T00:22:59+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d", + "reference": "ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "brick/math": "^0.8", + "ext-json": "*", + "php": "^7.2 || ^8", + "ramsey/collection": "^1.0", + "symfony/polyfill-ctype": "^1.8" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "codeception/aspect-mock": "^3", + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2", + "doctrine/annotations": "^1.8", + "goaop/framework": "^2", + "mockery/mockery": "^1.3", + "moontoast/math": "^1.1", + "paragonie/random-lib": "^2", + "php-mock/php-mock-mockery": "^1.3", + "php-mock/php-mock-phpunit": "^2.5", + "php-parallel-lint/php-parallel-lint": "^1.1", + "phpstan/extension-installer": "^1.0", + "phpstan/phpdoc-parser": "0.4.3", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-mockery": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^8.5", + "psy/psysh": "^0.10.0", + "slevomat/coding-standard": "^6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "3.9.4" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-ctype": "Enables faster processing of character classification using ctype functions.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "homepage": "https://github.com/ramsey/uuid", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "time": "2020-03-29T20:13:32+00:00" + }, + { + "name": "ramsey/uuid-doctrine", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid-doctrine.git", + "reference": "9facc4689547e72e03c1e18df4a0ee162b2778b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid-doctrine/zipball/9facc4689547e72e03c1e18df4a0ee162b2778b0", + "reference": "9facc4689547e72e03c1e18df4a0ee162b2778b0", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "doctrine/orm": "^2.5", + "php": "^5.4 | ^7 | ^8", + "ramsey/uuid": "^3.5 | ^4" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "^1", + "mockery/mockery": "^0.9 | ^1", + "phpunit/phpunit": "^4.8.36 | ^5.7 | ^6.5 | ^7", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Ramsey\\Uuid\\Doctrine\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Allow the use of ramsey/uuid as a Doctrine field type.", + "homepage": "https://github.com/ramsey/uuid-doctrine", + "keywords": [ + "database", + "doctrine", + "guid", + "identifier", + "uuid" + ], + "time": "2020-01-27T05:09:17+00:00" + }, + { + "name": "react/cache", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/aa10d63a1b40a36a486bdf527f28bac607ee6466", + "reference": "aa10d63a1b40a36a486bdf527f28bac607ee6466", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0", + "react/promise": "~2.0|~1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.0.0" + }, + "time": "2019-07-11T13:45:28+00:00" + }, + { + "name": "react/dns", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "89d83794e959ef3e0f1ab792f070b0157de1abf2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/89d83794e959ef3e0f1ab792f070b0157de1abf2", + "reference": "89d83794e959ef3e0f1ab792f070b0157de1abf2", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.0 || ^0.5", + "react/promise": "^3.0 || ^2.7 || ^1.2.1", + "react/promise-timer": "^1.2" + }, + "require-dev": { + "clue/block-react": "^1.2", + "phpunit/phpunit": "^9.0 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src" + } + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.3.0" + }, + "time": "2020-07-10T12:12:50+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "6d24de090cd59cfc830263cfba965be77b563c13" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/6d24de090cd59cfc830263cfba965be77b563c13", + "reference": "6d24de090cd59cfc830263cfba965be77b563c13", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" + }, + "suggest": { + "ext-event": "~1.0 for ExtEventLoop", + "ext-pcntl": "For signal handling support when using the StreamSelectLoop", + "ext-uv": "* for ExtUvLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src" + } + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.1.1" + }, + "time": "2020-01-01T18:39:52+00:00" + }, + { + "name": "react/http", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/http.git", + "reference": "865694453c95122f8972b9ed7961efb3c517fc5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/http/zipball/865694453c95122f8972b9ed7961efb3c517fc5e", + "reference": "865694453c95122f8972b9ed7961efb3c517fc5e", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "psr/http-message": "^1.0", + "react/event-loop": "^1.0 || ^0.5", + "react/promise": "^2.3 || ^1.2.1", + "react/promise-stream": "^1.1", + "react/socket": "^1.1", + "react/stream": "^1.0 || ^0.7.5", + "ringcentral/psr7": "^1.2" + }, + "require-dev": { + "clue/block-react": "^1.1", + "clue/http-proxy-react": "^1.3", + "clue/reactphp-ssh-proxy": "^1.0", + "clue/socks-react": "^1.0", + "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Http\\": "src" + } + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven, streaming HTTP client and server implementation for ReactPHP", + "keywords": [ + "async", + "client", + "event-driven", + "http", + "http client", + "http server", + "https", + "psr-7", + "reactphp", + "server", + "streaming" + ], + "support": { + "issues": "https://github.com/reactphp/http/issues", + "source": "https://github.com/reactphp/http/tree/v1.0.0" + }, + "time": "2020-07-11T13:29:43+00:00" + }, + { + "name": "react/promise", + "version": "v2.8.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/f3cff96a19736714524ca0dd1d4130de73dbbbc4", + "reference": "f3cff96a19736714524ca0dd1d4130de73dbbbc4", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^6.5 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v2.8.0" + }, + "time": "2020-05-12T15:16:56+00:00" + }, + { + "name": "react/promise-stream", + "version": "v1.2.0", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "url": "https://github.com/reactphp/promise-stream.git", + "reference": "6384d8b76cf7dcc44b0bf3343fb2b2928412d1fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/reactphp/promise-stream/zipball/6384d8b76cf7dcc44b0bf3343fb2b2928412d1fe", + "reference": "6384d8b76cf7dcc44b0bf3343fb2b2928412d1fe", "shasum": "", "mirrors": [ { @@ -4268,50 +5102,63 @@ ] }, "require": { - "php": ">=5.3.0" + "php": ">=5.3", + "react/promise": "^2.1 || ^1.2", + "react/stream": "^1.0 || ^0.7 || ^0.6 || ^0.5 || ^0.4.6" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } + "require-dev": { + "clue/block-react": "^1.0", + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3", + "react/promise-timer": "^1.0" }, + "type": "library", "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" - } + "React\\Promise\\Stream\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "https://repo.repman.io/downloads", "license": [ "MIT" ], "authors": [ { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "name": "Christian Lück", + "email": "christian@lueck.tv" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", + "description": "The missing link between Promise-land and Stream-land for ReactPHP", + "homepage": "https://github.com/reactphp/promise-stream", "keywords": [ - "log", - "psr", - "psr-3" + "Buffer", + "async", + "promise", + "reactphp", + "stream", + "unwrap" ], - "time": "2020-03-23T09:12:05+00:00" + "support": { + "issues": "https://github.com/reactphp/promise-stream/issues", + "source": "https://github.com/reactphp/promise-stream/tree/v1.2.0" + }, + "time": "2019-07-03T12:29:10+00:00" }, { - "name": "ralouphie/getallheaders", - "version": "3.0.3", + "name": "react/promise-timer", + "version": "v1.6.0", "source": { "type": "git", - "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "120b605dfeb996808c31b6477290a714d356e822" + "url": "https://github.com/reactphp/promise-timer.git", + "reference": "daee9baf6ef30c43ea4c86399f828bb5f558f6e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", - "reference": "120b605dfeb996808c31b6477290a714d356e822", + "url": "https://api.github.com/repos/reactphp/promise-timer/zipball/daee9baf6ef30c43ea4c86399f828bb5f558f6e6", + "reference": "daee9baf6ef30c43ea4c86399f828bb5f558f6e6", "shasum": "", "mirrors": [ { @@ -4321,43 +5168,60 @@ ] }, "require": { - "php": ">=5.6" + "php": ">=5.3", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5", + "react/promise": "^3.0 || ^2.7.0 || ^1.2.1" }, "require-dev": { - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5 || ^6.5" + "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35" }, "type": "library", "autoload": { + "psr-4": { + "React\\Promise\\Timer\\": "src/" + }, "files": [ - "src/getallheaders.php" + "src/functions_include.php" ] }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "https://repo.repman.io/downloads", "license": [ "MIT" ], "authors": [ { - "name": "Ralph Khattar", - "email": "ralph.khattar@gmail.com" + "name": "Christian Lück", + "email": "christian@lueck.tv" } ], - "description": "A polyfill for getallheaders.", - "time": "2019-03-08T08:55:37+00:00" + "description": "A trivial implementation of timeouts for Promises, built on top of ReactPHP.", + "homepage": "https://github.com/reactphp/promise-timer", + "keywords": [ + "async", + "event-loop", + "promise", + "reactphp", + "timeout", + "timer" + ], + "support": { + "issues": "https://github.com/reactphp/promise-timer/issues", + "source": "https://github.com/reactphp/promise-timer/tree/v1.6.0" + }, + "time": "2020-07-10T12:18:06+00:00" }, { - "name": "ramsey/collection", - "version": "1.0.1", + "name": "react/socket", + "version": "v1.5.0", "source": { "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca" + "url": "https://github.com/reactphp/socket.git", + "reference": "842dcd71df86671ee9491734035b3d2cf4a80ece" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca", - "reference": "925ad8cf55ba7a3fc92e332c58fd0478ace3e1ca", + "url": "https://api.github.com/repos/reactphp/socket/zipball/842dcd71df86671ee9491734035b3d2cf4a80ece", + "reference": "842dcd71df86671ee9491734035b3d2cf4a80ece", "shasum": "", "mirrors": [ { @@ -4367,64 +5231,55 @@ ] }, "require": { - "php": "^7.2" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.1", + "react/event-loop": "^1.0 || ^0.5", + "react/promise": "^2.6.0 || ^1.2.1", + "react/promise-timer": "^1.4.0", + "react/stream": "^1.1" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.5.0", - "fzaninotto/faker": "^1.5", - "jakub-onderka/php-parallel-lint": "^1", - "jangregor/phpstan-prophecy": "^0.6", - "mockery/mockery": "^1.3", - "phpstan/extension-installer": "^1", - "phpstan/phpdoc-parser": "0.4.1", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5", - "slevomat/coding-standard": "^6.0", - "squizlabs/php_codesniffer": "^3.5" + "clue/block-react": "^1.2", + "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35", + "react/promise-stream": "^1.2" }, "type": "library", "autoload": { "psr-4": { - "Ramsey\\Collection\\": "src/" + "React\\Socket\\": "src" } }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "https://repo.repman.io/downloads", "license": [ "MIT" ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A PHP 7.2+ library for representing and manipulating collections.", - "homepage": "https://github.com/ramsey/collection", + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" + "Connection", + "Socket", + "async", + "reactphp", + "stream" ], - "time": "2020-01-05T00:22:59+00:00" + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.5.0" + }, + "time": "2020-07-01T12:50:00+00:00" }, { - "name": "ramsey/uuid", - "version": "4.0.1", + "name": "react/stream", + "version": "v1.1.1", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d" + "url": "https://github.com/reactphp/stream.git", + "reference": "7c02b510ee3f582c810aeccd3a197b9c2f52ff1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d", - "reference": "ba8fff1d3abb8bb4d35a135ed22a31c6ef3ede3d", + "url": "https://api.github.com/repos/reactphp/stream/zipball/7c02b510ee3f582c810aeccd3a197b9c2f52ff1a", + "reference": "7c02b510ee3f582c810aeccd3a197b9c2f52ff1a", "shasum": "", "mirrors": [ { @@ -4434,84 +5289,53 @@ ] }, "require": { - "brick/math": "^0.8", - "ext-json": "*", - "php": "^7.2 || ^8", - "ramsey/collection": "^1.0", - "symfony/polyfill-ctype": "^1.8" - }, - "replace": { - "rhumsaa/uuid": "self.version" + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.0 || ^0.5 || ^0.4 || ^0.3.5" }, "require-dev": { - "codeception/aspect-mock": "^3", - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2", - "doctrine/annotations": "^1.8", - "goaop/framework": "^2", - "mockery/mockery": "^1.3", - "moontoast/math": "^1.1", - "paragonie/random-lib": "^2", - "php-mock/php-mock-mockery": "^1.3", - "php-mock/php-mock-phpunit": "^2.5", - "php-parallel-lint/php-parallel-lint": "^1.1", - "phpstan/extension-installer": "^1.0", - "phpstan/phpdoc-parser": "0.4.3", - "phpstan/phpstan": "^0.12", - "phpstan/phpstan-mockery": "^0.12", - "phpstan/phpstan-phpunit": "^0.12", - "phpunit/phpunit": "^8.5", - "psy/psysh": "^0.10.0", - "slevomat/coding-standard": "^6.0", - "squizlabs/php_codesniffer": "^3.5", - "vimeo/psalm": "3.9.4" - }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-ctype": "Enables faster processing of character classification using ctype functions.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.x-dev" - } - }, "autoload": { "psr-4": { - "Ramsey\\Uuid\\": "src/" - }, - "files": [ - "src/functions.php" - ] + "React\\Stream\\": "src" + } }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "https://repo.repman.io/downloads", "license": [ "MIT" ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", - "homepage": "https://github.com/ramsey/uuid", + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", "keywords": [ - "guid", - "identifier", - "uuid" + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" ], - "time": "2020-03-29T20:13:32+00:00" + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.1.1" + }, + "time": "2020-05-04T10:17:57+00:00" }, { - "name": "ramsey/uuid-doctrine", - "version": "1.6.0", + "name": "ringcentral/psr7", + "version": "1.3.0", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid-doctrine.git", - "reference": "9facc4689547e72e03c1e18df4a0ee162b2778b0" + "url": "https://github.com/ringcentral/psr7.git", + "reference": "360faaec4b563958b673fb52bbe94e37f14bc686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid-doctrine/zipball/9facc4689547e72e03c1e18df4a0ee162b2778b0", - "reference": "9facc4689547e72e03c1e18df4a0ee162b2778b0", + "url": "https://api.github.com/repos/ringcentral/psr7/zipball/360faaec4b563958b673fb52bbe94e37f14bc686", + "reference": "360faaec4b563958b673fb52bbe94e37f14bc686", "shasum": "", "mirrors": [ { @@ -4521,36 +5345,51 @@ ] }, "require": { - "doctrine/orm": "^2.5", - "php": "^5.4 | ^7 | ^8", - "ramsey/uuid": "^3.5 | ^4" + "php": ">=5.3", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" }, "require-dev": { - "jakub-onderka/php-parallel-lint": "^1", - "mockery/mockery": "^0.9 | ^1", - "phpunit/phpunit": "^4.8.36 | ^5.7 | ^6.5 | ^7", - "squizlabs/php_codesniffer": "^3.5" + "phpunit/phpunit": "~4.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, "autoload": { "psr-4": { - "Ramsey\\Uuid\\Doctrine\\": "src/" - } + "RingCentral\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] }, - "notification-url": "https://packagist.org/downloads/", + "notification-url": "https://repo.repman.io/downloads", "license": [ "MIT" ], - "description": "Allow the use of ramsey/uuid as a Doctrine field type.", - "homepage": "https://github.com/ramsey/uuid-doctrine", + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "PSR-7 message implementation", "keywords": [ - "database", - "doctrine", - "guid", - "identifier", - "uuid" + "http", + "message", + "stream", + "uri" ], - "time": "2020-01-27T05:09:17+00:00" + "support": { + "source": "https://github.com/ringcentral/psr7/tree/master" + }, + "time": "2018-05-29T20:21:04+00:00" }, { "name": "seld/jsonlint", @@ -9647,6 +10486,67 @@ ], "time": "2019-12-12T13:22:17+00:00" }, + { + "name": "league/flysystem-memory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-memory.git", + "reference": "d0e87477c32e29f999b4de05e64c1adcddb51757" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-memory/zipball/d0e87477c32e29f999b4de05e64c1adcddb51757", + "reference": "d0e87477c32e29f999b4de05e64c1adcddb51757", + "shasum": "", + "mirrors": [ + { + "url": "https://repo.repman.io/dists/%package%/%version%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.10" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\Memory\\": "src/" + } + }, + "notification-url": "https://repo.repman.io/downloads", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Leppanen", + "email": "chris.leppanen@gmail.com", + "role": "Developer" + } + ], + "description": "An in-memory adapter for Flysystem.", + "homepage": "https://github.com/thephpleague/flysystem-memory", + "keywords": [ + "Flysystem", + "adapter", + "memory" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-memory/issues", + "source": "https://github.com/thephpleague/flysystem-memory/tree/1.0.2" + }, + "time": "2019-05-30T21:34:13+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.9.5", @@ -12206,5 +13106,6 @@ "ext-pdo_pgsql": "*", "ext-zip": "*" }, - "platform-dev": [] + "platform-dev": [], + "plugin-api-version": "2.0.0" } diff --git a/config/bundles.php b/config/bundles.php index a766ec96..f2e911c2 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -15,4 +15,5 @@ DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], KnpU\OAuth2ClientBundle\KnpUOAuth2ClientBundle::class => ['all' => true], + League\FlysystemBundle\FlysystemBundle::class => ['all' => true], ]; diff --git a/config/packages/flysystem.yaml b/config/packages/flysystem.yaml new file mode 100644 index 00000000..ada549b2 --- /dev/null +++ b/config/packages/flysystem.yaml @@ -0,0 +1,12 @@ +# Read the documentation at https://github.com/thephpleague/flysystem-bundle/blob/master/docs/1-getting-started.md +flysystem: + storages: + storage.local.proxy: + adapter: 'local' + options: + directory: '%dists_dir%' + + proxy.storage: + adapter: 'lazy' + options: + source: '%env(STORAGE_SOURCE)%.proxy' diff --git a/config/services.yaml b/config/services.yaml index e09434af..68e8799c 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -25,6 +25,7 @@ services: $distsDir: '%dists_dir%' $resetPasswordTokenTtl: 86400 # 24h Symfony\Component\HttpFoundation\Session\Session $session: '@session' + $proxyFilesystem: '@proxy.storage' # makes classes in src/ available to be used as services # this creates a service per class whose id is the fully-qualified class name @@ -49,6 +50,13 @@ services: Buddy\Repman\Service\Dist\Storage: class: Buddy\Repman\Service\Dist\Storage\FileStorage + proxy.storage.public: + public: true + alias: 'proxy.storage' + + Buddy\Repman\Service\Proxy\ProxyRegister: + public: true + repman.organization.dist_storage: class: Buddy\Repman\Service\Dist\Storage\FileStorage arguments: diff --git a/docker/crontabs/root b/docker/crontabs/root index 6e2c5f8b..2ef00a4a 100644 --- a/docker/crontabs/root +++ b/docker/crontabs/root @@ -1,2 +1,3 @@ */5 * * * * /app/bin/console repman:proxy:sync-releases +*/6 * * * * /app/bin/console repman:proxy:sync-metadata 0 */2 * * * /app/bin/console repman:security:update-db diff --git a/phpstan.neon b/phpstan.neon index 24d986dc..22292553 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -25,7 +25,7 @@ parameters: - message: "#^Variable \\$http_response_header in isset\\(\\) always exists and is not nullable\\.$#" count: 1 - path: src/Service/Downloader/NativeDownloader.php + path: src/Service/Downloader/ReactDownloader.php bootstrapFiles: - vendor/twig/twig/src/Extension/CoreExtension.php # twig global functions diff --git a/src/Command/ProxySyncMetadataCommand.php b/src/Command/ProxySyncMetadataCommand.php new file mode 100644 index 00000000..92794ba7 --- /dev/null +++ b/src/Command/ProxySyncMetadataCommand.php @@ -0,0 +1,62 @@ +register = $register; + $this->downloader = $downloader; + $this->lockFactory = $lockFactory; + + parent::__construct(); + } + + /** + * @return void + */ + protected function configure() + { + $this + ->setName('repman:proxy:sync-metadata') + ->setDescription('Sync proxy metadata with origins') + ; + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $lock = $this->lockFactory->createLock(self::LOCK_NAME, self::LOCK_TTL); + if (!$lock->acquire()) { + return 0; + } + + try { + $this->register->all()->forEach(function (Proxy $proxy) use ($lock): void { + $proxy->syncMetadata(); + $lock->refresh(); + }); + } finally { + $lock->release(); + } + + return 0; + } +} diff --git a/src/Command/ProxySyncReleasesCommand.php b/src/Command/ProxySyncReleasesCommand.php index b5b17b6a..f0a26b0e 100644 --- a/src/Command/ProxySyncReleasesCommand.php +++ b/src/Command/ProxySyncReleasesCommand.php @@ -6,6 +6,7 @@ use Buddy\Repman\Service\Downloader; use Buddy\Repman\Service\Proxy\ProxyRegister; +use Buddy\Repman\Service\Stream; use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; @@ -80,7 +81,7 @@ private function syncPackages(\SimpleXMLElement $feed): void list($name, $version) = explode(' ', (string) $item->guid); if (isset($syncedPackages[$name])) { $this->lock->refresh(); - $proxy->downloadByVersion($name, $version); + $proxy->download($name, $version); } } } @@ -102,12 +103,12 @@ private function alreadySynced(string $pubDate): bool private function loadFeed(): \SimpleXMLElement { - $string = $this + $stream = $this ->downloader ->getContents('https://packagist.org/feeds/releases.rss') - ->getOrElse(''); + ->getOrElse(Stream::fromString('')); - $xml = @simplexml_load_string($string); + $xml = @simplexml_load_string((string) stream_get_contents($stream)); if ($xml === false) { throw new \RunTimeException('Unable to parse RSS feed'); } diff --git a/src/Controller/Admin/ProxyController.php b/src/Controller/Admin/ProxyController.php index b6ec71d8..4cbce81b 100644 --- a/src/Controller/Admin/ProxyController.php +++ b/src/Controller/Admin/ProxyController.php @@ -60,7 +60,7 @@ public function stats(Request $request): Response */ public function remove(string $proxy, string $packageName): Response { - $this->dispatchMessage(new RemoveDist($packageName)); + $this->dispatchMessage(new RemoveDist($proxy, $packageName)); $this->addFlash('success', sprintf('Dist files for package %s will be removed.', $packageName)); diff --git a/src/Controller/OrganizationController.php b/src/Controller/OrganizationController.php index 1b5d9dde..8b49fc4d 100644 --- a/src/Controller/OrganizationController.php +++ b/src/Controller/OrganizationController.php @@ -342,14 +342,6 @@ public function packageScanResults(Organization $organization, Package $package, ]); } - protected function getUser(): User - { - /** @var User $user */ - $user = parent::getUser(); - - return $user; - } - private function tryToRemoveWebhook(Package $package): void { if ($package->webhookCreatedAt() !== null) { diff --git a/src/Controller/ProxyController.php b/src/Controller/ProxyController.php index 1ad9b135..6774bb6f 100644 --- a/src/Controller/ProxyController.php +++ b/src/Controller/ProxyController.php @@ -7,12 +7,15 @@ use Buddy\Repman\Message\Proxy\AddDownloads; use Buddy\Repman\Message\Proxy\AddDownloads\Package; use Buddy\Repman\Service\Proxy; +use Buddy\Repman\Service\Proxy\Metadata; use Buddy\Repman\Service\Proxy\ProxyRegister; +use Buddy\Repman\Service\Symfony\ResponseCallback; use Munus\Control\Option; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\RouterInterface; @@ -47,34 +50,59 @@ public function packages(): JsonResponse } /** - * @Route("/p/{package}", name="package_provider", requirements={"package"="%package_name_pattern%"}, methods={"GET"}) + * @Route("/p/{package}", + * name="package_legacy_metadata", + * host="repo.{domain}", + * defaults={"domain"="%domain%"}, + * requirements={"package"="%package_name_pattern%","domain"="%domain%"}, + * methods={"GET"}) */ - public function provider(string $package): JsonResponse + public function legacyMetadata(string $package): Response { - return new JsonResponse($this->register->all() - ->map(fn (Proxy $proxy) => $proxy->providerData($package)) + /** @var Metadata $metadata */ + $metadata = $this->register->all() + ->map(fn (Proxy $proxy) => $proxy->legacyMetadata($package)) ->find(fn (Option $option) => !$option->isEmpty()) ->map(fn (Option $option) => $option->get()) - ->getOrElse(['packages' => new \stdClass()]) - ); + ->getOrElse(Metadata::fromString('{"packages": {}}')); + + return (new StreamedResponse(ResponseCallback::fromStream($metadata->stream()), 200, [ + 'Accept-Ranges' => 'bytes', + 'Content-Type' => 'application/json', + /* @phpstan-ignore-next-line */ + 'Content-Length' => fstat($metadata->stream())['size'], + ])) + ->setPublic() + ->setLastModified((new \DateTime())->setTimestamp($metadata->timestamp())) + ; } /** * @Route("/p2/{package}.json", - * name="package_provider_v2", + * name="package_metadata", * host="repo.{domain}", * defaults={"domain"="%domain%"}, * requirements={"package"="%package_name_pattern%","domain"="%domain%"}, * methods={"GET"}) */ - public function providerV2(string $package): JsonResponse + public function metadata(string $package): Response { - return new JsonResponse($this->register->all() - ->map(fn (Proxy $proxy) => $proxy->providerDataV2($package)) + /** @var Metadata $metadata */ + $metadata = $this->register->all() + ->map(fn (Proxy $proxy) => $proxy->metadata($package)) ->find(fn (Option $option) => !$option->isEmpty()) ->map(fn (Option $option) => $option->get()) - ->getOrElse(['packages' => new \stdClass()]) - ); + ->getOrElseThrow(new NotFoundHttpException()); + + return (new StreamedResponse(ResponseCallback::fromStream($metadata->stream()), 200, [ + 'Accept-Ranges' => 'bytes', + 'Content-Type' => 'application/json', + /* @phpstan-ignore-next-line */ + 'Content-Length' => fstat($metadata->stream())['size'], + ])) + ->setPublic() + ->setLastModified((new \DateTime())->setTimestamp($metadata->timestamp())) + ; } /** @@ -85,14 +113,24 @@ public function providerV2(string $package): JsonResponse * requirements={"package"="%package_name_pattern%","ref"="[a-f0-9]*?","type"="zip|tar","domain"="%domain%"}, * methods={"GET"}) */ - public function distribution(string $package, string $version, string $ref, string $type): BinaryFileResponse + public function distribution(string $package, string $version, string $ref, string $type): Response { - return new BinaryFileResponse($this->register->all() - ->map(fn (Proxy $proxy) => $proxy->distFilename($package, $version, $ref, $type)) + /** @var resource $stream */ + $stream = $this->register->all() + ->map(fn (Proxy $proxy) => $proxy->distribution($package, $version, $ref, $type)) ->find(fn (Option $option) => !$option->isEmpty()) ->map(fn (Option $option) => $option->get()) - ->getOrElseThrow(new NotFoundHttpException('This distribution file can not be found or downloaded from origin url.')) - ); + ->getOrElseThrow(new NotFoundHttpException('This distribution file can not be found or downloaded from origin url.')); + + return (new StreamedResponse(ResponseCallback::fromStream($stream), 200, [ + 'Accept-Ranges' => 'bytes', + 'Content-Type' => 'application/zip', + /* @phpstan-ignore-next-line */ + 'Content-Length' => fstat($stream)['size'], + ])) + ->setPublic() + ->setEtag($ref) + ; } /** diff --git a/src/Message/Proxy/RemoveDist.php b/src/Message/Proxy/RemoveDist.php index c62fafa6..fdefc5b6 100644 --- a/src/Message/Proxy/RemoveDist.php +++ b/src/Message/Proxy/RemoveDist.php @@ -6,13 +6,20 @@ final class RemoveDist { + private string $proxy; private string $packageName; - public function __construct(string $packageName) + public function __construct(string $proxy, string $packageName) { + $this->proxy = $proxy; $this->packageName = $packageName; } + public function proxy(): string + { + return $this->proxy; + } + public function packageName(): string { return $this->packageName; diff --git a/src/MessageHandler/Proxy/RemoveDistHandler.php b/src/MessageHandler/Proxy/RemoveDistHandler.php index ffa29e08..cad9b705 100644 --- a/src/MessageHandler/Proxy/RemoveDistHandler.php +++ b/src/MessageHandler/Proxy/RemoveDistHandler.php @@ -5,20 +5,20 @@ namespace Buddy\Repman\MessageHandler\Proxy; use Buddy\Repman\Message\Proxy\RemoveDist; -use Buddy\Repman\Service\Dist\Storage; +use Buddy\Repman\Service\Proxy\ProxyRegister; use Symfony\Component\Messenger\Handler\MessageHandlerInterface; final class RemoveDistHandler implements MessageHandlerInterface { - private Storage $distStorage; + private ProxyRegister $register; - public function __construct(Storage $distStorage) + public function __construct(ProxyRegister $register) { - $this->distStorage = $distStorage; + $this->register = $register; } public function __invoke(RemoveDist $message): void { - $this->distStorage->remove($message->packageName()); + $this->register->getByHost($message->proxy())->removeDist($message->packageName()); } } diff --git a/src/Migrations/Version20200720112146.php b/src/Migrations/Version20200720112146.php new file mode 100644 index 00000000..cdb97ba4 --- /dev/null +++ b/src/Migrations/Version20200720112146.php @@ -0,0 +1,77 @@ +container = $container; + } + + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + $filesystem = $this->container->get('proxy.storage.public'); + $register = $this->container->get(ProxyRegister::class); + + $register->all()->forEach(function (Proxy $proxy) use ($filesystem): void { + foreach ($filesystem->listContents(sprintf('%s', (string) parse_url($proxy->url(), PHP_URL_HOST)), true) as $file) { + if ($file['type'] !== 'file') { + continue; + } + + // remove old metadata + if ($file['extension'] === 'json') { + $filesystem->delete($file['path']); + } + + if (strpos($file['basename'], '_') === false) { + continue; + } + + // rename old dist files to new format + $newName = $file['dirname'].DIRECTORY_SEPARATOR.substr($file['basename'], strpos($file['basename'], '_') + 1); + if ($filesystem->has($newName)) { + continue; + } + + try { + $filesystem->rename($file['path'], $newName); + } catch (Exception $exception) { + $this->write(sprintf('Error when renaming %s: %s', $file['path'], $exception->getMessage())); + } + } + }); + } + + public function down(Schema $schema): void + { + // nothing to do here + } + + public function isTransactional(): bool + { + return false; + } +} diff --git a/src/Service/Cache.php b/src/Service/Cache.php deleted file mode 100644 index 041422ae..00000000 --- a/src/Service/Cache.php +++ /dev/null @@ -1,24 +0,0 @@ -> - */ - public function get(string $path, callable $supplier, int $expireTime = 0): Option; - - /** - * @return Option> - */ - public function find(string $path, int $expireTime = 0): Option; - - public function exists(string $path, int $expireTime = 0): bool; - - public function removeOld(string $path): void; -} diff --git a/src/Service/Cache/FileCache.php b/src/Service/Cache/FileCache.php deleted file mode 100644 index 2e82eaf8..00000000 --- a/src/Service/Cache/FileCache.php +++ /dev/null @@ -1,106 +0,0 @@ -basePath = $basePath; - $this->exceptionHandler = $exceptionHandler; - } - - public function get(string $path, callable $supplier, int $expireTime = 0): Option - { - $filename = $this->getPath($path); - if (is_readable($filename) && ($expireTime === 0 || filemtime($filename) > time() - $expireTime)) { - return Option::some(unserialize((string) file_get_contents($filename))); - } - - $this->ensureDirExist($filename); - - return TryTo::run($supplier) - ->onSuccess(function ($value) use ($filename): void {AtomicFile::write($filename, serialize($value)); }) - ->onFailure(function (\Throwable $throwable): void {$this->exceptionHandler->handle($throwable); }) - ->map(fn ($value) => Option::some($value)) - ->getOrElse(Option::none()); - } - - public function removeOld(string $path): void - { - $dir = $this->getPath(dirname($path)); - if (false === ($length = strpos(basename($path), '$')) || !is_dir($dir)) { - return; - } - - $pattern = substr(basename($path), 0, $length).'$*'; - $files = Finder::create()->files()->ignoreVCS(true)->name($pattern)->in($dir); - - foreach ($files as $file) { - /* @var SplFileInfo $file */ - @unlink($file->getPathname()); - } - } - - public function find(string $path, int $expireTime = 0): Option - { - $dir = $this->getPath(dirname($path)); - if (!is_dir($dir)) { - return Option::none(); - } - - $pattern = basename($path).'$*'; - /** @var SplFileInfo[] $files */ - $files = iterator_to_array(Finder::create()->files()->ignoreVCS(true)->ignoreUnreadableDirs(true)->name($pattern)->in($dir)->getIterator()); - if ($files === []) { - return Option::none(); - } - - $filename = current($files)->getPathname(); - if ($expireTime !== 0 && filemtime($filename) <= time() - $expireTime) { - return Option::none(); - } - - return Option::some(unserialize((string) file_get_contents($filename))); - } - - public function exists(string $path, int $expireTime = 0): bool - { - $filename = $this->getPath($path); - - return is_readable($filename) && ($expireTime === 0 || filemtime($filename) > time() - $expireTime); - } - - private function getPath(string $path): string - { - return sprintf('%s/%s', $this->basePath, $path); - } - - private function ensureDirExist(string $filename): void - { - $dirname = dirname($filename); - if (!is_dir($dirname)) { - mkdir($dirname, 0777, true); - } - } -} diff --git a/src/Service/Cache/InMemoryCache.php b/src/Service/Cache/InMemoryCache.php deleted file mode 100644 index 43181a76..00000000 --- a/src/Service/Cache/InMemoryCache.php +++ /dev/null @@ -1,40 +0,0 @@ - - */ - private array $cache; - - public function get(string $path, callable $supplier, int $expireTime = 0): Option - { - if (!isset($this->cache[$path])) { - $this->cache[$path] = $supplier(); - } - - return Option::some($this->cache[$path]); - } - - public function removeOld(string $path): void - { - // TODO: Implement remove() method. - } - - public function find(string $path, int $expireTime = 0): Option - { - return isset($this->cache[$path]) ? Option::some($this->cache[$path]) : Option::none(); - } - - public function exists(string $path, int $expireTime = 0): bool - { - return isset($this->cache[$path]); - } -} diff --git a/src/Service/Dist/Storage.php b/src/Service/Dist/Storage.php index 9f262d29..869cc6d4 100644 --- a/src/Service/Dist/Storage.php +++ b/src/Service/Dist/Storage.php @@ -5,7 +5,6 @@ namespace Buddy\Repman\Service\Dist; use Buddy\Repman\Service\Dist; -use Munus\Collection\GenericList; interface Storage { @@ -19,11 +18,4 @@ public function download(string $url, Dist $dist, array $headers = []): void; public function filename(Dist $dist): string; public function size(Dist $dist): int; - - /** - * @return GenericList - */ - public function packages(string $repo): GenericList; - - public function remove(string $packageName): void; } diff --git a/src/Service/Dist/Storage/FileStorage.php b/src/Service/Dist/Storage/FileStorage.php index e53d4c2b..21f45f86 100644 --- a/src/Service/Dist/Storage/FileStorage.php +++ b/src/Service/Dist/Storage/FileStorage.php @@ -8,9 +8,6 @@ use Buddy\Repman\Service\Dist; use Buddy\Repman\Service\Dist\Storage; use Buddy\Repman\Service\Downloader; -use Munus\Collection\GenericList; -use Symfony\Component\Finder\Finder; -use Symfony\Component\Finder\SplFileInfo; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; final class FileStorage implements Storage @@ -43,11 +40,11 @@ public function download(string $url, Dist $dist, array $headers = []): void AtomicFile::write( $filename, - $this->downloader->getContents($url, $headers, function () use ($url): void { + (string) stream_get_contents($this->downloader->getContents($url, $headers, function () use ($url): void { throw new NotFoundHttpException(sprintf('File not found at %s', $url)); })->getOrElseThrow( new \RuntimeException(sprintf('Failed to download %s from %s', $dist->package(), $url)) - ) + )) ); } @@ -71,42 +68,6 @@ public function size(Dist $dist): int return $size === false ? 0 : $size; } - /** - * @return GenericList - */ - public function packages(string $repo): GenericList - { - $dir = $this->distsDir.'/'.$repo.'/dist'; - if (!is_dir($dir)) { - return GenericList::empty(); - } - - $files = Finder::create()->directories()->sortByName()->depth(1)->ignoreVCS(true)->in($dir); - - return GenericList::ofAll(array_map( - fn (SplFileInfo $fileInfo) => $fileInfo->getRelativePathname(), - iterator_to_array($files->getIterator() - ))); - } - - public function remove(string $packageName): void - { - $dirs = []; - foreach (Finder::create()->directories()->path($packageName)->ignoreVCS(true)->in($this->distsDir) as $dir) { - /* @var SplFileInfo $dir */ - $dirs[] = $dir->getPathname(); - foreach (Finder::create()->files()->in($dir->getPathname()) as $file) { - /* @var SplFileInfo $file */ - @unlink($file->getPathname()); - } - } - - // can't remove dir in Finder loop, RecursiveDirectoryIterator throws error - foreach ($dirs as $dir) { - @rmdir($dir); - } - } - private function ensureDirExist(string $filename): void { $dirname = dirname($filename); diff --git a/src/Service/Dist/Storage/InMemoryStorage.php b/src/Service/Dist/Storage/InMemoryStorage.php index 8a088863..59fecf3d 100644 --- a/src/Service/Dist/Storage/InMemoryStorage.php +++ b/src/Service/Dist/Storage/InMemoryStorage.php @@ -6,7 +6,6 @@ use Buddy\Repman\Service\Dist; use Buddy\Repman\Service\Dist\Storage; -use Munus\Collection\GenericList; /** * @codeCoverageIgnore @@ -47,14 +46,4 @@ public function size(Dist $dist): int { return 0; } - - public function packages(string $repo): GenericList - { - return GenericList::ofAll($this->dists); - } - - public function remove(string $packageName): void - { - // TODO: Implement remove() method. - } } diff --git a/src/Service/Downloader.php b/src/Service/Downloader.php index 70917088..7664f89e 100644 --- a/src/Service/Downloader.php +++ b/src/Service/Downloader.php @@ -9,9 +9,24 @@ interface Downloader { /** + * todo: replace with getAsyncContents. + * * @param string[] $headers * - * @return Option + * @return Option */ public function getContents(string $url, array $headers = [], callable $notFoundHandler = null): Option; + + /** + * @param string[] $headers + * @param callable(resource):void $onFulfilled + */ + public function getAsyncContents(string $url, array $headers, callable $onFulfilled): void; + + /** + * @param callable(int):void $onFulfilled + */ + public function getLastModified(string $url, callable $onFulfilled): void; + + public function run(): void; } diff --git a/src/Service/Downloader/NativeDownloader.php b/src/Service/Downloader/NativeDownloader.php deleted file mode 100644 index e2f2e106..00000000 --- a/src/Service/Downloader/NativeDownloader.php +++ /dev/null @@ -1,71 +0,0 @@ - - */ - public function getContents(string $url, array $headers = [], callable $notFoundHandler = null): Option - { - $retries = 3; - do { - $content = @file_get_contents($url, false, $this->createContext($headers)); - if ($content !== false) { - return Option::some($content); - } - - if (isset($http_response_header) && $this->getStatusCode($http_response_header) === 404 && $notFoundHandler !== null) { - $notFoundHandler(); - } - --$retries; - } while ($retries > 0); - - return Option::none(); - } - - /** - * @param string[] $headers - * - * @return resource - */ - private function createContext(array $headers = []) - { - $phpVersion = 'PHP '.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'.'.PHP_RELEASE_VERSION; - - return stream_context_create([ - 'http' => [ - 'header' => array_merge([ - sprintf( - 'User-Agent: Repman/%s (%s; %s; %s)', - Kernel::REPMAN_VERSION, - php_uname('s'), - php_uname('r'), - $phpVersion - ), - ], $headers), - 'follow_location' => 1, - 'max_redirects' => 20, - ], - ]); - } - - /** - * @param mixed[] $headers - */ - private function getStatusCode(array $headers): int - { - preg_match('{HTTP\/\S*\s(\d{3})}', $headers[0], $match); - - return (int) $match[1]; - } -} diff --git a/src/Service/Downloader/ReactDownloader.php b/src/Service/Downloader/ReactDownloader.php new file mode 100644 index 00000000..74766ba1 --- /dev/null +++ b/src/Service/Downloader/ReactDownloader.php @@ -0,0 +1,116 @@ +loop = Factory::create(); + $this->browser = new Browser($this->loop, new Connector($this->loop, ['timeout' => 10])); + } + + /** + * @param string[] $headers + * + * @return Option + */ + public function getContents(string $url, array $headers = [], callable $notFoundHandler = null): Option + { + $retries = 3; + do { + $stream = @fopen($url, 'r', false, $this->createContext($headers)); + if ($stream !== false) { + return Option::some($stream); + } + + if (isset($http_response_header) && $this->getStatusCode($http_response_header) === 404 && $notFoundHandler !== null) { + $notFoundHandler(); + } + --$retries; + } while ($retries > 0); + + return Option::none(); + } + + public function getAsyncContents(string $url, array $headers, callable $onFulfilled): void + { + $this->browser->get($url, array_merge($headers, ['User-Agent' => $this->userAgent()])) + ->then(function (ResponseInterface $response) use ($onFulfilled): void { + $stream = $response->getBody()->detach(); + if (!is_resource($stream)) { + return; + } + $onFulfilled($stream); + }); + } + + /** + * @param callable(int):void $onFulfilled + */ + public function getLastModified(string $url, callable $onFulfilled): void + { + $this->browser->head($url, ['User-Agent' => $this->userAgent()])->then(function (ResponseInterface $response) use ($onFulfilled): void { + $lastModified = $response->getHeader('Last-Modified'); + if ($lastModified !== []) { + $onFulfilled((int) strtotime($lastModified[0])); + } + }); + } + + public function run(): void + { + $this->loop->run(); + } + + /** + * @param string[] $headers + * + * @return resource + */ + private function createContext(array $headers = []) + { + return stream_context_create([ + 'http' => [ + 'header' => array_merge([sprintf('User-Agent: %s', $this->userAgent())], $headers), + 'follow_location' => 1, + 'max_redirects' => 20, + ], + ]); + } + + private function userAgent(): string + { + return sprintf( + 'Repman/%s (%s; %s; %s)', + Kernel::REPMAN_VERSION, + php_uname('s'), + php_uname('r'), + 'PHP '.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'.'.PHP_RELEASE_VERSION + ); + } + + /** + * @param mixed[] $headers + */ + private function getStatusCode(array $headers): int + { + preg_match('{HTTP\/\S*\s(\d{3})}', $headers[0], $match); + + return (int) $match[1]; + } +} diff --git a/src/Service/Proxy.php b/src/Service/Proxy.php index d60913b4..f1430372 100644 --- a/src/Service/Proxy.php +++ b/src/Service/Proxy.php @@ -4,78 +4,71 @@ namespace Buddy\Repman\Service; -use Buddy\Repman\Service\Dist\Storage; -use Buddy\Repman\Service\Proxy\MetadataProvider; -use Composer\Semver\VersionParser; +use Buddy\Repman\Service\Proxy\Metadata; +use League\Flysystem\FileNotFoundException; +use League\Flysystem\FilesystemInterface; use Munus\Collection\GenericList; use Munus\Control\Option; final class Proxy { - public const PACKAGES_PATH = 'packages.json'; - public const PACKAGES_EXPIRE_TIME = 60; - private string $url; private string $name; - private MetadataProvider $metadataProvider; - private Storage $distStorage; - private VersionParser $versionParser; - - public function __construct(string $name, string $url, MetadataProvider $metadataProvider, Storage $distStorage) - { + private FilesystemInterface $filesystem; + private Downloader $downloader; + + public function __construct( + string $name, + string $url, + FilesystemInterface $proxyFilesystem, + Downloader $downloader + ) { $this->name = $name; $this->url = rtrim($url, '/'); - $this->metadataProvider = $metadataProvider; - $this->distStorage = $distStorage; - $this->versionParser = new VersionParser(); + $this->filesystem = $proxyFilesystem; + $this->downloader = $downloader; } /** - * @return Option + * @return Option */ - public function distFilename(string $package, string $version, string $ref, string $format): Option + public function metadata(string $package): Option { - $dist = new Dist($this->name, $package, $version, $ref, $format); - if (!$this->distStorage->has($dist)) { - $this->tryToDownload($package, $version, $dist); - } - $distFilename = $this->distStorage->filename($dist); - - return Option::when(file_exists($distFilename), $distFilename); + return $this->fetchMetadata(sprintf('%s/p2/%s.json', $this->url, $package)); } /** - * @return Option> + * @return Option */ - public function providerData(string $package, int $expireTime = self::PACKAGES_EXPIRE_TIME): Option + public function distribution(string $package, string $version, string $ref, string $format): Option { - if (!($fromPath = $this->metadataProvider->fromPath('p/'.$package, $this->url, $expireTime))->isEmpty()) { - return $fromPath; + $path = $this->distPath($package, $ref, $format); + if (!$this->filesystem->has($path)) { + foreach ($this->decodeMetadata($package) as $packageData) { + if (($packageData['dist']['reference'] ?? '') === $ref) { + $this->filesystem->writeStream($path, $this->downloader->getContents($packageData['dist']['url']) + ->getOrElseThrow(new \RuntimeException(sprintf('Failed to download file from %s', $packageData['dist']['url']))) + ); + break; + } + } } - $providerPath = $this->getProviderPath($package); - if ($providerPath->isEmpty()) { + try { + $stream = $this->filesystem->readStream($path); + + return $stream !== false ? Option::some($stream) : Option::none(); + } catch (FileNotFoundException $exception) { return Option::none(); } - - return $this->metadataProvider->fromUrl($this->getUrl($providerPath->get())); } /** - * @return Option> + * @return Option */ - public function providerDataV2(string $package, int $expireTime = self::PACKAGES_EXPIRE_TIME): Option + public function legacyMetadata(string $package): Option { - if (!($fromPath = $this->metadataProvider->fromPath('p2/'.$package, $this->url, $expireTime))->isEmpty()) { - return $fromPath; - } - - $providerPath = $this->getProviderPathV2($package); - if ($providerPath->isEmpty()) { - return Option::none(); - } - - return $this->metadataProvider->fromUrl($this->getUrl($providerPath->get())); + return $this->fetchMetadata(sprintf('%s/p/%s.json', $this->url, $package)); } /** @@ -83,101 +76,129 @@ public function providerDataV2(string $package, int $expireTime = self::PACKAGES */ public function syncedPackages(): GenericList { - return $this->distStorage->packages($this->name); + $packages = GenericList::empty(); + foreach ($this->filesystem->listContents(sprintf('%s/dist', $this->name)) as $vendor) { + foreach ($this->filesystem->listContents($vendor['path']) as $package) { + $packages = $packages->append($vendor['basename'].'/'.$package['basename']); + } + } + + return $packages; } - public function downloadByVersion(string $package, string $version, bool $fromCache = true): void + public function download(string $package, string $version): void { - $normalizedVersion = $this->versionParser->normalize($version); - $providerData = $this->providerData($package, $fromCache ? 0 : -60)->getOrElse([]); - - foreach ($providerData['packages'][$package] ?? [] as $packageData) { - $packageVersion = $packageData['version_normalized'] ?? $this->versionParser->normalize($packageData['version']); - $packageDist = $packageData['dist']; - - if ($packageVersion !== $normalizedVersion && isset($packageDist['url'], $packageDist['reference'])) { - $this->distStorage->download($packageDist['url'], new Dist( - $this->name, - $package, - $normalizedVersion, - $packageDist['reference'], - $packageDist['type'] - )); + $lastDist = null; + + foreach ($this->decodeMetadata($package) as $packageData) { + $lastDist = $packageData['dist'] ?? $lastDist; + $path = $this->distPath($package, $lastDist['reference'], $lastDist['type']); + if ($version === $packageData['version'] && !$this->filesystem->has($path)) { + $this->filesystem->writeStream($path, $this->downloader->getContents($lastDist['url']) + ->getOrElseThrow(new \RuntimeException(sprintf('Failed to download file from %s', $lastDist['url']))) + ); break; } } } - /** - * @return Option - */ - private function getProviderPath(string $packageName): Option + public function removeDist(string $package): void { - $root = $this->getRootPackages(); - if (isset($root['provider-includes'])) { - foreach ($root['provider-includes'] as $url => $meta) { - $data = $this->metadataProvider->fromUrl($this->getUrl(str_replace('%hash%', $meta['sha256'], $url)))->getOrElse([]); - if (isset($data['providers'][$packageName])) { - return Option::some( - (string) str_replace( - ['%package%', '%hash%'], - [$packageName, $data['providers'][$packageName]['sha256']], - $root['providers-url'] - ) - ); - } - } + if (mb_strlen($package) === 0) { + throw new \InvalidArgumentException('Empty package name'); } - return Option::none(); + $this->filesystem->deleteDir(sprintf('%s/dist/%s', $this->name, $package)); } - /** - * @return Option - */ - private function getProviderPathV2(string $packageName): Option + public function syncMetadata(): void { - $root = $this->getRootPackages(); - if (isset($root['metadata-url'])) { - return Option::some( - (string) str_replace( - ['%package%'], - [$packageName], - $root['metadata-url'] - ) + foreach ($this->filesystem->listContents($this->name) as $dir) { + if (!in_array($dir['basename'], ['p', 'p2'], true)) { + continue; + } + + $this->syncPackagesMetadata(array_filter( + $this->filesystem->listContents($dir['path'], true), + fn (array $file) => $file['type'] === 'file' && $file['extension'] === 'json') ); } + $this->downloader->run(); + } - return Option::none(); + public function url(): string + { + return $this->url; } /** - * @return array + * @param mixed[] $files */ - private function getRootPackages(): array + private function syncPackagesMetadata(array $files): void { - return $this->metadataProvider->fromUrl($this->getUrl(self::PACKAGES_PATH), self::PACKAGES_EXPIRE_TIME)->getOrElse([]); + foreach ($files as $file) { + $url = sprintf('%s://%s', parse_url($this->url, PHP_URL_SCHEME), $file['path']); + // todo: what if proxy do not return `Last-Modified` header? + $this->downloader->getLastModified($url, function (int $timestamp) use ($url, $file): void { + if ($timestamp > ($file['timestamp'] ?? time())) { + $this->downloader->getAsyncContents($url, [], function ($stream) use ($file): void { + $this->filesystem->putStream($file['path'], $stream); + }); + } + }); + } } - private function getUrl(string $path): string + /** + * @return mixed[] + */ + private function decodeMetadata(string $package): array { - return sprintf('%s/%s', $this->url, $path); + /** @var Metadata $metadata */ + $metadata = $this->metadata($package)->getOrElse(Metadata::fromString('[]')); + $metadata = json_decode((string) stream_get_contents($metadata->stream()), true); + + return is_array($metadata) ? ($metadata['packages'][$package] ?? []) : []; } - private function tryToDownload(string $package, string $version, Dist $dist, bool $fromCache = true): void + /** + * @return Option + */ + private function fetchMetadata(string $url): Option { - $providerData = $this->providerData($package, $fromCache ? 0 : -60)->getOrElse([]); - foreach ($providerData['packages'][$package] ?? [] as $packageData) { - $packageVersion = $packageData['version_normalized'] ?? $this->versionParser->normalize($packageData['version']); - if (($packageVersion === $version || md5($packageVersion) === $version) && isset($packageData['dist']['url'])) { - $this->distStorage->download($packageData['dist']['url'], $dist); - - return; + $path = $this->metadataPath($url); + if (!$this->filesystem->has($path)) { + $metadata = $this->downloader->getContents($url)->getOrNull(); + if ($metadata === null) { + return Option::none(); } + $this->filesystem->writeStream($path, $metadata); } - if ($fromCache) { - $this->tryToDownload($package, $version, $dist, false); + $stream = $this->filesystem->readStream($path); + if ($stream === false) { + return Option::none(); } + + return Option::some(new Metadata( + (int) $this->filesystem->getTimestamp($path), + $stream + )); + } + + private function distPath(string $package, string $ref, string $format): string + { + return sprintf( + '%s/dist/%s/%s.%s', + (string) parse_url($this->url, PHP_URL_HOST), + $package, + $ref, + $format + ); + } + + private function metadataPath(string $url): string + { + return (string) parse_url($url, PHP_URL_HOST).'/'.ltrim((string) parse_url($url, PHP_URL_PATH), '/'); } } diff --git a/src/Service/Proxy/Metadata.php b/src/Service/Proxy/Metadata.php new file mode 100644 index 00000000..da877786 --- /dev/null +++ b/src/Service/Proxy/Metadata.php @@ -0,0 +1,44 @@ +timestamp = $timestamp; + $this->stream = $stream; + } + + public static function fromString(string $string): self + { + return new self(time(), Stream::fromString($string)); + } + + public function timestamp(): int + { + return $this->timestamp; + } + + /** + * @return resource + */ + public function stream() + { + return $this->stream; + } +} diff --git a/src/Service/Proxy/MetadataProvider.php b/src/Service/Proxy/MetadataProvider.php deleted file mode 100644 index 52b9f5d5..00000000 --- a/src/Service/Proxy/MetadataProvider.php +++ /dev/null @@ -1,20 +0,0 @@ -> - */ - public function fromUrl(string $url, int $expireTime = 0): Option; - - /** - * @return Option> - */ - public function fromPath(string $package, string $repoUrl, int $expireTime = 0): Option; -} diff --git a/src/Service/Proxy/MetadataProvider/CacheableMetadataProvider.php b/src/Service/Proxy/MetadataProvider/CacheableMetadataProvider.php deleted file mode 100644 index 2547367c..00000000 --- a/src/Service/Proxy/MetadataProvider/CacheableMetadataProvider.php +++ /dev/null @@ -1,61 +0,0 @@ -downloader = $downloader; - $this->cache = $cache; - } - - /** - * @return Option> - */ - public function fromUrl(string $url, int $expireTime = 0): Option - { - $path = $this->getPath($url); - - return $this->cache->get($path, function () use ($url, $path, $expireTime): array { - $content = $this->downloader->getContents($url)->getOrElseThrow( - new \RuntimeException(sprintf('Failed to download metadata from %s', $url)) - ); - - if ($expireTime === 0) { - $this->cache->removeOld($path); - } - - return Json::decode($content); - }, $expireTime); - } - - /** - * @return Option> - */ - public function fromPath(string $package, string $repoUrl, int $expireTime = 0): Option - { - if (!$this->cache->exists($this->getPath($repoUrl.'/'.Proxy::PACKAGES_PATH), $expireTime)) { - return Option::none(); - } - - return $this->cache->find((string) parse_url($repoUrl, PHP_URL_HOST).'/'.$package, $expireTime); - } - - private function getPath(string $url): string - { - return (string) parse_url($url, PHP_URL_HOST).'/'.ltrim((string) parse_url($url, PHP_URL_PATH), '/'); - } -} diff --git a/src/Service/Proxy/ProxyFactory.php b/src/Service/Proxy/ProxyFactory.php index ba78a43d..3131d0f4 100644 --- a/src/Service/Proxy/ProxyFactory.php +++ b/src/Service/Proxy/ProxyFactory.php @@ -4,18 +4,19 @@ namespace Buddy\Repman\Service\Proxy; -use Buddy\Repman\Service\Dist\Storage; +use Buddy\Repman\Service\Downloader; use Buddy\Repman\Service\Proxy; +use League\Flysystem\FilesystemInterface; final class ProxyFactory { - private MetadataProvider $metadataProvider; - private Storage $distStorage; + private Downloader $downloader; + private FilesystemInterface $filesystem; - public function __construct(MetadataProvider $metadataProvider, Storage $distStorage) + public function __construct(Downloader $downloader, FilesystemInterface $proxyFilesystem) { - $this->metadataProvider = $metadataProvider; - $this->distStorage = $distStorage; + $this->downloader = $downloader; + $this->filesystem = $proxyFilesystem; } public function create(string $url): Proxy @@ -23,8 +24,8 @@ public function create(string $url): Proxy return new Proxy( (string) parse_url($url, PHP_URL_HOST), $url, - $this->metadataProvider, - $this->distStorage + $this->filesystem, + $this->downloader ); } } diff --git a/src/Service/Stream.php b/src/Service/Stream.php new file mode 100644 index 00000000..5dc76be6 --- /dev/null +++ b/src/Service/Stream.php @@ -0,0 +1,23 @@ +basePath = __DIR__.'/../Resources'; @@ -19,13 +26,18 @@ public function __construct() /** * @param string[] $headers * - * @return Option + * @return Option */ public function getContents(string $url, array $headers = [], callable $notFoundHandler = null): Option { + if (isset($this->content[$url])) { + return Option::some(Stream::fromString($this->content[$url]['content'])); + } + $path = $this->basePath.parse_url($url, PHP_URL_PATH); + if (file_exists($path)) { - return Option::some((string) file_get_contents($path)); + return Option::some(Stream::fromString((string) file_get_contents($path))); } if (strstr($path, 'not-found') !== false && $notFoundHandler !== null) { @@ -34,4 +46,46 @@ public function getContents(string $url, array $headers = [], callable $notFound return Option::none(); } + + /** + * @param callable(resource):void $onFulfilled + */ + public function getAsyncContents(string $url, array $headers, callable $onFulfilled): void + { + if (isset($this->content[$url]) && $this->content[$url]['content'] !== null) { + $onFulfilled(Stream::fromString($this->content[$url]['content'])); + } + } + + /** + * @param callable(int):void $onFulfilled + */ + public function getLastModified(string $url, callable $onFulfilled): void + { + if (isset($this->content[$url])) { + $timestamp = $this->content[$url]['timestamp']; + } + + $path = $this->basePath.parse_url($url, PHP_URL_PATH); + if (file_exists($path)) { + $timestamp = (int) filemtime($path); + } + + if (isset($timestamp)) { + $onFulfilled($timestamp); + } + } + + public function addContent(string $url, ?string $content, int $timestamp = null): void + { + $this->content[$url] = [ + 'timestamp' => $timestamp ?? time(), + 'content' => $content, + ]; + } + + public function run(): void + { + // TODO: Implement run() method. + } } diff --git a/tests/Doubles/FakeMetadataProvider.php b/tests/Doubles/FakeMetadataProvider.php deleted file mode 100644 index 28562096..00000000 --- a/tests/Doubles/FakeMetadataProvider.php +++ /dev/null @@ -1,38 +0,0 @@ - - */ - private array $metadata = []; - - public function fromUrl(string $url, int $expireTime = 0): Option - { - if (!isset($this->metadata[$url])) { - return Option::none(); - } - - return Option::some($this->metadata[$url]); - } - - public function fromPath(string $package, string $repoUrl, int $expireTime = 0): Option - { - return Option::none(); - } - - /** - * @param array $metadata - */ - public function setMetadata(string $url, array $metadata): void - { - $this->metadata[$url] = $metadata; - } -} diff --git a/tests/Functional/Command/ProxySyncMetadataCommandTest.php b/tests/Functional/Command/ProxySyncMetadataCommandTest.php new file mode 100644 index 00000000..30b94cbb --- /dev/null +++ b/tests/Functional/Command/ProxySyncMetadataCommandTest.php @@ -0,0 +1,64 @@ +downloader = new FakeDownloader(); + $this->lockFactory = new LockFactory(new FlockStore()); + } + + public function testMetadataSynchronization(): void + { + $lock = $this->lockFactory->createLock(ProxySyncMetadataCommand::LOCK_NAME); + self::assertFalse($lock->isAcquired()); + + $commandTester = new CommandTester($this->prepareCommand()); + $result = $commandTester->execute([]); + + self::assertEquals($result, 0); + + // test if lock was released + self::assertTrue($lock->acquire()); + $lock->release(); + } + + public function testJobLocking(): void + { + $lock = $this->lockFactory->createLock(ProxySyncMetadataCommand::LOCK_NAME); + $lock->acquire(); + self::assertTrue($lock->isAcquired()); + + $commandTester = new CommandTester($this->prepareCommand()); + $result = $commandTester->execute([]); + + self::assertEquals($result, 0); + self::assertTrue($lock->isAcquired()); + $lock->release(); + } + + private function prepareCommand(): ProxySyncMetadataCommand + { + return new ProxySyncMetadataCommand( + $this->container()->get(ProxyRegister::class), + $this->downloader, + $this->lockFactory + ); + } +} diff --git a/tests/Functional/Command/ProxySyncReleasesCommandTest.php b/tests/Functional/Command/ProxySyncReleasesCommandTest.php index 8b99d995..e7c277c1 100644 --- a/tests/Functional/Command/ProxySyncReleasesCommandTest.php +++ b/tests/Functional/Command/ProxySyncReleasesCommandTest.php @@ -5,13 +5,9 @@ namespace Buddy\Repman\Tests\Functional\Command; use Buddy\Repman\Command\ProxySyncReleasesCommand; -use Buddy\Repman\Service\Cache\InMemoryCache; -use Buddy\Repman\Service\Dist\Storage\FileStorage; use Buddy\Repman\Service\Downloader; -use Buddy\Repman\Service\Proxy\MetadataProvider\CacheableMetadataProvider; -use Buddy\Repman\Service\Proxy\ProxyFactory; use Buddy\Repman\Service\Proxy\ProxyRegister; -use Buddy\Repman\Tests\Doubles\FakeDownloader; +use Buddy\Repman\Service\Stream; use Buddy\Repman\Tests\Functional\FunctionalTestCase; use Doctrine\DBAL\Connection; use Munus\Control\Option; @@ -25,7 +21,7 @@ final class ProxySyncReleasesCommandTest extends FunctionalTestCase { private string $basePath = __DIR__.'/../../Resources'; private FilesystemAdapter $cache; - private string $newDistPath = '/packagist.org/dist/buddy-works/repman/1.2.3.0_5e77ad71826b9411cb873c0947a7d541d822dff1.zip'; + private string $newDistPath = '/packagist.org/dist/buddy-works/repman/61e39aa8197cf1bc7fcb16a6f727b0c291bc9b76.zip'; private string $feedPath = '/packagist.org/feed/releases.rss'; public function testSyncReleases(): void @@ -85,18 +81,10 @@ private function prepareCommand(string $feed, bool $fromCache = false, bool $loc } $feedDownloader = $this->createMock(Downloader::class); - $feedDownloader->method('getContents')->willReturn(Option::of($feed)); - - $storageDownloader = $this->createMock(Downloader::class); - $storageDownloader->method('getContents')->willReturn(Option::of('test')); + $feedDownloader->method('getContents')->willReturn(Option::of(Stream::fromString($feed))); return new ProxySyncReleasesCommand( - new ProxyRegister( - new ProxyFactory( - new CacheableMetadataProvider(new FakeDownloader(), new InMemoryCache()), - new FileStorage($this->basePath, $storageDownloader) - ) - ), + $this->container()->get(ProxyRegister::class), $feedDownloader, $this->cache(), $lockFactory diff --git a/tests/Functional/Controller/Admin/ConfigControllerTest.php b/tests/Functional/Controller/Admin/ConfigControllerTest.php index 904c8683..565a5df3 100644 --- a/tests/Functional/Controller/Admin/ConfigControllerTest.php +++ b/tests/Functional/Controller/Admin/ConfigControllerTest.php @@ -83,6 +83,9 @@ public function testToggleAuthenticationOptions(): void $this->client->request('GET', $this->urlTo('register_buddy_start')); self::assertEquals(Response::HTTP_NOT_FOUND, $this->client->getResponse()->getStatusCode()); + $this->client->request('GET', $this->urlTo('app_register_confirm', ['token' => '825f33c5-2311-41ec-ba18-e967027b3f6f'])); + self::assertEquals(Response::HTTP_NOT_FOUND, $this->client->getResponse()->getStatusCode()); + $this->client->request('GET', $this->urlTo('admin_config')); $this->client->submitForm('save', [ 'local_authentication' => 'login_and_registration', diff --git a/tests/Functional/Controller/ProxyControllerTest.php b/tests/Functional/Controller/ProxyControllerTest.php index aa0fbb95..bbf7589b 100644 --- a/tests/Functional/Controller/ProxyControllerTest.php +++ b/tests/Functional/Controller/ProxyControllerTest.php @@ -35,9 +35,9 @@ public function testPackagesAction(): void public function testProviderAction(): void { - $this->client->request('GET', '/p/buddy-works/repman', [], [], [ + $response = $this->contentFromStream(fn () => $this->client->request('GET', '/p/buddy-works/repman', [], [], [ 'HTTP_HOST' => 'repo.repman.wip', - ]); + ])); self::assertMatchesPattern(' { @@ -46,27 +46,28 @@ public function testProviderAction(): void "buddy-works/repman": "@array@" } } - ', $this->client->getResponse()->getContent()); + ', $response); + self::assertTrue($this->client->getResponse()->isCacheable()); } public function testProviderActionEmptyPackagesWhenNotExist(): void { - $this->client->request('GET', '/p/buddy-works/example-app', [], [], [ + $response = $this->contentFromStream(fn () => $this->client->request('GET', '/p/buddy-works/example-app', [], [], [ 'HTTP_HOST' => 'repo.repman.wip', - ]); + ])); self::assertMatchesPattern(' { "packages": {} } - ', $this->client->getResponse()->getContent()); + ', $response); } public function testProviderV2Action(): void { - $this->client->request('GET', '/p2/buddy-works/repman.json', [], [], [ + $response = $this->contentFromStream(fn () => $this->client->request('GET', '/p2/buddy-works/repman.json', [], [], [ 'HTTP_HOST' => 'repo.repman.wip', - ]); + ])); self::assertMatchesPattern(' { @@ -75,16 +76,27 @@ public function testProviderV2Action(): void "buddy-works/repman": "@array@" } } - ', $this->client->getResponse()->getContent()); + ', $response); + self::assertTrue($this->client->getResponse()->isCacheable()); } - public function testDistributionAction(): void + public function testProviderV2ActionWhenPackageNotExist(): void { - $this->client->request('GET', '/dists/buddy-works/repman/0.1.2.0/f0c896a759d4e2e1eff57978318e841911796305.zip', [], [], [ + $this->client->request('GET', '/p2/buddy-works/example-app.json', [], [], [ 'HTTP_HOST' => 'repo.repman.wip', ]); + self::assertTrue($this->client->getResponse()->isNotFound()); + } + + public function testDistributionAction(): void + { + $file = $this->contentFromStream(fn () => $this->client->request('GET', '/dists/buddy-works/repman/0.1.2.0/f0c896a759d4e2e1eff57978318e841911796305.zip', [], [], [ + 'HTTP_HOST' => 'repo.repman.wip', + ])); + self::assertTrue($this->client->getResponse()->isOk()); + self::assertTrue($this->client->getResponse()->isCacheable()); } public function testDistributionNotFoundAction(): void diff --git a/tests/Functional/FunctionalTestCase.php b/tests/Functional/FunctionalTestCase.php index fe550e66..83f5b23f 100644 --- a/tests/Functional/FunctionalTestCase.php +++ b/tests/Functional/FunctionalTestCase.php @@ -24,6 +24,16 @@ protected function setUp(): void $this->fixtures = new FixturesManager(self::$kernel->getContainer()->get('test.service_container')); } + public function contentFromStream(callable $request): string + { + ob_start(); + $request(); + $content = (string) ob_get_contents(); + ob_end_clean(); + + return $content; + } + /** * @param array $parameters */ diff --git a/tests/Integration/MessageHandler/Proxy/RemoveDistHandlerTest.php b/tests/Integration/MessageHandler/Proxy/RemoveDistHandlerTest.php index ccff3b03..fa4473ac 100644 --- a/tests/Integration/MessageHandler/Proxy/RemoveDistHandlerTest.php +++ b/tests/Integration/MessageHandler/Proxy/RemoveDistHandlerTest.php @@ -5,7 +5,7 @@ namespace Buddy\Repman\Tests\Integration\MessageHandler\Proxy; use Buddy\Repman\Message\Proxy\RemoveDist; -use Buddy\Repman\Service\Dist\Storage; +use Buddy\Repman\Service\Proxy\ProxyRegister; use Buddy\Repman\Tests\Integration\IntegrationTestCase; use Munus\Collection\GenericList; use Symfony\Component\Messenger\MessageBusInterface; @@ -15,11 +15,11 @@ final class RemoveDistHandlerTest extends IntegrationTestCase public function testRemoveDistByPackageName(): void { $this->container()->get(MessageBusInterface::class)->dispatch( - new RemoveDist('some-vendor/some-name') + new RemoveDist('packagist.org', 'some-vendor/some-name') ); self::assertTrue(GenericList::ofAll(['buddy-works/repman'])->equals( - $this->container()->get(Storage::class)->packages('packagist.org') + $this->container()->get(ProxyRegister::class)->getByHost('packagist.org')->syncedPackages() )); } } diff --git a/tests/Resources/p/buddy-works/repman$cd34b123d88f963b6ccdce310612bf6ae337fb50cd8d83f4412ae591443231d0.json b/tests/Resources/p/buddy-works/repman.json similarity index 100% rename from tests/Resources/p/buddy-works/repman$cd34b123d88f963b6ccdce310612bf6ae337fb50cd8d83f4412ae591443231d0.json rename to tests/Resources/p/buddy-works/repman.json diff --git a/tests/Resources/packagist.org/dist/buddy-works/repman/0.1.2.0_f0c896a759d4e2e1eff57978318e841911796305.zip b/tests/Resources/packagist.org/dist/buddy-works/repman/f0c896a759d4e2e1eff57978318e841911796305.zip similarity index 100% rename from tests/Resources/packagist.org/dist/buddy-works/repman/0.1.2.0_f0c896a759d4e2e1eff57978318e841911796305.zip rename to tests/Resources/packagist.org/dist/buddy-works/repman/f0c896a759d4e2e1eff57978318e841911796305.zip diff --git a/tests/Resources/packagist.org/p/buddy-works/repman.json b/tests/Resources/packagist.org/p/buddy-works/repman.json new file mode 100644 index 00000000..ff5f8e30 --- /dev/null +++ b/tests/Resources/packagist.org/p/buddy-works/repman.json @@ -0,0 +1,202 @@ +{ + "packages": { + "buddy-works\/repman": { + "0.1.0": { + "name": "buddy-works\/repman", + "description": "Power of object-oriented programming with the elegance of functional programming.", + "keywords": [], + "homepage": "", + "version": "0.1.0", + "version_normalized": "0.1.0.0-RC1", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arkadiusz Kondas", + "email": "arkadiusz.kondas@gmail.com" + } + ], + "source": { + "type": "git", + "url": "https:\/\/github.com\/munusphp\/munus.git", + "reference": "5e77ad71826b9411cb873c0947a7d541d822dff1" + }, + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/munusphp\/munus\/zipball\/5e77ad71826b9411cb873c0947a7d541d822dff1", + "reference": "5e77ad71826b9411cb873c0947a7d541d822dff1", + "shasum": "" + }, + "type": "library", + "time": "2019-11-29T21:18:39+00:00", + "autoload": { + "psr-4": { + "Munus\\": "src\/" + } + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "friendsofphp\/php-cs-fixer": "^2.15", + "phpunit\/phpunit": "^8.4", + "vimeo\/psalm": "^3.6", + "psalm\/plugin-phpunit": "^0.7.0", + "phpstan\/phpstan": "^0.11.19" + }, + "uid": 3420230, + "notification-url": "https:\/\/packagist.org\/downloads\/" + }, + "2.0.0": { + "version": "2.0.0", + "dist":{ + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/buddy-works\/repman\/zipball\/not-found", + "reference": "not-found", + "shasum": "" + } + }, + "0.1.2": { + "name": "buddy-works\/repman", + "description": "Power of object-oriented programming with the elegance of functional programming.", + "keywords": [], + "homepage": "", + "version": "0.1.2", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arkadiusz Kondas", + "email": "arkadiusz.kondas@gmail.com" + } + ], + "source": { + "type": "git", + "url": "https:\/\/github.com\/munusphp\/munus.git", + "reference": "f0c896a759d4e2e1eff57978318e841911796305" + }, + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/munusphp\/munus\/zipball\/f0c896a759d4e2e1eff57978318e841911796305", + "reference": "f0c896a759d4e2e1eff57978318e841911796305", + "shasum": "" + }, + "type": "library", + "time": "2019-11-29T21:18:39+00:00", + "autoload": { + "psr-4": { + "Munus\\": "src\/" + } + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "friendsofphp\/php-cs-fixer": "^2.15", + "phpunit\/phpunit": "^8.4", + "vimeo\/psalm": "^3.6", + "psalm\/plugin-phpunit": "^0.7.0", + "phpstan\/phpstan": "^0.11.19" + }, + "uid": 3420230, + "notification-url": "https:\/\/packagist.org\/downloads\/" + }, + "dev-feature/awesome": { + "name": "buddy-works\/repman", + "description": "Power of object-oriented programming with the elegance of functional programming.", + "keywords": [], + "homepage": "", + "version": "dev-feature/awesome", + "version_normalized": "dev-feature/awesome", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arkadiusz Kondas", + "email": "arkadiusz.kondas@gmail.com" + } + ], + "source": { + "type": "git", + "url": "https:\/\/github.com\/munusphp\/munus.git", + "reference": "e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb" + }, + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/munusphp\/munus\/zipball\/e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb", + "reference": "e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb", + "shasum": "" + }, + "type": "library", + "time": "2019-12-08T08:35:40+00:00", + "autoload": { + "psr-4": { + "Munus\\": "src\/" + } + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "friendsofphp\/php-cs-fixer": "^2.15", + "phpunit\/phpunit": "^8.4", + "vimeo\/psalm": "^3.6", + "psalm\/plugin-phpunit": "^0.7.0", + "phpstan\/phpstan": "^0.11.19" + }, + "uid": 3386500, + "notification-url": "https:\/\/packagist.org\/downloads\/" + }, + "dev-master": { + "name": "buddy-works\/repman", + "description": "Power of object-oriented programming with the elegance of functional programming.", + "keywords": [], + "homepage": "", + "version": "dev-master", + "version_normalized": "9999999-dev", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arkadiusz Kondas", + "email": "arkadiusz.kondas@gmail.com" + } + ], + "source": { + "type": "git", + "url": "https:\/\/github.com\/munusphp\/munus.git", + "reference": "e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb" + }, + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/munusphp\/munus\/zipball\/e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb", + "reference": "e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb", + "shasum": "" + }, + "type": "library", + "time": "2019-12-08T08:35:40+00:00", + "autoload": { + "psr-4": { + "Munus\\": "src\/" + } + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "friendsofphp\/php-cs-fixer": "^2.15", + "phpunit\/phpunit": "^8.4", + "vimeo\/psalm": "^3.6", + "psalm\/plugin-phpunit": "^0.7.0", + "phpstan\/phpstan": "^0.11.19" + }, + "uid": 3386500, + "notification-url": "https:\/\/packagist.org\/downloads\/" + } + } + } +} diff --git a/tests/Resources/packagist.org/p2/buddy-works/repman.json b/tests/Resources/packagist.org/p2/buddy-works/repman.json new file mode 100644 index 00000000..1728cdc3 --- /dev/null +++ b/tests/Resources/packagist.org/p2/buddy-works/repman.json @@ -0,0 +1,211 @@ +{ + "packages": { + "buddy-works\/repman": { + "0.1.0": { + "name": "buddy-works\/repman", + "description": "Power of object-oriented programming with the elegance of functional programming.", + "keywords": [], + "homepage": "", + "version": "0.1.0", + "version_normalized": "0.1.0.0-RC1", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arkadiusz Kondas", + "email": "arkadiusz.kondas@gmail.com" + } + ], + "source": { + "type": "git", + "url": "https:\/\/github.com\/munusphp\/munus.git", + "reference": "5e77ad71826b9411cb873c0947a7d541d822dff1" + }, + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/munusphp\/munus\/zipball\/5e77ad71826b9411cb873c0947a7d541d822dff1", + "reference": "5e77ad71826b9411cb873c0947a7d541d822dff1", + "shasum": "" + }, + "type": "library", + "time": "2019-11-29T21:18:39+00:00", + "autoload": { + "psr-4": { + "Munus\\": "src\/" + } + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "friendsofphp\/php-cs-fixer": "^2.15", + "phpunit\/phpunit": "^8.4", + "vimeo\/psalm": "^3.6", + "psalm\/plugin-phpunit": "^0.7.0", + "phpstan\/phpstan": "^0.11.19" + }, + "uid": 3420230, + "notification-url": "https:\/\/packagist.org\/downloads\/" + }, + "2.0.0": { + "version": "2.0.0", + "dist":{ + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/buddy-works\/repman\/zipball\/not-found", + "reference": "not-found", + "shasum": "" + } + }, + "1.2.3": { + "version": "1.2.3", + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/zipball\/61e39aa8197cf1bc7fcb16a6f727b0c291bc9b76", + "reference": "61e39aa8197cf1bc7fcb16a6f727b0c291bc9b76", + "shasum": "" + } + }, + "0.1.2": { + "name": "buddy-works\/repman", + "description": "Power of object-oriented programming with the elegance of functional programming.", + "keywords": [], + "homepage": "", + "version": "0.1.2", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arkadiusz Kondas", + "email": "arkadiusz.kondas@gmail.com" + } + ], + "source": { + "type": "git", + "url": "https:\/\/github.com\/munusphp\/munus.git", + "reference": "f0c896a759d4e2e1eff57978318e841911796305" + }, + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/munusphp\/munus\/zipball\/f0c896a759d4e2e1eff57978318e841911796305", + "reference": "f0c896a759d4e2e1eff57978318e841911796305", + "shasum": "" + }, + "type": "library", + "time": "2019-11-29T21:18:39+00:00", + "autoload": { + "psr-4": { + "Munus\\": "src\/" + } + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "friendsofphp\/php-cs-fixer": "^2.15", + "phpunit\/phpunit": "^8.4", + "vimeo\/psalm": "^3.6", + "psalm\/plugin-phpunit": "^0.7.0", + "phpstan\/phpstan": "^0.11.19" + }, + "uid": 3420230, + "notification-url": "https:\/\/packagist.org\/downloads\/" + }, + "dev-feature/awesome": { + "name": "buddy-works\/repman", + "description": "Power of object-oriented programming with the elegance of functional programming.", + "keywords": [], + "homepage": "", + "version": "dev-feature/awesome", + "version_normalized": "dev-feature/awesome", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arkadiusz Kondas", + "email": "arkadiusz.kondas@gmail.com" + } + ], + "source": { + "type": "git", + "url": "https:\/\/github.com\/munusphp\/munus.git", + "reference": "e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb" + }, + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/munusphp\/munus\/zipball\/e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb", + "reference": "e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb", + "shasum": "" + }, + "type": "library", + "time": "2019-12-08T08:35:40+00:00", + "autoload": { + "psr-4": { + "Munus\\": "src\/" + } + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "friendsofphp\/php-cs-fixer": "^2.15", + "phpunit\/phpunit": "^8.4", + "vimeo\/psalm": "^3.6", + "psalm\/plugin-phpunit": "^0.7.0", + "phpstan\/phpstan": "^0.11.19" + }, + "uid": 3386500, + "notification-url": "https:\/\/packagist.org\/downloads\/" + }, + "dev-master": { + "name": "buddy-works\/repman", + "description": "Power of object-oriented programming with the elegance of functional programming.", + "keywords": [], + "homepage": "", + "version": "dev-master", + "version_normalized": "9999999-dev", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Arkadiusz Kondas", + "email": "arkadiusz.kondas@gmail.com" + } + ], + "source": { + "type": "git", + "url": "https:\/\/github.com\/munusphp\/munus.git", + "reference": "e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb" + }, + "dist": { + "type": "zip", + "url": "https:\/\/api.github.com\/repos\/munusphp\/munus\/zipball\/e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb", + "reference": "e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb", + "shasum": "" + }, + "type": "library", + "time": "2019-12-08T08:35:40+00:00", + "autoload": { + "psr-4": { + "Munus\\": "src\/" + } + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "friendsofphp\/php-cs-fixer": "^2.15", + "phpunit\/phpunit": "^8.4", + "vimeo\/psalm": "^3.6", + "psalm\/plugin-phpunit": "^0.7.0", + "phpstan\/phpstan": "^0.11.19" + }, + "uid": 3386500, + "notification-url": "https:\/\/packagist.org\/downloads\/" + } + } + } +} diff --git a/tests/Resources/zipball/61e39aa8197cf1bc7fcb16a6f727b0c291bc9b76 b/tests/Resources/zipball/61e39aa8197cf1bc7fcb16a6f727b0c291bc9b76 new file mode 100644 index 00000000..411e74f6 Binary files /dev/null and b/tests/Resources/zipball/61e39aa8197cf1bc7fcb16a6f727b0c291bc9b76 differ diff --git a/tests/Unit/DistTest.php b/tests/Unit/DistTest.php new file mode 100644 index 00000000..ae133e17 --- /dev/null +++ b/tests/Unit/DistTest.php @@ -0,0 +1,18 @@ +version()); + } +} diff --git a/tests/Unit/Service/Cache/FileCacheTest.php b/tests/Unit/Service/Cache/FileCacheTest.php deleted file mode 100644 index 485a484b..00000000 --- a/tests/Unit/Service/Cache/FileCacheTest.php +++ /dev/null @@ -1,127 +0,0 @@ -basePath = sys_get_temp_dir().'/repman'; - $this->exceptionHandler = new InMemoryExceptionHandler(); - $this->cache = new FileCache($this->basePath, $this->exceptionHandler); - $this->packagesPath = $this->basePath.'/packagist/packages.json'; - } - - protected function tearDown(): void - { - $filesystem = new Filesystem(); - $filesystem->remove($this->basePath); - } - - public function testThrowExceptionWhenCacheDirIsNotWritable(): void - { - $this->expectException(\InvalidArgumentException::class); - - new FileCache('/proc/new', new InMemoryExceptionHandler()); - } - - public function testCacheHitAndExists(): void - { - $content = '{"some":"json"}'; - @mkdir(dirname($this->packagesPath)); - file_put_contents($this->packagesPath, serialize($content)); - - self::assertTrue(Option::some($content)->equals( - $this->cache->get('packagist/packages.json', function (): void { - throw new \RuntimeException('This should not happen'); - }) - )); - self::assertTrue($this->cache->exists('packagist/packages.json')); - } - - public function testHandleExceptionOnFailure(): void - { - $exception = new \LogicException('something goes wrong'); - - $this->cache->get('some.file', function () use ($exception): void { - throw $exception; - }); - - self::assertTrue($this->exceptionHandler->exist($exception)); - } - - public function testCacheFind(): void - { - $file = '/p/buddy-works/repman$d1392374.json'; - @mkdir(dirname($this->basePath.$file), 0777, true); - file_put_contents($this->basePath.$file, 'a:1:{s:4:"some";s:4:"json";}'); - - self::assertTrue(Option::some(['some' => 'json'])->equals($this->cache->find('/p/buddy-works/repman'))); - self::assertTrue(Option::none()->equals($this->cache->find('/path/to/not-exist-dir'))); - self::assertTrue(Option::none()->equals($this->cache->find('/p/buddy-works/missing-package'))); - } - - public function testCacheFindExpire(): void - { - $file = '/p/buddy-works/repman$d1392374.json'; - @mkdir(dirname($this->basePath.$file), 0777, true); - file_put_contents($this->basePath.$file, 'a:1:{s:4:"some";s:4:"json";}'); - - self::assertTrue(Option::none()->equals($this->cache->find('/p/buddy-works/repman', -2))); - } - - public function testCacheHitExpire(): void - { - $cache = new FileCache(__DIR__.'/../../../Resources', new InMemoryExceptionHandler()); - - self::assertTrue(Option::none()->equals($cache->get('packages.json', function (): void { - // to prevent overwrite packages.json - throw new \LogicException(); - }, 60))); - } - - public function testCacheMiss(): void - { - $content = '{"some":"json"}'; - - self::assertTrue(!file_exists($this->packagesPath)); - self::assertTrue(Option::some($content)->equals( - $this->cache->get('packagist/packages.json', fn () => $content) - )); - self::assertTrue(file_exists($this->packagesPath)); - } - - public function testCacheRemoveByPattern(): void - { - $file = '/p/buddy-works/repman$d1392374.json'; - @mkdir(dirname($this->basePath.$file), 0777, true); - file_put_contents($this->basePath.$file, '{}'); - - $this->cache->removeOld($file); - self::assertTrue(!file_exists($this->basePath.$file)); - } - - public function testCacheNotRemoveWhenDollarSignIsMissing(): void - { - $file = '/p/buddy-works/repman.json'; - @mkdir(dirname($this->basePath.$file), 0777, true); - file_put_contents($this->basePath.$file, '{}'); - - $this->cache->removeOld($file); - self::assertTrue(file_exists($this->basePath.$file)); - @unlink($this->basePath.$file); - } -} diff --git a/tests/Unit/Service/Dist/Storage/FileStorageTest.php b/tests/Unit/Service/Dist/Storage/FileStorageTest.php index a167aacf..a93e6757 100644 --- a/tests/Unit/Service/Dist/Storage/FileStorageTest.php +++ b/tests/Unit/Service/Dist/Storage/FileStorageTest.php @@ -8,9 +8,9 @@ use Buddy\Repman\Service\Dist\Storage\FileStorage; use Buddy\Repman\Service\Downloader; use Buddy\Repman\Tests\Doubles\FakeDownloader; -use Munus\Collection\GenericList; use PHPUnit\Framework\TestCase; use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; final class FileStorageTest extends TestCase { @@ -34,7 +34,7 @@ public function testDownloadPackage(): void $packagePath = $this->basePath.'/packagist.org/dist/buddy-works/repman/0.1.2.0_f0c896.zip'; self::assertFileNotExists($packagePath); - $this->storage->download('https://some.domain/packagist.org/dist/buddy-works/repman/0.1.2.0_f0c896a759d4e2e1eff57978318e841911796305.zip', new Dist( + $this->storage->download('https://some.domain/packagist.org/dist/buddy-works/repman/f0c896a759d4e2e1eff57978318e841911796305.zip', new Dist( 'packagist.org', 'buddy-works/repman', '0.1.2.0', @@ -86,23 +86,30 @@ public function testHasPackage(): void ))); } - public function testDistPackageRemove(): void + public function testSize(): void { $this->createTempFile($packagePath = $this->basePath.'/packagist.org/dist/buddy-works/repman/0.1.2.0_f0c896.zip'); - $this->createTempFile($otherPath = $this->basePath.'/packagist.org/dist/buddy-works/other-package/0.1.2.0_f0c896.zip'); - self::assertFileExists($packagePath); - self::assertFileExists($otherPath); - - $this->storage->remove('buddy-works/repman'); - - self::assertFileNotExists($packagePath); - self::assertFileExists($otherPath); + self::assertEquals(7, $this->storage->size(new Dist( + 'packagist.org', + 'buddy-works/repman', + '0.1.2.0', + 'f0c896', + 'zip' + ))); } - public function testReturnEmptyPackagesListWhenDirNotExist(): void + public function testThrowHttpNotFoundExceptionWhenFileNotFound(): void { - self::assertTrue(GenericList::empty()->equals($this->storage->packages('not-exist'))); + $this->expectException(NotFoundHttpException::class); + + $this->storage->download('https://some.domain/packagist.org/dist/not-found', new Dist( + 'packagist.org', + 'buddy-works/repman', + '0.1.2.0', + 'f0c896', + 'zip' + )); } private function createTempFile(string $path): void diff --git a/tests/Unit/Service/Downloader/NativeDownloaderTest.php b/tests/Unit/Service/Downloader/NativeDownloaderTest.php deleted file mode 100644 index 746c42b2..00000000 --- a/tests/Unit/Service/Downloader/NativeDownloaderTest.php +++ /dev/null @@ -1,35 +0,0 @@ -equals( - (new NativeDownloader())->getContents($packages) - )); - } - - public function testFailedDownload(): void - { - self::assertTrue(Option::none()->equals( - (new NativeDownloader())->getContents('/tmp/not-exists') - )); - } - - public function testNotFoundHandler(): void - { - $this->expectException(\LogicException::class); - - (new NativeDownloader())->getContents('https://repman.io/not-exist', [], function (): void {throw new \LogicException('Not found'); }); - } -} diff --git a/tests/Unit/Service/Downloader/ReactDownloaderTest.php b/tests/Unit/Service/Downloader/ReactDownloaderTest.php new file mode 100644 index 00000000..4854b7a4 --- /dev/null +++ b/tests/Unit/Service/Downloader/ReactDownloaderTest.php @@ -0,0 +1,45 @@ +getContents($packages)->getOrNull())); + } + + public function testFailedDownload(): void + { + self::assertTrue(Option::none()->equals( + (new ReactDownloader())->getContents('/tmp/not-exists') + )); + } + + public function testNotFoundHandler(): void + { + $this->expectException(\LogicException::class); + + (new ReactDownloader())->getContents('https://repman.io/not-exist', [], function (): void {throw new \LogicException('Not found'); }); + } + + public function testLastModified(): void + { + $downloader = new ReactDownloader(); + $downloader->getLastModified('https://repman.io', function (int $timestamp): void { + self::assertTrue($timestamp > 0); + }); + $downloader->getLastModified('/tmp/not-exists', function (int $timestamp): void { + throw new \LogicException('Should not happen'); + }); + $downloader->run(); + } +} diff --git a/tests/Unit/Service/Proxy/MetadataProvider/CacheableMetadataProviderTest.php b/tests/Unit/Service/Proxy/MetadataProvider/CacheableMetadataProviderTest.php deleted file mode 100644 index 94cdb65a..00000000 --- a/tests/Unit/Service/Proxy/MetadataProvider/CacheableMetadataProviderTest.php +++ /dev/null @@ -1,61 +0,0 @@ -cache->exists(Argument::cetera())->willReturn(false); - - self::assertTrue(Option::none()->equals( - $this->provider->fromPath('buddy-works/repman', 'https://repman.buddy.works', 60) - )); - } - - public function testFromPathWithCache(): void - { - $metadata = ['metadata']; - $this->cache->exists(Argument::cetera())->willReturn(true); - $this->cache->find(Argument::type('string'), Proxy::PACKAGES_EXPIRE_TIME)->willReturn(Option::some($metadata)); - - self::assertTrue(Option::some($metadata)->equals( - $this->provider->fromPath('buddy-works/repman', 'https://repman.buddy.works', 60) - )); - } - - protected function setUp(): void - { - $this->cache = $this->prophesize(Cache::class); - $this->downloader = $this->prophesize(Downloader::class); - - /** @var Downloader $downloader */ - $downloader = $this->downloader->reveal(); - /** @var Cache $cache */ - $cache = $this->cache->reveal(); - - $this->provider = new CacheableMetadataProvider($downloader, $cache); - } -} diff --git a/tests/Unit/Service/ProxyTest.php b/tests/Unit/Service/ProxyTest.php index 9189ba63..24d30ca1 100644 --- a/tests/Unit/Service/ProxyTest.php +++ b/tests/Unit/Service/ProxyTest.php @@ -4,122 +4,117 @@ namespace Buddy\Repman\Tests\Unit\Service; -use Buddy\Repman\Service\Cache\InMemoryCache; -use Buddy\Repman\Service\Dist; -use Buddy\Repman\Service\Dist\Storage; -use Buddy\Repman\Service\Dist\Storage\InMemoryStorage; use Buddy\Repman\Service\Proxy; -use Buddy\Repman\Service\Proxy\MetadataProvider\CacheableMetadataProvider; +use Buddy\Repman\Service\Proxy\Metadata; use Buddy\Repman\Tests\Doubles\FakeDownloader; -use Buddy\Repman\Tests\Doubles\FakeMetadataProvider; -use Munus\Control\Option; +use League\Flysystem\Filesystem; +use League\Flysystem\Memory\MemoryAdapter; use PHPUnit\Framework\TestCase; -use Prophecy\Argument; final class ProxyTest extends TestCase { - public function testPackageProvider(): void - { - $proxy = new Proxy('packagist.org', 'https://packagist.org', new CacheableMetadataProvider(new FakeDownloader(), new InMemoryCache()), new InMemoryStorage()); - $provider = $proxy->providerData('buddy-works/repman')->get(); + private Proxy $proxy; + private Filesystem $filesystem; + private FakeDownloader $downloader; - self::assertEquals('0.1.0', $provider['packages']['buddy-works/repman']['0.1.0']['version']); + protected function setUp(): void + { + $this->proxy = new Proxy( + 'packagist.org', + 'https://packagist.org', + $this->filesystem = new Filesystem(new MemoryAdapter()), + $this->downloader = new FakeDownloader() + ); } - public function testPackageProviderFromCache(): void + public function testPackageMetadataDownload(): void { - $cache = new InMemoryCache(); - $cache->get('packagist.org/packages.json', function (): array {return ['metadata']; }); - $cache->get('packagist.org/p/buddy-works/repman', function (): array {return ['package-metadata']; }); - $proxy = new Proxy('packagist.org', 'https://packagist.org', new CacheableMetadataProvider(new FakeDownloader(), $cache), new InMemoryStorage()); + $this->downloader->addContent('https://packagist.org/p2/buddy-works/repman.json', 'metadata'); + + $metadata = $this->proxy->metadata('buddy-works/repman'); - self::assertEquals(['package-metadata'], $proxy->providerData('buddy-works/repman')->get()); + self::assertTrue($metadata->isPresent()); } - public function testPackageProviderV2FromCache(): void + public function testDownloadDistWhenNotExists(): void { - $cache = new InMemoryCache(); - $cache->get('packagist.org/packages.json', fn () => ['metadata-url' => '/p2/%package%.json']); - $cache->get('packagist.org/p2/buddy-works/repman', fn () => ['package-metadata']); - $proxy = new Proxy('packagist.org', 'https://packagist.org', new CacheableMetadataProvider(new FakeDownloader(), $cache), new InMemoryStorage()); - - self::assertEquals(['package-metadata'], $proxy->providerDataV2('buddy-works/repman')->get()); + $this->filesystem->write('packagist.org/p2/buddy-works/repman.json', (string) file_get_contents(__DIR__.'/../../Resources/packagist.org/p2/buddy-works/repman.json')); + self::assertFalse($this->filesystem->has('packagist.org/dist/buddy-works/repman/61e39aa8197cf1bc7fcb16a6f727b0c291bc9b76.zip')); + $distribution = $this->proxy->distribution('buddy-works/repman', '1.2.3', '61e39aa8197cf1bc7fcb16a6f727b0c291bc9b76', 'zip'); + self::assertTrue($distribution->isPresent()); } - public function testPackageProviderV2NotFound(): void + public function testDistRemove(): void { - $cache = new InMemoryCache(); - $cache->get('packagist.org/packages.json', function (): array {return []; }); - $proxy = new Proxy('packagist.org', 'https://packagist.org', new CacheableMetadataProvider(new FakeDownloader(), $cache), new InMemoryStorage()); + $this->filesystem->write('packagist.org/dist/vendor/package/some.zip', 'package-data'); + + $this->proxy->removeDist('vendor/package'); - self::assertTrue($proxy->providerDataV2('buddy-works/repman')->isEmpty()); + self::assertFalse($this->filesystem->has('packagist.org/dist/vendor/package')); + + // test if remove package that not exist does not cause error + $this->proxy->removeDist('vendor/package'); } - public function testStorageDownloadDistWhenNotExists(): void + public function testPreventRemoveDist(): void { - $distFilepath = __DIR__.'/../../Resources/packagist.org/dist/buddy-works/repman/0.1.2.0_f0c896a759d4e2e1eff57978318e841911796305.zip'; - /** @phpstan-var mixed $storage */ - $storage = $this->prophesize(Storage::class); - $storage->has(Argument::type(Dist::class))->willReturn(false); - $storage->filename(Argument::type(Dist::class))->willReturn($distFilepath); - $storage->download('https://api.github.com/repos/munusphp/munus/zipball/f0c896a759d4e2e1eff57978318e841911796305', Argument::type(Dist::class)) - ->shouldBeCalledOnce(); - - $proxy = new Proxy('packagist.org', 'https://packagist.org', new CacheableMetadataProvider(new FakeDownloader(), new InMemoryCache()), $storage->reveal()); - - self::assertStringContainsString( - '0.1.2.0_f0c896a759d4e2e1eff57978318e841911796305.zip', - $proxy->distFilename('buddy-works/repman', '0.1.2.0', 'f0c896a759d4e2e1eff57978318e841911796305', 'zip')->get() - ); + $this->expectException(\InvalidArgumentException::class); + + $this->proxy->removeDist(''); } - public function testReturnNoneWhenDistPackageNotExists(): void + public function testSyncMetadata(): void { - /** @phpstan-var mixed $storage */ - $storage = $this->prophesize(Storage::class); - $storage->has(Argument::type(Dist::class))->willReturn(false); - $storage->filename(Argument::type(Dist::class))->willReturn('/not/exist'); - $proxy = new Proxy('packagist.org', 'https://packagist.org', new CacheableMetadataProvider(new FakeDownloader(), new InMemoryCache()), $storage->reveal()); - - self::assertTrue(Option::none()->equals( - $proxy->distFilename('not-exist-vendor/not-exist-package', '0.1.2.0', 'f0c896a759d4e2e1eff57978318e841911796305', 'zip') - )); + $oldTimestamp = strtotime('2019-01-01 08:00:00'); + $this->filesystem->write('packagist.org/dist/some.json', 'content'); // should be ignored + $this->filesystem->write('packagist.org/p2/buddy-works/repman.json', 'content', ['timestamp' => $oldTimestamp]); + $this->filesystem->write('packagist.org/p2/buddy-works/old.json', 'content', ['timestamp' => $oldTimestamp]); + $this->downloader->addContent('https://packagist.org/p2/buddy-works/repman.json', 'new'); + $this->downloader->addContent('https://packagist.org/p2/buddy-works/old.json', 'content', $oldTimestamp); + + /** @var Metadata $metadata */ + $metadata = $this->proxy->metadata('buddy-works/repman')->get(); + self::assertEquals($oldTimestamp, $metadata->timestamp()); + + $this->proxy->syncMetadata(); + + /** @var Metadata $metadata */ + $metadata = $this->proxy->metadata('buddy-works/repman')->get(); + self::assertTrue($metadata->timestamp() > $oldTimestamp); + self:self::assertEquals('new', stream_get_contents($metadata->stream())); + + /** @var Metadata $metadata */ + $metadata = $this->proxy->metadata('buddy-works/old')->get(); + self::assertEquals($oldTimestamp, $metadata->timestamp()); } - public function testStorageNotForceToDownloadWhenDistExists(): void + public function testIgnoreSyncIfCannotDownload(): void { - /** @phpstan-var mixed $storage */ - $storage = $this->prophesize(Storage::class); - $storage->has(Argument::type(Dist::class))->willReturn(true); - $storage->download(Argument::cetera())->shouldNotBeCalled(); - $storage->filename(Argument::type(Dist::class))->willReturn( - __DIR__.'/../../Resources/packagist.org/dist/buddy-works/repman/0.1.2.0_f0c896a759d4e2e1eff57978318e841911796305.zip' - ); + $oldTimestamp = strtotime('2019-01-01 08:00:00'); + $this->filesystem->write('packagist.org/p2/buddy-works/repman.json', 'content', ['timestamp' => $oldTimestamp]); + $this->downloader->addContent('https://packagist.org/p2/buddy-works/repman.json', null); - $proxy = new Proxy('packagist.org', 'https://packagist.org', new FakeMetadataProvider(), $storage->reveal()); + $this->proxy->syncMetadata(); - self::assertStringContainsString( - '0.1.2.0_f0c896a759d4e2e1eff57978318e841911796305.zip', - $proxy->distFilename('buddy-works/repman', '0.1.2.0', 'f0c896a759d4e2e1eff57978318e841911796305', 'zip')->get() - ); + /** @var Metadata $metadata */ + $metadata = $this->proxy->metadata('buddy-works/repman')->get(); + self::assertEquals($oldTimestamp, $metadata->timestamp()); } - public function testStorageHandleDistWithSlashInVersion(): void + public function testSyncLegacyMetadata(): void { - $distFilepath = __DIR__.'/../../Resources/packagist.org/dist/buddy-works/repman/0cdaa0ab95de9fcf94ad9b1d2f80e15d_e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb.zip'; - /** @phpstan-var mixed $storage */ - $storage = $this->prophesize(Storage::class); - $storage->has(Argument::type(Dist::class))->willReturn(false); - $storage->filename(Argument::type(Dist::class))->willReturn($distFilepath); - $storage->download('https://api.github.com/repos/munusphp/munus/zipball/e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb', Argument::type(Dist::class)) - ->shouldBeCalledOnce(); - - $proxy = new Proxy('packagist.org', 'https://packagist.org', new CacheableMetadataProvider(new FakeDownloader(), new InMemoryCache()), $storage->reveal()); - - self::assertStringContainsString( - '0cdaa0ab95de9fcf94ad9b1d2f80e15d_e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb.zip', - $proxy->distFilename('buddy-works/repman', 'dev-feature/awesome', 'e738ed3634a11f6b5e23aca3d1c3f9be4efd8cfb', 'zip')->get() - ); - self::assertEquals('0cdaa0ab95de9fcf94ad9b1d2f80e15d', (new Dist('repo', 'package', 'dev-feature/awesome', 'ref', 'format'))->version()); + $oldTimestamp = strtotime('2019-01-01 08:00:00'); + $this->filesystem->write('packagist.org/p/buddy-works/repman.json', 'content', ['timestamp' => $oldTimestamp]); + $this->downloader->addContent('https://packagist.org/p/buddy-works/repman.json', 'new'); + + /** @var Metadata $metadata */ + $metadata = $this->proxy->legacyMetadata('buddy-works/repman')->get(); + self::assertEquals($oldTimestamp, $metadata->timestamp()); + + $this->proxy->syncMetadata(); + + /** @var Metadata $metadata */ + $metadata = $this->proxy->legacyMetadata('buddy-works/repman')->get(); + self::assertTrue($metadata->timestamp() > $oldTimestamp); } } diff --git a/tests/Unit/Service/RemoteFilesystem/NativeRemoteFilesystemTest.php b/tests/Unit/Service/RemoteFilesystem/NativeRemoteFilesystemTest.php index 04606dc9..8887be84 100644 --- a/tests/Unit/Service/RemoteFilesystem/NativeRemoteFilesystemTest.php +++ b/tests/Unit/Service/RemoteFilesystem/NativeRemoteFilesystemTest.php @@ -4,21 +4,21 @@ namespace Buddy\Repman\Tests\Unit\Service\RemoteFilesystem; -use Buddy\Repman\Service\Downloader\NativeDownloader; +use Buddy\Repman\Service\Downloader\ReactDownloader; use PHPUnit\Framework\TestCase; final class NativeRemoteFilesystemTest extends TestCase { public function testCorrectDownload(): void { - $rf = new NativeDownloader(); + $rf = new ReactDownloader(); self::assertFalse($rf->getContents(__FILE__)->isEmpty()); } public function testInCorrectDownload(): void { - $rf = new NativeDownloader(); + $rf = new ReactDownloader(); self::assertTrue($rf->getContents('/not/exits')->isEmpty()); } diff --git a/tests/Unit/Service/StreamTest.php b/tests/Unit/Service/StreamTest.php new file mode 100644 index 00000000..e4f10015 --- /dev/null +++ b/tests/Unit/Service/StreamTest.php @@ -0,0 +1,18 @@ +expectException(\RuntimeException::class); + + Stream::fromString('string', 'php://invalid'); + } +}