From f79d63be1b91334e12b2cd7c53078fa6111a0c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Thu, 6 Apr 2017 16:27:18 +0200 Subject: [PATCH 01/42] WIP --- app/AppKernel.php | 2 + app/Resources/assets/style/style.scss | 1 - .../views/ldraw/model/detail.html.twig | 7 +- app/Resources/views/macros/elements.html.twig | 2 +- app/autoload.php | 2 +- app/config/config.yml | 17 +- app/config/parameters.yml.dist | 5 +- app/config/routing.yml | 2 + app/config/service/loader.yml | 4 +- app/config/service/manager.yml | 4 - app/config/service/service.yml | 17 +- composer.json | 4 +- composer.lock | 331 +++++++++++++++++- .../Command/LoadLDRawLibraryCommand.php | 10 +- src/AppBundle/Controller/MediaController.php | 41 +-- src/AppBundle/Entity/LDraw/Model.php | 23 -- src/AppBundle/Entity/LDraw/Type.php | 91 ----- .../Exception/FileNotFoundException.php | 15 + .../Imagine/Loader/RemoteStreamLoader.php | 22 ++ src/AppBundle/Manager/LDraw/TypeManager.php | 37 -- src/AppBundle/Manager/LDrawManager.php | 15 +- src/AppBundle/Manager/RebrickableManager.php | 18 + .../Repository/LDraw/PartRepository.php | 9 - src/AppBundle/Service/LDViewService.php | 33 +- .../Service/Loader/LDrawLoaderService.php | 132 +++---- src/AppBundle/Service/ZipService.php | 8 + .../LDraw/CategoryControllerTest.php | 25 ++ .../Controller/LDraw/PartControllerTest.php | 25 ++ src/AppBundle/Twig/AppExtension.php | 35 ++ src/AppBundle/Utils/DatParser.php | 150 ++++---- var/SymfonyRequirements.php | 6 +- web/config.php | 2 +- 32 files changed, 679 insertions(+), 416 deletions(-) delete mode 100644 src/AppBundle/Entity/LDraw/Type.php create mode 100644 src/AppBundle/Exception/FileNotFoundException.php create mode 100644 src/AppBundle/Imagine/Loader/RemoteStreamLoader.php delete mode 100644 src/AppBundle/Manager/LDraw/TypeManager.php create mode 100644 src/AppBundle/Manager/RebrickableManager.php delete mode 100644 src/AppBundle/Repository/LDraw/PartRepository.php create mode 100644 src/AppBundle/Service/ZipService.php create mode 100644 src/AppBundle/Tests/Controller/LDraw/CategoryControllerTest.php create mode 100644 src/AppBundle/Tests/Controller/LDraw/PartControllerTest.php create mode 100644 src/AppBundle/Twig/AppExtension.php diff --git a/app/AppKernel.php b/app/AppKernel.php index 0718d4f..286cdd6 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -19,7 +19,9 @@ class AppKernel extends Kernel new Knp\Bundle\MenuBundle\KnpMenuBundle(), new Oneup\FlysystemBundle\OneupFlysystemBundle(), new Knp\Bundle\PaginatorBundle\KnpPaginatorBundle(), + new Knp\Bundle\GaufretteBundle\KnpGaufretteBundle(), new Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle(), + new Liip\ImagineBundle\LiipImagineBundle(), ]; if (in_array($this->getEnvironment(), ['dev', 'test'], true)) { diff --git a/app/Resources/assets/style/style.scss b/app/Resources/assets/style/style.scss index 263739b..2cd9f12 100644 --- a/app/Resources/assets/style/style.scss +++ b/app/Resources/assets/style/style.scss @@ -1,4 +1,3 @@ @import "variables"; @import "main"; - diff --git a/app/Resources/views/ldraw/model/detail.html.twig b/app/Resources/views/ldraw/model/detail.html.twig index 5495401..35cd79b 100644 --- a/app/Resources/views/ldraw/model/detail.html.twig +++ b/app/Resources/views/ldraw/model/detail.html.twig @@ -8,7 +8,6 @@
number:
{{ model.number }}
name:
{{ model.name }}
category:
{{ model.category ? model.category.name }}
-
type:
{{ model.type ? model.type.name }}
model:
{{ model.path }}
author:
{{ model.author }}
keywords:
@@ -24,7 +23,7 @@ {% endfor %}
Download:
-
{{ model.number }}
+
{{ model.number }}
rebrickable parts ({{ rbParts|length }}):

@@ -42,7 +41,7 @@

- +

@@ -78,7 +77,7 @@ window.onload = function() { modelView = new ModelViewer(); var scene = modelView.initScene($('#model')); - modelView.loadStl('{{ path('model_stl', {'number' : model.number })}}'); + modelView.loadStl('{{ url('media_file', {'path': model.path }) }}'); modelView.render(); }; diff --git a/app/Resources/views/macros/elements.html.twig b/app/Resources/views/macros/elements.html.twig index b6df6ff..611b7ec 100644 --- a/app/Resources/views/macros/elements.html.twig +++ b/app/Resources/views/macros/elements.html.twig @@ -1,6 +1,6 @@ {% macro part(model) %}
- +

{{ model.number }}

{% endmacro %} diff --git a/app/autoload.php b/app/autoload.php index 31321fa..c92a7cc 100644 --- a/app/autoload.php +++ b/app/autoload.php @@ -1,7 +1,7 @@ =5.3.2" + }, + "require-dev": { + "sami/sami": "dev-master" + }, + "suggest": { + "ext-gd": "to use the GD implementation", + "ext-gmagick": "to use the Gmagick implementation", + "ext-imagick": "to use the Imagick implementation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.7-dev" + } + }, + "autoload": { + "psr-0": { + "Imagine": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bulat Shakirzyanov", + "email": "mallluhuct@gmail.com", + "homepage": "http://avalanche123.com" + } + ], + "description": "Image processing for PHP 5.3", + "homepage": "http://imagine.readthedocs.org/", + "keywords": [ + "drawing", + "graphics", + "image manipulation", + "image processing" + ], + "time": "2015-09-19T16:54:05+00:00" + }, { "name": "incenteev/composer-parameter-handler", "version": "v2.1.2", @@ -1053,6 +1110,94 @@ ], "time": "2014-01-12T16:20:24+00:00" }, + { + "name": "knplabs/gaufrette", + "version": "v0.3.1", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/Gaufrette.git", + "reference": "771ad16f4b2e7f9d35f44b201956e83c6fbf5dde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/Gaufrette/zipball/771ad16f4b2e7f9d35f44b201956e83c6fbf5dde", + "reference": "771ad16f4b2e7f9d35f44b201956e83c6fbf5dde", + "shasum": "" + }, + "require": { + "php": ">=5.4" + }, + "conflict": { + "microsoft/windowsazure": "<0.4.3" + }, + "require-dev": { + "amazonwebservices/aws-sdk-for-php": "1.5.*", + "aws/aws-sdk-php": "^2.4.12", + "doctrine/dbal": ">=2.3", + "dropbox-php/dropbox-php": "*", + "google/apiclient": "~1.1.3", + "herzult/php-ssh": "*", + "league/flysystem": "~1.0", + "mikey179/vfsstream": "~1.2.0", + "phpseclib/phpseclib": "^2.0", + "phpspec/phpspec": "~2.4", + "phpunit/phpunit": "3.7.*", + "rackspace/php-opencloud": "^1.9.2" + }, + "suggest": { + "amazonwebservices/aws-sdk-for-php": "to use the legacy Amazon S3 adapters", + "aws/aws-sdk-php": "to use the Amazon S3 adapter", + "doctrine/dbal": "to use the Doctrine DBAL adapter", + "dropbox-php/dropbox-php": "to use the Dropbox adapter", + "ext-apc": "to use the APC adapter", + "ext-curl": "*", + "ext-fileinfo": "This extension is used to automatically detect the content-type of a file in the AwsS3, OpenCloud, AzureBlogStorage and GoogleCloudStorage adapters", + "ext-mbstring": "*", + "ext-mongo": "*", + "ext-zip": "to use the Zip adapter", + "google/apiclient": "to use GoogleCloudStorage adapter", + "herzult/php-ssh": "to use SFtp adapter", + "knplabs/knp-gaufrette-bundle": "to use with Symfony2", + "league/flysystem": "to use Flysystem adapters", + "microsoft/windowsazure": "to use Microsoft Azure Blob Storage adapter", + "phpseclib/phpseclib": "to use PhpseclibSftp adapter", + "rackspace/php-opencloud": "to use Opencloud adapter" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.4.x-dev" + } + }, + "autoload": { + "psr-0": { + "Gaufrette": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "The contributors", + "homepage": "http://github.com/knplabs/Gaufrette/contributors" + }, + { + "name": "KnpLabs Team", + "homepage": "http://knplabs.com" + } + ], + "description": "PHP5 library that provides a filesystem abstraction layer", + "homepage": "http://knplabs.com", + "keywords": [ + "abstraction", + "file", + "filesystem", + "media" + ], + "time": "2017-03-20T01:23:34+00:00" + }, { "name": "knplabs/knp-components", "version": "1.3.4", @@ -1124,6 +1269,64 @@ ], "time": "2016-12-06T18:10:24+00:00" }, + { + "name": "knplabs/knp-gaufrette-bundle", + "version": "v0.4.0", + "source": { + "type": "git", + "url": "https://github.com/KnpLabs/KnpGaufretteBundle.git", + "reference": "06d91a8a575773cd0361c1246c9c499b6bdd5d68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/KnpLabs/KnpGaufretteBundle/zipball/06d91a8a575773cd0361c1246c9c499b6bdd5d68", + "reference": "06d91a8a575773cd0361c1246c9c499b6bdd5d68", + "shasum": "" + }, + "require": { + "knplabs/gaufrette": "~0.1.7|~0.2|~0.3", + "symfony/framework-bundle": "~2.0|~3.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.2", + "symfony/console": "~2.0|~3.0", + "symfony/yaml": "~2.0|~3.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "0.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Knp\\Bundle\\GaufretteBundle\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "The contributors", + "homepage": "https://github.com/knplabs/KnpGaufretteBundle/contributors" + }, + { + "name": "Antoine Hérault", + "email": "antoine.herault@gmail.com" + } + ], + "description": "Allows to easily use the Gaufrette library in a Symfony project", + "homepage": "http://knplabs.com", + "keywords": [ + "abstraction", + "file", + "filesystem", + "media" + ], + "time": "2017-03-16T21:01:25+00:00" + }, { "name": "knplabs/knp-menu", "version": "2.2.0", @@ -1394,16 +1597,16 @@ }, { "name": "lexik/form-filter-bundle", - "version": "v5.0.3", + "version": "v5.0.4", "source": { "type": "git", "url": "https://github.com/lexik/LexikFormFilterBundle.git", - "reference": "124a6c8e9eb109e7616a18d916bbc33137bb308d" + "reference": "28c09d6d9f278875ca9648c4b66eeb457c37d3b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lexik/LexikFormFilterBundle/zipball/124a6c8e9eb109e7616a18d916bbc33137bb308d", - "reference": "124a6c8e9eb109e7616a18d916bbc33137bb308d", + "url": "https://api.github.com/repos/lexik/LexikFormFilterBundle/zipball/28c09d6d9f278875ca9648c4b66eeb457c37d3b6", + "reference": "28c09d6d9f278875ca9648c4b66eeb457c37d3b6", "shasum": "" }, "require": { @@ -1444,13 +1647,113 @@ "description": "This bundle aim to provide classes to build some form filters and then build a doctrine query from this form filter.", "homepage": "https://github.com/lexik/LexikFormFilterBundle", "keywords": [ - "Symfony2", "bundle", "doctrine", "filter", - "form" + "form", + "symfony" ], - "time": "2017-01-24T13:03:45+00:00" + "time": "2017-03-27T07:28:34+00:00" + }, + { + "name": "liip/imagine-bundle", + "version": "1.7.4", + "source": { + "type": "git", + "url": "https://github.com/liip/LiipImagineBundle.git", + "reference": "105dd9c3446e3eb44e33161d4e636a3abafb6d7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/liip/LiipImagineBundle/zipball/105dd9c3446e3eb44e33161d4e636a3abafb6d7f", + "reference": "105dd9c3446e3eb44e33161d4e636a3abafb6d7f", + "shasum": "" + }, + "require": { + "imagine/imagine": "^0.6.3,<0.7", + "php": "^5.3.9|^7.0", + "symfony/asset": "~2.3|~3.0", + "symfony/filesystem": "~2.3|~3.0", + "symfony/finder": "~2.3|~3.0", + "symfony/framework-bundle": "~2.3|~3.0", + "symfony/options-resolver": "~2.3|~3.0", + "symfony/process": "~2.3|~3.0", + "symfony/templating": "~2.3|~3.0", + "symfony/translation": "~2.3|~3.0" + }, + "require-dev": { + "amazonwebservices/aws-sdk-for-php": "~1.0", + "aws/aws-sdk-php": "~2.4", + "doctrine/cache": "~1.1", + "doctrine/orm": "~2.3", + "ext-gd": "*", + "friendsofphp/php-cs-fixer": "~2.0", + "phpunit/phpunit": "~4.3|~5.0", + "psr/log": "~1.0", + "satooshi/php-coveralls": "~1.0", + "sllh/php-cs-fixer-styleci-bridge": "~2.1", + "symfony/browser-kit": "~2.3|~3.0", + "symfony/console": "~2.3|~3.0", + "symfony/dependency-injection": "~2.3|~3.0", + "symfony/form": "~2.3|~3.0", + "symfony/phpunit-bridge": "~2.3|~3.0", + "symfony/validator": "~2.3|~3.0", + "symfony/yaml": "~2.3|~3.0", + "twig/twig": "~1.12|~2.0" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "required on PHP >= 7.0 to use mongo components with mongodb extension", + "amazonwebservices/aws-sdk-for-php": "required to use AWS version 1 cache resolver", + "aws/aws-sdk-php": "required to use AWS version 2/3 cache resolver", + "doctrine/mongodb-odm": "required to use mongodb-backed doctrine components", + "ext-exif": "required to read EXIF metadata from images", + "ext-gd": "required to use gd driver", + "ext-gmagick": "required to use gmagick driver", + "ext-imagick": "required to use imagick driver", + "ext-mongo": "required for mongodb components on PHP <7.0", + "ext-mongodb": "required for mongodb components on PHP >=7.0", + "league/flysystem": "required to use FlySystem data loader or cache resolver", + "monolog/monolog": "A psr/log compatible logger is required to enable logging", + "twig/twig": "required to use the provided Twig extension. Version 1.12 or greater needed" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-1.0": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "Liip\\ImagineBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Liip and other contributors", + "homepage": "https://github.com/liip/LiipImagineBundle/contributors" + } + ], + "description": "This bundle provides an image manipulation abstraction toolkit for Symfony-based projects.", + "homepage": "http://liip.ch", + "keywords": [ + "bundle", + "image", + "imagine", + "liip", + "manipulation", + "photos", + "pictures", + "symfony", + "transformation" + ], + "time": "2017-03-02T20:18:55+00:00" }, { "name": "monolog/monolog", @@ -1532,16 +1835,16 @@ }, { "name": "oneup/flysystem-bundle", - "version": "1.11.0", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/1up-lab/OneupFlysystemBundle.git", - "reference": "a68f83415e3af2313c529be6b22bfddfcfe8e90f" + "reference": "2addd1077360790a7722fef09388a003576d585c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/1up-lab/OneupFlysystemBundle/zipball/a68f83415e3af2313c529be6b22bfddfcfe8e90f", - "reference": "a68f83415e3af2313c529be6b22bfddfcfe8e90f", + "url": "https://api.github.com/repos/1up-lab/OneupFlysystemBundle/zipball/2addd1077360790a7722fef09388a003576d585c", + "reference": "2addd1077360790a7722fef09388a003576d585c", "shasum": "" }, "require": { @@ -1562,6 +1865,7 @@ "league/flysystem-ziparchive": "~1.0", "litipk/flysystem-fallback-adapter": "~0.1", "phpunit/phpunit": "^4.4", + "superbalist/flysystem-google-storage": "~4.0", "symfony/asset": "~2.0|~3.0", "symfony/browser-kit": "~2.0|~3.0", "symfony/finder": "~2.0|~3.0", @@ -1583,6 +1887,7 @@ "league/flysystem-webdav": "Allows you to use WebDAV storage", "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", "litipk/flysystem-fallback-adapter": "Allows you to use a fallback filesystem", + "superbalist/flysystem-google-storage": "Allows you to use Google Cloud Storage buckets", "twistor/flysystem-stream-wrapper": "Allows you to use stream wrapper" }, "type": "symfony-bundle", @@ -1611,7 +1916,7 @@ "abstraction", "filesystem" ], - "time": "2017-03-03T13:41:53+00:00" + "time": "2017-03-27T08:48:46+00:00" }, { "name": "paragonie/random_compat", diff --git a/src/AppBundle/Command/LoadLDRawLibraryCommand.php b/src/AppBundle/Command/LoadLDRawLibraryCommand.php index 0d94f07..f46823b 100644 --- a/src/AppBundle/Command/LoadLDRawLibraryCommand.php +++ b/src/AppBundle/Command/LoadLDRawLibraryCommand.php @@ -15,7 +15,7 @@ class LoadLDRawLibraryCommand extends ContainerAwareCommand ->setName('app:load:ldraw') ->setDescription('Loads LDraw library parts') ->setHelp('This command allows you to..') - ->addArgument('ldraw_path', InputArgument::OPTIONAL, 'Path to LDraw library folder'); + ->addArgument('file', InputArgument::OPTIONAL, 'Model to load'); } protected function execute(InputInterface $input, OutputInterface $output) @@ -27,11 +27,11 @@ class LoadLDRawLibraryCommand extends ContainerAwareCommand try { // TODO handle relative path to dir - if (($ldrawPath = $input->getArgument('ldraw_path')) == null) { - $ldrawPath = $ldrawLoader->downloadLibrary(); + if (($ldrawPath = $input->getArgument('file')) != null) { + $ldrawPath = $ldrawLoader->loadModel($ldrawPath); + } else { + $ldrawLoader->loadAllModels(); } - - $ldrawLoader->loadData($ldrawPath); } catch (\Exception $e) { printf($e->getMessage()); } diff --git a/src/AppBundle/Controller/MediaController.php b/src/AppBundle/Controller/MediaController.php index 83923e3..ea25151 100644 --- a/src/AppBundle/Controller/MediaController.php +++ b/src/AppBundle/Controller/MediaController.php @@ -8,6 +8,7 @@ use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Routing\Annotation\Route; /** @@ -16,54 +17,28 @@ use Symfony\Component\Routing\Annotation\Route; class MediaController extends Controller { /** - * @Route("/model/stl/{number}", name="model_stl") + * @Route("/{path}", name="media_file", requirements={"path"=".+"}) * * @return Response */ - public function stlAction(Model $model) + public function stlAction($path) { $mediaFilesystem = $this->get('oneup_flysystem.media_filesystem'); - if ($mediaFilesystem->has($model->getPath())) { - $response = new BinaryFileResponse($mediaFilesystem->getAdapter()->getPathPrefix().DIRECTORY_SEPARATOR.$model->getPath()); - $response->headers->set('Content-Type', 'application/vnd.ms-pki.stl'); + if ($mediaFilesystem->has($path)) { + $response = new BinaryFileResponse($mediaFilesystem->getAdapter()->getPathPrefix().DIRECTORY_SEPARATOR.$path); + $response->headers->set('Content-Type', $mediaFilesystem->getMimetype($path)); // Create the disposition of the file $disposition = $response->headers->makeDisposition( ResponseHeaderBag::DISPOSITION_ATTACHMENT, - $model->getNumber().'.stl' + basename($path) ); $response->headers->set('Content-Disposition', $disposition); return $response; } - throw new FileNotFoundException($model->getPath()); - } - - /** - * @Route("/model/image/{number}", name="model_image") - * - * @return Response - */ - public function imageAction(Model $model) - { - $mediaFilesystem = $this->get('oneup_flysystem.media_filesystem'); - - if ($mediaFilesystem->has('ldraw'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.$model->getNumber().'.png')) { - $response = new BinaryFileResponse($mediaFilesystem->getAdapter()->getPathPrefix().DIRECTORY_SEPARATOR.'ldraw'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.$model->getNumber().'.png'); - $response->headers->set('Content-Type', 'image/png'); - - // Create the disposition of the file - $disposition = $response->headers->makeDisposition( - ResponseHeaderBag::DISPOSITION_ATTACHMENT, - $model->getNumber().'png' - ); - - $response->headers->set('Content-Disposition', $disposition); - - return $response; - } - throw new FileNotFoundException($model->getNumber().'png'); + throw new FileNotFoundException($path); } } diff --git a/src/AppBundle/Entity/LDraw/Model.php b/src/AppBundle/Entity/LDraw/Model.php index fb7e90a..a761fa6 100644 --- a/src/AppBundle/Entity/LDraw/Model.php +++ b/src/AppBundle/Entity/LDraw/Model.php @@ -17,13 +17,6 @@ class Model { use NumberTrait; - /** - * @var Type - * - * @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Type", inversedBy="models", cascade={"persist"}) - */ - private $type; - /** * @var string * @@ -163,22 +156,6 @@ class Model return $this; } - /** - * @return string - */ - public function getType() - { - return $this->type; - } - - /** - * @param string $type - */ - public function setType($type) - { - $this->type = $type; - } - /** * @return string */ diff --git a/src/AppBundle/Entity/LDraw/Type.php b/src/AppBundle/Entity/LDraw/Type.php deleted file mode 100644 index c33ea34..0000000 --- a/src/AppBundle/Entity/LDraw/Type.php +++ /dev/null @@ -1,91 +0,0 @@ -models = new ArrayCollection(); - } - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @param string $name - */ - public function setName($name) - { - $this->name = $name; - } - - /** - * Get models. - * - * @return ArrayCollection - */ - public function getModels() - { - return $this->models; - } - - /** - * @param Model $model - * - * @return Type - */ - public function addModel(Model $model) - { - $this->models->add($model); - - return $this; - } - - /** - * @param Model $model - * - * @return Type - */ - public function removeModel(Model $model) - { - $this->models->remove($model); - - return $this; - } -} diff --git a/src/AppBundle/Exception/FileNotFoundException.php b/src/AppBundle/Exception/FileNotFoundException.php new file mode 100644 index 0000000..dbacf85 --- /dev/null +++ b/src/AppBundle/Exception/FileNotFoundException.php @@ -0,0 +1,15 @@ +repository = $repository; - } - - /** - * Create new Keyword entity with $name or retrieve one. - * - * @param $name - * - * @return Type - */ - public function create($name) - { - if (($type = $this->repository->findByName($name)) == null) { - $type = new Type(); - $type->setName($name); - } - - return $type; - } -} diff --git a/src/AppBundle/Manager/LDrawManager.php b/src/AppBundle/Manager/LDrawManager.php index e452f93..84fb05b 100644 --- a/src/AppBundle/Manager/LDrawManager.php +++ b/src/AppBundle/Manager/LDrawManager.php @@ -17,9 +17,6 @@ class LDrawManager /** @var KeywordManager */ private $keywordManager; - /** @var TypeManager */ - private $typeManager; - /** @var SubpartManager */ private $subpartManager; @@ -34,16 +31,14 @@ class LDrawManager * * @param CategoryManager $categoryManager * @param KeywordManager $keywordManager - * @param TypeManager $typeManager * @param SubpartManager $subpartManager * @param ModelManager $modelManager * @param AliasManager $aliasManager */ - public function __construct(CategoryManager $categoryManager, KeywordManager $keywordManager, TypeManager $typeManager, SubpartManager $subpartManager, ModelManager $modelManager, AliasManager $aliasManager) + public function __construct(CategoryManager $categoryManager, KeywordManager $keywordManager, SubpartManager $subpartManager, ModelManager $modelManager, AliasManager $aliasManager) { $this->categoryManager = $categoryManager; $this->keywordManager = $keywordManager; - $this->typeManager = $typeManager; $this->subpartManager = $subpartManager; $this->modelManager = $modelManager; $this->aliasManager = $aliasManager; @@ -65,14 +60,6 @@ class LDrawManager return $this->keywordManager; } - /** - * @return TypeManager - */ - public function getTypeManager() - { - return $this->typeManager; - } - /** * @return SubpartManager */ diff --git a/src/AppBundle/Manager/RebrickableManager.php b/src/AppBundle/Manager/RebrickableManager.php new file mode 100644 index 0000000..9896d53 --- /dev/null +++ b/src/AppBundle/Manager/RebrickableManager.php @@ -0,0 +1,18 @@ +ldview = $ldview; $this->mediaFilesystem = $mediaFilesystem; + $this->ldrawLibraryFilesystem = $ldrawLibraryFilesystem; } /** * @param Filesystem $ldrawFilesystem */ - public function setLdrawFilesystem($ldrawFilesystem) + public function setLdrawFilesystem($ldrawLibraryFilesystem) { - $this->ldrawFilesystem = $ldrawFilesystem; + $this->ldrawLibraryFilesystem = $ldrawLibraryFilesystem; } /** @@ -53,18 +56,18 @@ class LDViewService * * @return File */ - public function datToStl($file, $LDrawDir = null) + public function datToStl($file) { if (!$this->mediaFilesystem->has('ldraw'.DIRECTORY_SEPARATOR.'models')) { $this->mediaFilesystem->createDir('ldraw'.DIRECTORY_SEPARATOR.'models'); } - $newFile = 'ldraw'.DIRECTORY_SEPARATOR.'models'.DIRECTORY_SEPARATOR.$file['filename'].'.stl'; + $newFile = 'ldraw'.DIRECTORY_SEPARATOR.'models'.DIRECTORY_SEPARATOR.basename($file,'.dat').'.stl'; - if (!$this->mediaFilesystem->has($newFile)) { + if (!file_exists($newFile)) { $this->runLDView([ - $this->ldrawFilesystem->getAdapter()->getPathPrefix().$file['path'], - '-LDrawDir='.$this->ldrawFilesystem->getAdapter()->getPathPrefix(), + $file, + '-LDrawDir='.$this->ldrawLibraryFilesystem->getAdapter()->getPathPrefix(), '-ExportFiles=1', '-ExportSuffix=.stl', '-ExportsDir='.$this->mediaFilesystem->getAdapter()->getPathPrefix().'ldraw'.DIRECTORY_SEPARATOR.'models', @@ -87,18 +90,18 @@ class LDViewService * * @return File */ - public function datToPng($file, $LDrawDir = null) + public function datToPng($file) { if (!$this->mediaFilesystem->has('ldraw'.DIRECTORY_SEPARATOR.'images')) { $this->mediaFilesystem->createDir('ldraw'.DIRECTORY_SEPARATOR.'images'); } - $newFile = 'ldraw'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.$file['filename'].'.png'; + $newFile = 'ldraw'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.basename($file,'.dat').'.png'; if (!$this->mediaFilesystem->has($newFile)) { $this->runLDView([ - $this->ldrawFilesystem->getAdapter()->getPathPrefix().$file['path'], - '-LDrawDir='.$this->ldrawFilesystem->getAdapter()->getPathPrefix(), + $file, + '-LDrawDir='.$this->ldrawLibraryFilesystem->getAdapter()->getPathPrefix(), '-AutoCrop=1', '-SaveAlpha=0', '-SnapshotSuffix=.png', diff --git a/src/AppBundle/Service/Loader/LDrawLoaderService.php b/src/AppBundle/Service/Loader/LDrawLoaderService.php index 402274f..bbf08cd 100644 --- a/src/AppBundle/Service/Loader/LDrawLoaderService.php +++ b/src/AppBundle/Service/Loader/LDrawLoaderService.php @@ -5,8 +5,8 @@ namespace AppBundle\Service\Loader; use AppBundle\Entity\LDraw\Category; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\LDraw\Type; +use AppBundle\Exception\FileNotFoundException; use AppBundle\Manager\LDrawManager; -use AppBundle\Service\LDrawService; use AppBundle\Service\LDViewService; use AppBundle\Utils\DatParser; use League\Flysystem\Adapter\Local; @@ -22,7 +22,7 @@ class LDrawLoaderService extends BaseLoaderService /** * @var Filesystem */ - private $ldraw; + private $ldrawLibraryFilesystem; /** * @var string download URL with current LDraw library @@ -41,67 +41,35 @@ class LDrawLoaderService extends BaseLoaderService private $datParser; /** - * @param array $ldraw_url + * LDrawLoaderService constructor. + * @param Filesystem $ldrawLibraryFilesystem + * @param LDViewService $LDViewService + * @param $ldraw_url + * @param LDrawManager $ldrawService + * @param DatParser $datParser */ - public function __construct(LDViewService $LDViewService, $ldraw_url, LDrawManager $ldrawService, $datParser) + public function __construct($ldrawLibraryFilesystem, $LDViewService, $ldraw_url, $ldrawService, $datParser) { $this->LDViewService = $LDViewService; $this->ldraw_url = $ldraw_url; $this->ldrawService = $ldrawService; $this->datParser = $datParser; + $this->ldrawLibraryFilesystem = $ldrawLibraryFilesystem; } - /** - * Download current LDraw library and extract it to system tmp directory. - * - * @return string Absolute path to temporary Ldraw library - */ - public function downloadLibrary() + public function loadAllModels() { - $temp = $this->downloadFile($this->ldraw_url); - - // Create unique temporary directory - $temp_dir = tempnam(sys_get_temp_dir(), 'printabrick.'); - mkdir($temp_dir); - - // Unzip downloaded library zip file to temporary directory - $zip = new \ZipArchive(); - if ($zip->open($temp) != 'true') { - throw new LogicException('Error :- Unable to open the Zip File'); - } - $zip->extractTo($temp_dir); - $zip->close(); - - // Unlink downloaded zip file - unlink($temp); - - return $temp_dir; - } - - /** - * @param $LDrawLibrary - */ - public function loadData($LDrawLibrary) - { - $adapter = new Local($LDrawLibrary); - $this->ldraw = new Filesystem($adapter); - - $this->LDViewService->setLdrawFilesystem($this->ldraw); - - $this->loadParts(); - } - - // TODO refactor - - public function loadParts() - { - $files = $this->ldraw->get('parts')->getContents(); + $files = $this->ldrawLibraryFilesystem->get('parts')->getContents(); + $modelManager = $this->ldrawService->getModelManager(); $this->initProgressBar(count($files)); foreach ($files as $file) { if ($file['type'] == 'file' && $file['extension'] == 'dat') { - $this->loadModel($file); + $model = $this->loadModel($this->ldrawLibraryFilesystem->getAdapter()->getPathPrefix().$file['path']); + + if($model) + $modelManager->getRepository()->save($model); } $this->progressBar->advance(); @@ -116,31 +84,32 @@ class LDrawLoaderService extends BaseLoaderService * * @return Model|null */ - private function loadModel($file) + public function loadModel($file) { $modelManager = $this->ldrawService->getModelManager(); $subpartManager = $this->ldrawService->getSubpartManager(); $aliasManager = $this->ldrawService->getAliasManager(); - $header = $this->datParser->parse($this->ldraw->get($file['path'])); + if($model = $modelManager->findByNumber(basename($file,'.dat'))) { + return $model; + } + + $header = $this->datParser->parse($file); if ($this->isModelIncluded($header)) { - if (isset($header['parent']) && ($parent = $this->getModelParent($header['parent'])) && ($parentFile = $this->getModelFile($parent))) { - $parentHeader = $this->datParser->parse($parentFile); + if (isset($header['parent']) && ($parent = $this->getModelParent($header['parent'])) && ($parentFile = $this->findModelFile($parent))) { + $parentModel = $this->loadModel($parentFile); - if ($this->isModelIncluded($parentHeader)) { - $model = $modelManager->create($parentHeader['id']); - $alias = $aliasManager->create($header['id'], $model); + if ($parentModel) { + $alias = $aliasManager->create($header['id'], $parentModel); $aliasManager->getRepository()->save($alias); - - return $model; + $this->progressBar->advance(); } + return $parentModel; } else { $model = $modelManager->create($header['id']); $model->setName($header['name']); - $model - ->setCategory($this->ldrawService->getCategoryManager()->create($header['category'])) - ->setType($this->ldrawService->getTypeManager()->create($header['type'])); + $model->setCategory($this->ldrawService->getCategoryManager()->create($header['category'])); if (isset($header['keywords'])) { foreach ($header['keywords'] as $keyword) { @@ -150,20 +119,19 @@ class LDrawLoaderService extends BaseLoaderService } if (isset($header['subparts'])) { - $model->setType($this->ldrawService->getTypeManager()->create('Shortcut')); + foreach ($header['subparts'] as $subpartId) { + $subpartId = $this->getModelParent($subpartId); - foreach ($header['subparts'] as $subpart) { - $subpart = $this->getModelParent($subpart); + $subModel = $modelManager->getRepository()->findOneBy(['number'=>$subpartId]); - if ($subpartFile = $this->getModelFile($subpart)) { - $subpartHeader = $this->datParser->parse($subpartFile); + if(!$subModel && ($subpartFile = $this->findModelFile($subpartId)) != null) { + $subModel = $this->loadModel($subpartFile); + } - if ($this->isModelIncluded($subpartHeader)) { - $subpartModel = $modelManager->create($subpartHeader['id']); - - $subpart = $subpartManager->create($model, $subpartModel); - $subpartManager->getRepository()->save($subpart); - } + if ($subModel) { + $subpart = $subpartManager->create($model, $subModel); + $subpartManager->getRepository()->save($subpart); + $this->progressBar->advance(); } } } @@ -172,13 +140,9 @@ class LDrawLoaderService extends BaseLoaderService $model->setModified($header['modified']); $model->setPath($this->loadStlModel($file)); - $modelManager->getRepository()->save($model); - return $model; } } - - return null; } /** @@ -186,13 +150,13 @@ class LDrawLoaderService extends BaseLoaderService * * @param $id * - * @return \League\Flysystem\Directory|File|\League\Flysystem\Handler|null + * @return string */ - private function getModelFile($id) + private function findModelFile($id) { $path = 'parts/'.strtolower($id).'.dat'; - if ($this->ldraw->has($path)) { - return $this->ldraw->get($path); + if ($this->ldrawLibraryFilesystem->has($path)) { + return $this->ldrawLibraryFilesystem->getAdapter()->getPathPrefix().$path; } return null; @@ -207,7 +171,7 @@ class LDrawLoaderService extends BaseLoaderService */ private function getModelParent($number) { - if (($file = $this->getModelFile($number)) == null) { + if (($file = $this->findModelFile($number)) == null) { return $number; } @@ -216,7 +180,7 @@ class LDrawLoaderService extends BaseLoaderService do { $parent = isset($header['parent']) ? $header['parent'] : null; - if ($file = $this->getModelFile($parent)) { + if ($file = $this->findModelFile($parent)) { $header = $this->datParser->parse($file); } else { break; @@ -240,7 +204,7 @@ class LDrawLoaderService extends BaseLoaderService strpos($header['id'], 's') !== 0 && $header['type'] != 'Subpart' && $header['type'] != 'Sticker' - && !(($header['type'] == 'Printed') && $this->getModelFile($header['parent'])) + && !(($header['type'] == 'Printed') && $this->findModelFile($header['parent'])) ) { return true; } @@ -260,7 +224,7 @@ class LDrawLoaderService extends BaseLoaderService private function loadStlModel($file) { try { - return $this->LDViewService->datToStl($file, $this->ldraw)->getPath(); + return $this->LDViewService->datToStl($file)->getPath(); } catch (\Exception $e) { throw $e; //TODO } diff --git a/src/AppBundle/Service/ZipService.php b/src/AppBundle/Service/ZipService.php new file mode 100644 index 0000000..7be9188 --- /dev/null +++ b/src/AppBundle/Service/ZipService.php @@ -0,0 +1,8 @@ +request('GET', '/ldraw_category/'); + $this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET /ldraw_category/"); + + // Go to the show view + $crawler = $client->click($crawler->selectLink('show')->link()); + $this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code"); + } + + */ +} diff --git a/src/AppBundle/Tests/Controller/LDraw/PartControllerTest.php b/src/AppBundle/Tests/Controller/LDraw/PartControllerTest.php new file mode 100644 index 0000000..4a1b64f --- /dev/null +++ b/src/AppBundle/Tests/Controller/LDraw/PartControllerTest.php @@ -0,0 +1,25 @@ +request('GET', '/ldraw_part/'); + $this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code for GET /ldraw_part/"); + + // Go to the show view + $crawler = $client->click($crawler->selectLink('show')->link()); + $this->assertEquals(200, $client->getResponse()->getStatusCode(), "Unexpected HTTP status code"); + } + + */ +} diff --git a/src/AppBundle/Twig/AppExtension.php b/src/AppBundle/Twig/AppExtension.php new file mode 100644 index 0000000..326e233 --- /dev/null +++ b/src/AppBundle/Twig/AppExtension.php @@ -0,0 +1,35 @@ +rebrickableAPIManager = $rebrickableAPIManager; + } + + public function getFilters() + { + return [ + new \Twig_SimpleFilter('partImage', [$this, 'partImage']), + ]; + } + + public function partImage($number) + { + if ($part = $this->rebrickableAPIManager->getPart($number)) { + return $part->getImgUrl(); + } + } +} diff --git a/src/AppBundle/Utils/DatParser.php b/src/AppBundle/Utils/DatParser.php index 7ea6aa7..8c3cef0 100644 --- a/src/AppBundle/Utils/DatParser.php +++ b/src/AppBundle/Utils/DatParser.php @@ -2,6 +2,7 @@ namespace AppBundle\Utils; +use AppBundle\Exception\FileNotFoundException; use League\Flysystem\File; use Symfony\Component\Asset\Exception\LogicException; @@ -38,93 +39,98 @@ class DatParser * * @return array */ - public function parse(File $file) + public function parse($file) { $header = []; - $handle = $file->readStream(); + if(file_exists($file)) { + try { + $handle = fopen($file, 'r'); - if ($handle) { - $firstLine = false; + if ($handle) { + $firstLine = false; - while (($line = fgets($handle)) !== false) { - $line = trim($line); + while (($line = fgets($handle)) !== false) { + $line = trim($line); - // Comments or META Commands - if (strpos($line, '0 ') === 0) { - $line = preg_replace('/^0 /', '', $line); + // Comments or META Commands + if (strpos($line, '0 ') === 0) { + $line = preg_replace('/^0 /', '', $line); - // 0 - if (!$firstLine) { - $array = explode(' ', ltrim(trim($line, 2), '=_~')); - $header['category'] = isset($array[0]) ? $array[0] : ''; - $header['name'] = preg_replace('/ {2,}/', ' ', ltrim($line, '=_')); + // 0 + if (!$firstLine) { + $array = explode(' ', ltrim(trim($line, 2), '=_~')); + $header['category'] = isset($array[0]) ? $array[0] : ''; + $header['name'] = preg_replace('/ {2,}/', ' ', ltrim($line, '=_')); - $firstLine = true; - } - // 0 !CATEGORY - elseif (strpos($line, '!CATEGORY ') === 0) { - $header['category'] = trim(preg_replace('/^!CATEGORY /', '', $line)); - } - // 0 !KEYWORDS , , ..., - elseif (strpos($line, '!KEYWORDS ') === 0) { - $header['keywords'] = explode(', ', preg_replace('/^!KEYWORDS /', '', $line)); - } - // 0 Name: .dat - elseif (strpos($line, 'Name: ') === 0) { - if (!isset($header['id'])) { - $header['id'] = preg_replace('/(^Name: )(.*)(.dat)/', '$2', $line); + $firstLine = true; + } // 0 !CATEGORY + elseif (strpos($line, '!CATEGORY ') === 0) { + $header['category'] = trim(preg_replace('/^!CATEGORY /', '', $line)); + } // 0 !KEYWORDS , , ..., + elseif (strpos($line, '!KEYWORDS ') === 0) { + $header['keywords'] = explode(', ', preg_replace('/^!KEYWORDS /', '', $line)); + } // 0 Name: .dat + elseif (strpos($line, 'Name: ') === 0) { + if (!isset($header['id'])) { + $header['id'] = preg_replace('/(^Name: )(.*)(.dat)/', '$2', $line); + } + } // 0 Author: [] + elseif (strpos($line, 'Author: ') === 0) { + $header['author'] = preg_replace('/^Author: /', '', $line); + } // 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR + elseif (strpos($line, '!LDRAW_ORG ') === 0) { + $type = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE| ORIGINAL)(.*)/', '$2', $line); + + $header['type'] = $type; + + // Last modification date in format YYYY-RR + $date = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE | ORIGINAL )(.*)/', '$4', $line); + if (preg_match('/^[1-2][0-9]{3}-[0-9]{2}$/', $date)) { + $header['modified'] = \DateTime::createFromFormat('Y-m-d H:i:s', $date . '-01 00:00:00'); + } else { + $header['modified'] = null; + } + } + } elseif (strpos($line, '1 ') === 0) { + $header['subparts'][] = $this->getAlias($line); } } - // 0 Author: [] - elseif (strpos($line, 'Author: ') === 0) { - $header['author'] = preg_replace('/^Author: /', '', $line); - } - // 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR - elseif (strpos($line, '!LDRAW_ORG ') === 0) { - $type = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE| ORIGINAL)(.*)/', '$2', $line); - $header['type'] = $type; - - // Last modification date in format YYYY-RR - $date = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE | ORIGINAL )(.*)/', '$4', $line); - if (preg_match('/^[1-2][0-9]{3}-[0-9]{2}$/', $date)) { - $header['modified'] = \DateTime::createFromFormat('Y-m-d H:i:s', $date.'-01 00:00:00'); - } else { - $header['modified'] = null; - } + if ($this->isStickerShortcutPart($header['name'], $header['id'])) { + $header['type'] = 'Sticker'; + } elseif (($parent = $this->relationMapper->find($header['id'], 'alias_model')) != $header['id']) { + $header['type'] = 'Alias'; + $header['subparts'] = null; + $header['parent'] = $parent; + } elseif (isset($header['subparts']) && count($header['subparts']) == 1 && in_array($header['type'], ['Part Alias', 'Shortcut Physical_Colour', 'Shortcut Alias', 'Part Physical_Colour'])) { + $header['parent'] = $header['subparts'][0]; + $header['subparts'] = null; + } elseif ($parent = $this->getPrintedParentId($header['id'])) { + $header['type'] = 'Printed'; + $header['subparts'] = null; + $header['parent'] = $parent; + } elseif ($parent = $this->getObsoleteParentId($header['name'])) { + $header['type'] = 'Alias'; + $header['subparts'] = null; + $header['parent'] = $parent; + } elseif (strpos($header['name'], '~') === 0 && $header['type'] != 'Alias') { + $header['type'] = 'Obsolete/Subpart'; } - } elseif (strpos($line, '1 ') === 0) { - $header['subparts'][] = $this->getAlias($line); + + $header['name'] = ltrim($header['name'], '~'); + + fclose($handle); + + return $header; } + } catch (\Exception $exception) { + dump($exception->getMessage()); + + return null; } - - if ($this->isStickerShortcutPart($header['name'], $header['id'])) { - $header['type'] = 'Sticker'; - } elseif (($parent = $this->relationMapper->find($header['id'], 'alias_model')) != $header['id']) { - $header['type'] = 'Alias'; - $header['parent'] = $parent; - } elseif (isset($header['subparts']) && count($header['subparts']) == 1 && in_array($header['type'], ['Part Alias', 'Shortcut Physical_Colour', 'Shortcut Alias', 'Part Physical_Colour'])) { - $header['parent'] = $header['subparts'][0]; - $header['subparts'] = null; - } elseif ($parent = $this->getPrintedParentId($header['id'])) { - $header['type'] = 'Printed'; - $header['parent'] = $parent; - } elseif ($parent = $this->getObsoleteParentId($header['name'])) { - $header['type'] = 'Alias'; - $header['parent'] = $parent; - } elseif (strpos($header['name'], '~') === 0 && $header['type'] != 'Alias') { - $header['type'] = 'Obsolete/Subpart'; - } - - $header['name'] = ltrim($header['name'], '~'); - - fclose($handle); - - return $header; } - - throw new LogicException('Error parsing DAT file'); //TODO + return null; } /** diff --git a/var/SymfonyRequirements.php b/var/SymfonyRequirements.php index 7e7723a..7e7a99d 100644 --- a/var/SymfonyRequirements.php +++ b/var/SymfonyRequirements.php @@ -780,7 +780,11 @@ class SymfonyRequirements extends RequirementCollection { $size = ini_get('realpath_cache_size'); $size = trim($size); - $unit = strtolower(substr($size, -1, 1)); + $unit = ''; + if (!ctype_digit($size)) { + $unit = strtolower(substr($size, -1, 1)); + $size = (int) substr($size, 0, -1); + } switch ($unit) { case 'g': return $size * 1024 * 1024 * 1024; diff --git a/web/config.php b/web/config.php index a031a3a..69df43c 100644 --- a/web/config.php +++ b/web/config.php @@ -270,7 +270,7 @@ $hasMinorProblems = (bool) count($minorProblems); } .sf-reset ul a, .sf-reset ul a:hover { - background: url(../images/blue-arrow.png) no-repeat right 6px; + background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAICAYAAAAx8TU7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAFdJREFUeNpiYACBjjOhDEiACSggCKTLgXQ5TJARqhIkcReIKxgqTGYxwvV0nDEGkmeAOIwJySiQ4HsgvseIpGo3ELsCtZ9lRDIvDCiwhwHJPEFkJwEEGACq6hdnax8y1AAAAABJRU5ErkJggg==) no-repeat right 7px; padding-right: 10px; } .sf-reset ul, ol { From 00e70875ff3f7f1b851744f5e177aff56627616c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Fri, 7 Apr 2017 03:01:32 +0200 Subject: [PATCH 02/42] Setup Liip imagine budle to cache images --- app/AppKernel.php | 1 - app/config/config.yml | 55 +++++++++--- app/config/service/service.yml | 2 +- composer.json | 3 +- composer.lock | 112 +++++++++++++++++++----- src/AppBundle/Service/LDViewService.php | 9 +- src/AppBundle/Twig/AppExtension.php | 17 ++-- 7 files changed, 154 insertions(+), 45 deletions(-) diff --git a/app/AppKernel.php b/app/AppKernel.php index 286cdd6..302254e 100644 --- a/app/AppKernel.php +++ b/app/AppKernel.php @@ -19,7 +19,6 @@ class AppKernel extends Kernel new Knp\Bundle\MenuBundle\KnpMenuBundle(), new Oneup\FlysystemBundle\OneupFlysystemBundle(), new Knp\Bundle\PaginatorBundle\KnpPaginatorBundle(), - new Knp\Bundle\GaufretteBundle\KnpGaufretteBundle(), new Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle(), new Liip\ImagineBundle\LiipImagineBundle(), ]; diff --git a/app/config/config.yml b/app/config/config.yml index b5af026..2ce4e0a 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -92,26 +92,55 @@ knp_paginator: pagination: KnpPaginatorBundle:Pagination:semantic_ui_pagination.html.twig # sliding pagination controls template sortable: KnpPaginatorBundle:Pagination:sortable_link.html.twig # sort link template -#liip_imagine: -# filter_sets: -# cache: ~ -# square_thumb: -### cache: my_remote_resolver -# data_loader: remote.loader -## quality: 75 -## filters: -## thumbnail: { size: [150, 150], mode: outbound } +liip_imagine: + loaders: + media: + flysystem: + filesystem_service: oneup_flysystem.media_filesystem + rebrickable: + stream: + wrapper: 'http://rebrickable.com/media/' + + resolvers: + default: + web_path: ~ + + filter_sets: + model: + data_loader: media + cache: ~ + quality: 80 + filters: + thumbnail: { size: [200, 200], mode: inset } + rebrickable_part_min: + quality: 80 + data_loader: rebrickable + cache: ~ + filters: + thumbnail: { size: [200, 200], mode: inset } + rebrickable_set_mid: + quality: 80 + data_loader: rebrickable + cache: ~ + filters: + thumbnail: { size: [600, 600], mode: inset } + rebrickable_set_min: + quality: 80 + data_loader: rebrickable + cache: ~ + filters: + thumbnail: { size: [300, 300], mode: inset } oneup_flysystem: adapters: - media_adapter: + media: local: directory: "%kernel.root_dir%/../var/media/" - ldraw_library_adapter: + ldraw_library: local: directory: "%ldraw_library%" filesystems: media: - adapter: media_adapter + adapter: media ldraw_library: - adapter: ldraw_library_adapter \ No newline at end of file + adapter: ldraw_library \ No newline at end of file diff --git a/app/config/service/service.yml b/app/config/service/service.yml index 5790ddb..31313cc 100644 --- a/app/config/service/service.yml +++ b/app/config/service/service.yml @@ -17,7 +17,7 @@ services: app.twig_extension: class: AppBundle\Twig\AppExtension public: false - arguments: ['api.@manager.rebrickable'] + arguments: ['@api.manager.rebrickable'] tags: - { name: twig.extension } diff --git a/composer.json b/composer.json index 4f9f063..2a7f64b 100644 --- a/composer.json +++ b/composer.json @@ -23,10 +23,9 @@ "sensio/framework-extra-bundle": "^3.0.2", "incenteev/composer-parameter-handler": "^2.0", "guzzlehttp/guzzle": "^6.2", - "knplabs/knp-menu-bundle": "^2.0", + "knplabs/knp-menu-bundle": "^2.1", "oneup/flysystem-bundle": "^1.7", "knplabs/knp-paginator-bundle": "^2.5", - "knplabs/knp-gaufrette-bundle": "~0.3", "lexik/form-filter-bundle": "~5.0", "liip/imagine-bundle": "^1.7" }, diff --git a/composer.lock b/composer.lock index 924f4ff..185359a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,67 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "fcff1b4aff59fb1710a57c91e6a04a6c", + "content-hash": "6a70240b303126447c6fc3c6f75bf6ff", "packages": [ + { + "name": "composer/ca-bundle", + "version": "1.0.7", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/b17e6153cb7f33c7e44eb59578dc12eee5dc8e12", + "reference": "b17e6153cb7f33c7e44eb59578dc12eee5dc8e12", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^5.3.2 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.5", + "psr/log": "^1.0", + "symfony/process": "^2.5 || ^3.0" + }, + "suggest": { + "symfony/process": "This is necessary to reliably check whether openssl_x509_parse is vulnerable on older php versions, but can be ignored on PHP 5.5.6+" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "time": "2017-03-06T11:59:08+00:00" + }, { "name": "doctrine/annotations", "version": "v1.2.7", @@ -2233,19 +2292,20 @@ }, { "name": "sensiolabs/security-checker", - "version": "v4.0.2", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/sensiolabs/security-checker.git", - "reference": "56bded66985e22f6eac2cf86735fd21c625bff2f" + "reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/56bded66985e22f6eac2cf86735fd21c625bff2f", - "reference": "56bded66985e22f6eac2cf86735fd21c625bff2f", + "url": "https://api.github.com/repos/sensiolabs/security-checker/zipball/9e69eddf3bc49d1ee5c7908564da3141796d4bbc", + "reference": "9e69eddf3bc49d1ee5c7908564da3141796d4bbc", "shasum": "" }, "require": { + "composer/ca-bundle": "^1.0", "symfony/console": "~2.7|~3.0" }, "bin": [ @@ -2273,7 +2333,7 @@ } ], "description": "A security checker for your composer.lock", - "time": "2017-03-09T17:33:20+00:00" + "time": "2017-03-31T14:50:32+00:00" }, { "name": "swiftmailer/swiftmailer", @@ -2787,16 +2847,16 @@ }, { "name": "symfony/symfony", - "version": "v3.2.6", + "version": "v3.2.7", "source": { "type": "git", "url": "https://github.com/symfony/symfony.git", - "reference": "b0f8a7fa4b8baadf9db299cb6ac87c96a8977dbe" + "reference": "1631040ea8fc5e0e405c00d35cbf2c0b7b555f64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/symfony/zipball/b0f8a7fa4b8baadf9db299cb6ac87c96a8977dbe", - "reference": "b0f8a7fa4b8baadf9db299cb6ac87c96a8977dbe", + "url": "https://api.github.com/repos/symfony/symfony/zipball/1631040ea8fc5e0e405c00d35cbf2c0b7b555f64", + "reference": "1631040ea8fc5e0e405c00d35cbf2c0b7b555f64", "shasum": "" }, "require": { @@ -2927,7 +2987,7 @@ "keywords": [ "framework" ], - "time": "2017-03-10T18:35:48+00:00" + "time": "2017-04-05T12:52:29+00:00" }, { "name": "twig/twig", @@ -2995,49 +3055,59 @@ "packages-dev": [ { "name": "friendsofphp/php-cs-fixer", - "version": "v2.1.2", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "c7de769d7b44f2c9de68e1f678b65efd8126f60b" + "reference": "d6f17423412d33df6b69c9aaf12037b91703533b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/c7de769d7b44f2c9de68e1f678b65efd8126f60b", - "reference": "c7de769d7b44f2c9de68e1f678b65efd8126f60b", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/d6f17423412d33df6b69c9aaf12037b91703533b", + "reference": "d6f17423412d33df6b69c9aaf12037b91703533b", "shasum": "" }, "require": { + "doctrine/annotations": "^1.2", "ext-tokenizer": "*", "php": "^5.3.6 || >=7.0 <7.2", "sebastian/diff": "^1.1", - "symfony/console": "^2.3 || ^3.0", + "symfony/console": "^2.4 || ^3.0", "symfony/event-dispatcher": "^2.1 || ^3.0", "symfony/filesystem": "^2.4 || ^3.0", "symfony/finder": "^2.2 || ^3.0", + "symfony/options-resolver": "^2.6 || ^3.0", "symfony/polyfill-php54": "^1.0", "symfony/polyfill-php55": "^1.3", + "symfony/polyfill-php70": "^1.0", "symfony/polyfill-xml": "^1.3", "symfony/process": "^2.3 || ^3.0", "symfony/stopwatch": "^2.5 || ^3.0" }, "conflict": { - "hhvm": "<3.9" + "hhvm": "<3.18" }, "require-dev": { "gecko-packages/gecko-php-unit": "^2.0", "justinrainbow/json-schema": "^5.0", "phpunit/phpunit": "^4.5 || ^5.0", "satooshi/php-coveralls": "^1.0", - "symfony/phpunit-bridge": "^3.2" + "symfony/phpunit-bridge": "^3.2.2" }, "suggest": { - "ext-xml": "For better performance." + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "ext-xml": "For better performance.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." }, "bin": [ "php-cs-fixer" ], "type": "application", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, "autoload": { "psr-4": { "PhpCsFixer\\": "src/" @@ -3058,7 +3128,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2017-03-15T17:13:07+00:00" + "time": "2017-03-31T16:16:30+00:00" }, { "name": "ircmaxell/password-compat", @@ -3210,7 +3280,7 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v3.2.6", + "version": "v3.2.7", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", diff --git a/src/AppBundle/Service/LDViewService.php b/src/AppBundle/Service/LDViewService.php index 802bbec..8eb86bb 100644 --- a/src/AppBundle/Service/LDViewService.php +++ b/src/AppBundle/Service/LDViewService.php @@ -102,9 +102,14 @@ class LDViewService $this->runLDView([ $file, '-LDrawDir='.$this->ldrawLibraryFilesystem->getAdapter()->getPathPrefix(), - '-AutoCrop=1', - '-SaveAlpha=0', + '-AutoCrop=0', + '-SaveAlpha=1', + '-BackgroundColor3=0xFFFFFF', + '-DefaultColor3=0x136FC3', '-SnapshotSuffix=.png', + '-HiResPrimitives=1', + '-CurveQuality=10', + '-DefaultLatLong=40,35', '-SaveDir='.$this->mediaFilesystem->getAdapter()->getPathPrefix().'ldraw'.DIRECTORY_SEPARATOR.'images', '-SaveSnapshots=1', ]); diff --git a/src/AppBundle/Twig/AppExtension.php b/src/AppBundle/Twig/AppExtension.php index 326e233..1e3418f 100644 --- a/src/AppBundle/Twig/AppExtension.php +++ b/src/AppBundle/Twig/AppExtension.php @@ -3,6 +3,9 @@ namespace AppBundle\Twig; use AppBundle\Api\Manager\RebrickableManager; +use AppBundle\Entity\Rebrickable\Color; +use AppBundle\Entity\Rebrickable\Part; +use AppBundle\Entity\Rebrickable\Set; class AppExtension extends \Twig_Extension { @@ -12,7 +15,7 @@ class AppExtension extends \Twig_Extension /** * AppExtension constructor. * - * @param $rebrickableAPIManager + * @param RebrickableManager $rebrickableAPIManager */ public function __construct($rebrickableAPIManager) { @@ -23,13 +26,17 @@ class AppExtension extends \Twig_Extension { return [ new \Twig_SimpleFilter('partImage', [$this, 'partImage']), + new \Twig_SimpleFilter('setImage', [$this, 'setImage']), ]; } - public function partImage($number) + public function partImage(Part $part, Color $color = null) { - if ($part = $this->rebrickableAPIManager->getPart($number)) { - return $part->getImgUrl(); - } + return '/parts/ldraw/'.($color ? $color->getId():'-1').'/'.$part->getNumber().'.png'; + } + + public function setImage(Set $set) + { + return '/sets/'.strtolower($set->getNumber()).'.jpg'; } } From 61caac6f559f6c47854457830cdc129e11687b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Fri, 7 Apr 2017 19:05:34 +0200 Subject: [PATCH 03/42] Improve ModelViewer --- app/Resources/assets/js/ModelViewer.js | 256 +++++++++++++++++++------ 1 file changed, 193 insertions(+), 63 deletions(-) diff --git a/app/Resources/assets/js/ModelViewer.js b/app/Resources/assets/js/ModelViewer.js index 09bc7cc..5604ee2 100644 --- a/app/Resources/assets/js/ModelViewer.js +++ b/app/Resources/assets/js/ModelViewer.js @@ -1,85 +1,215 @@ -ModelViewer = function() { - var container; - var camera, cameraTarget, scene, renderer, controls, object; - var width, height; +var ModelViewer = function($container) { + var $this = this; + this.container = $container; - this.initScene = function($container) { - width = parseFloat($container.width()); - height = parseFloat($container.height()); + this.camera = null; + this.scene = null; + this.renderer = null; + this.controls = null; + this.object = null; + this.width = parseFloat(this.container.width()); + this.height = parseFloat(this.container.height()); + this.visible = true; + this.scale = 1; - camera = new THREE.PerspectiveCamera(45, width/height, 0.1, 1000); - camera.position.set(-2, 2, 0.8); - camera.lookAt(new THREE.Vector3(0, 3, 0)); + this.initScene(); - scene = new THREE.Scene(); - scene.fog = new THREE.FogExp2(0x000000, 0.001); + function renderLoop() { + requestAnimationFrame( renderLoop ); + $this.render(); + } - // var grid = new THREE.GridHelper( 30, 70 ); - // // grid.position.set(30/70,-0.5,30/70); - // scene.add( grid ); + renderLoop(); +}; - // Lights +ModelViewer.prototype.initScene = function() { - light = new THREE.DirectionalLight( 0xffffff ); - light.position.set( 1, 1, 1 ); - scene.add( light ); + this.scene = new THREE.Scene(); - light = new THREE.DirectionalLight( 0x002288 ); - light.position.set( -1, -1, -1 ); - scene.add( light ); + this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000); + this.camera.position.z = 8; + this.camera.position.y = -5; + this.camera.position.x = 3; + this.camera.up = new THREE.Vector3(0, 0, 1); + + this.reflectCamera = new THREE.CubeCamera(1, 50, 200); + this.scene.add(this.reflectCamera); + + var material = new THREE.MeshPhongMaterial({ + color: 0xffffff, + emissive: 0xffffff, + shading: THREE.SmoothShading, + fog: false, + side: THREE.BackSide + }); + + var skybox = new THREE.Mesh(new THREE.CubeGeometry(100, 100, 100), material); + skybox.name = 'skybox'; + this.scene.add(skybox); + + var groundPlaneMaterial = new THREE.MeshPhongMaterial({ + color: 0x888888, + wireframe: false, + transparent: true, + opacity: 0.25, + // fog: true, + // specular: 0x999999, + envMap: this.reflectCamera.renderTarget + }); + var x = 80; + var y = 80; + var division_x = Math.floor(x / 2); + var division_y = Math.floor(y / 2); + + this.plane = new THREE.Mesh(new THREE.PlaneGeometry(x, y, division_x, division_y), groundPlaneMaterial); + this.plane.name = 'plane'; + this.plane.receiveShadow = true; + this.scene.add(this.plane); + + this.grid = new THREE.GridHelper( 80, 100, 0xEEEEEE,0xEEEEEE); + this.grid.rotation.x = Math.PI/2; + + this.scene.add(this.grid); - scene.add( new THREE.AmbientLight( 0xf0f0f0 )); - scene.background = new THREE.Color( 0x000000 ); + // this.wirePlane = new THREE.Mesh(new THREE.PlaneGeometry(x, y, division_x, division_y), new THREE.MeshPhongMaterial({ + // emissive: 0xEEEEEE, + // color: 0x000000, + // wireframe: true, + // wireframeLinewidth: 2 + // })); + // this.wirePlane.name = 'planewire'; + // this.wirePlane.receiveShadow = true; + // this.wirePlane.position.z = this.plane.position.z + .01; + // this.scene.add(this.wirePlane); - // renderer - renderer = new THREE.WebGLRenderer(); - renderer.setClearColor( scene.fog.color ); - renderer.setPixelRatio( window.devicePixelRatio ); - renderer.setSize( width, height ); - renderer.gammaInput = true; - renderer.gammaOutput = true; - renderer.shadowMap.enabled = true; - renderer.shadowMap.renderReverseSided = false; + this.renderer = new THREE.WebGLRenderer(); - $container.append(renderer.domElement); + this.renderer.setSize(this.width, this.height); - controls = new THREE.OrbitControls( camera, renderer.domElement ); - controls.addEventListener( 'change', this.render ); // add this only if there is no animation loop (requestAnimationFrame) - // controls.enableDamping = true; - // controls.dampingFactor = 0.25; - controls.enableZoom = true; - }; + this.container.append(this.renderer.domElement); - this.loadStl = function(model) { - var loader = new THREE.STLLoader(); - loader.load(model, function (geometry) { - var material = new THREE.MeshPhongMaterial({color: 0xaaaaaa, shininess:200, specular: 0x333333, shading: THREE.FlatShading}); - var mesh = new THREE.Mesh(geometry, material); + this.scene.fog = new THREE.FogExp2(0xcacaca, 0.9); - mesh.geometry.computeBoundingBox(); - var positionY = (mesh.geometry.boundingBox.max.y + mesh.geometry.boundingBox.min.y)/2; + // // Light + this.spotLight = new THREE.SpotLight(0xffffff, 0.9, 0); + this.spotLight.position.set(-70, 100, 100); + this.spotLight.castShadow = false; + this.scene.add(this.spotLight); - // mesh.position.set(0, positionY, 0); - mesh.rotation.set(Math.PI, 0, 0); - mesh.castShadow = true; - mesh.receiveShadow = true; - scene.add(mesh); - - renderer.render(scene, camera); - }); - }; + this.bottomSpotLight = new THREE.SpotLight(0xffffff, 0.3, 0); + this.bottomSpotLight.position.set(70, -100, -100); + this.bottomSpotLight.castShadow = false; + this.scene.add(this.bottomSpotLight); - this.animate = function() { - requestAnimationFrame( this.animate ); - this.render(); - }; + this.pointLight = new THREE.PointLight(0xffaaff, 0.9, 0); + this.pointLight.position.set(32, -39, 35); + this.pointLight.castShadow = true; + this.scene.add(this.pointLight); - this.render = function() { + this.renderer.shadowMap.enabled = true; + this.renderer.shadowMap.renderReverseSided = false; - renderer.render(scene, camera); + // $container.append(this.renderer.domElement); - }; + this.controls = new THREE.OrbitControls( this.camera, this.renderer.domElement ); + this.controls.enableZoom = true; +}; + +ModelViewer.prototype.addModel = function(geometry) { + var material = new THREE.MeshPhongMaterial({ + color: 0x136FC3, + specular: 0x0D0D0D, + shading: THREE.SmoothShading, + shininess: 150, + fog: false, + side: THREE.DoubleSide, + // wireframe: true, + }); + + geometry.center(); + var mesh = new THREE.Mesh(geometry, material); + + mesh.scale.set(this.scale, this.scale, this.scale); + + mesh.geometry.computeFaceNormals(); + mesh.geometry.computeVertexNormals(); + mesh.rotation.set(-Math.PI/2,0, 0); + mesh.geometry.computeBoundingBox(); + + mesh.castShadow = true; + // mesh.receiveShadow = true; + + var dims = mesh.geometry.boundingBox.max.clone().sub(mesh.geometry.boundingBox.min); + + maxDim = Math.max(Math.max(dims.x, dims.y), dims.z); + + mesh.position.x = -(mesh.geometry.boundingBox.min.x + mesh.geometry.boundingBox.max.x) / 2 * this.scale; + mesh.position.z = -(mesh.geometry.boundingBox.min.y + mesh.geometry.boundingBox.max.y) / 2 * this.scale; + mesh.position.y = -mesh.geometry.boundingBox.min.z * this.scale; + + var positionY = (mesh.geometry.boundingBox.max.z + mesh.geometry.boundingBox.min.z)/2 * this.scale; + var positionZ = (mesh.geometry.boundingBox.max.y - mesh.geometry.boundingBox.min.y)/2 * this.scale; + + mesh.position.set(0, positionY, positionZ); + + // this.scene.face_count = mesh.geometry.faces.length; + this.scene.add(mesh); + + this.centerCamera(mesh); +}; + +ModelViewer.prototype.loadStl = function(model) { + var self = this; + + var loader = new THREE.STLLoader(); + + loader.load(model, function (geometry) { + self.addModel(geometry); + }); +}; + +ModelViewer.prototype.centerCamera = function(mesh) { + + var boxHelper = new THREE.BoxHelper( mesh ); + + var sceneCenter = this.objectCenter(mesh); + + var geometry = mesh.geometry; + + var distanceX = (geometry.boundingBox.max.x - geometry.boundingBox.min.x) / 2 / Math.tan(this.camera.fov * this.camera.aspect * Math.PI / 360); + var distanceY = (geometry.boundingBox.max.y - geometry.boundingBox.min.y) / 2 / Math.tan(this.camera.fov * this.camera.aspect * Math.PI / 360); + var distanceZ = (geometry.boundingBox.max.z - geometry.boundingBox.min.z) / 2 / Math.tan(this.camera.fov * Math.PI / 360); + + var maxDistance = Math.max(Math.max(distanceX, distanceY), distanceZ); + maxDistance *= 2.6 * this.scale; + + var cameraPosition = this.camera.position.normalize().multiplyScalar(maxDistance); + + this.controls.maxDistance = 3 * maxDistance; + + this.controls.position0 = cameraPosition; + this.controls.target0 = sceneCenter; + this.controls.reset(); +}; + +ModelViewer.prototype.objectCenter = function (mesh) { + var middle = new THREE.Vector3(); + var geometry = mesh.geometry; + + geometry.center(); + geometry.computeBoundingBox(); + + middle.x = (geometry.boundingBox.max.x + geometry.boundingBox.min.x) / 2; + middle.y = (geometry.boundingBox.max.y + geometry.boundingBox.min.y) / 2; + middle.z = (geometry.boundingBox.max.z + geometry.boundingBox.min.z) / 2; + + mesh.localToWorld(middle); + return middle; +}; + +ModelViewer.prototype.render = function() { + this.renderer.render(this.scene, this.camera); }; \ No newline at end of file From 2976f6b801d49845ecf11cd6927f2c6456120cee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Fri, 7 Apr 2017 19:07:15 +0200 Subject: [PATCH 04/42] Update Controllers --- .gitignore | 1 + app/Resources/views/base.html.twig | 71 ++++++++++++------- app/Resources/views/macros/elements.html.twig | 10 ++- .../views/{ldraw => }/model/detail.html.twig | 13 ++-- .../views/{ldraw => }/model/index.html.twig | 14 ++-- .../views/rebrickable/part/detail.html.twig | 15 ++-- .../views/rebrickable/set/detail.html.twig | 29 ++------ src/AppBundle/Controller/MediaController.php | 2 +- .../{LDraw => }/ModelController.php | 23 ++++-- .../Controller/Rebrickable/SetController.php | 1 - src/AppBundle/Menu/Builder.php | 2 +- 11 files changed, 98 insertions(+), 83 deletions(-) rename app/Resources/views/{ldraw => }/model/detail.html.twig (91%) rename app/Resources/views/{ldraw => }/model/index.html.twig (66%) rename src/AppBundle/Controller/{LDraw => }/ModelController.php (77%) diff --git a/.gitignore b/.gitignore index 640116f..ad6ac8c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ /node_modules/ /app/Resources/libs/semantic/dist /web/resources/ +/web/media/ .php_cs.cache \ No newline at end of file diff --git a/app/Resources/views/base.html.twig b/app/Resources/views/base.html.twig index f8dff0e..d22379a 100644 --- a/app/Resources/views/base.html.twig +++ b/app/Resources/views/base.html.twig @@ -2,39 +2,56 @@ - - - {% block title %}Welcome!{% endblock %} - {% block stylesheets %} - - {% endblock %} - - - - {% block body %} -

- - {% endif %}
-
+
@@ -75,10 +76,8 @@ {% endblock %} \ No newline at end of file diff --git a/app/Resources/views/ldraw/model/index.html.twig b/app/Resources/views/model/index.html.twig similarity index 66% rename from app/Resources/views/ldraw/model/index.html.twig rename to app/Resources/views/model/index.html.twig index 0bc5989..11219bf 100644 --- a/app/Resources/views/ldraw/model/index.html.twig +++ b/app/Resources/views/model/index.html.twig @@ -3,7 +3,7 @@ {% import 'macros/elements.html.twig' as elements %} {% block content %} -
+ {{ form_start(form) }} {{ form_row(form.search) }} @@ -19,14 +19,10 @@

{{ models.getTotalItemCount }}

-
-
- {% for model in models %} -
- {{ elements.part(model) }} -
- {% endfor %} -
+
+ {% for model in models %} + {{ elements.part(model) }} + {% endfor %}
{{ knp_pagination_render(models) }} diff --git a/app/Resources/views/rebrickable/part/detail.html.twig b/app/Resources/views/rebrickable/part/detail.html.twig index 050fdff..9f41151 100644 --- a/app/Resources/views/rebrickable/part/detail.html.twig +++ b/app/Resources/views/rebrickable/part/detail.html.twig @@ -15,9 +15,6 @@
{% endif %} -
- -
{% if part.model %} {{ elements.part(part.model) }} @@ -28,9 +25,19 @@ Sets + +
{% for set in sets %} - {{ set.number }} + {% endfor %} +
{{ dump(apiPart) }} diff --git a/app/Resources/views/rebrickable/set/detail.html.twig b/app/Resources/views/rebrickable/set/detail.html.twig index 153eea9..9d6a7a2 100644 --- a/app/Resources/views/rebrickable/set/detail.html.twig +++ b/app/Resources/views/rebrickable/set/detail.html.twig @@ -10,8 +10,9 @@
count of parts:
{{ set.partCount }}
- + + {{ dump(rbset.imgUrl) }}

Parts {{ inventoryParts|length }} @@ -20,36 +21,18 @@
{% for inventoryPart in inventoryParts %}
- {% endfor %}
- -

- Models {{ parts|length }} -

- -
- {% for part in parts %} - - {% endfor %} -
- - {% endif %} {{ brset ? dump(brset) }} diff --git a/src/AppBundle/Controller/MediaController.php b/src/AppBundle/Controller/MediaController.php index ea25151..e9e4b23 100644 --- a/src/AppBundle/Controller/MediaController.php +++ b/src/AppBundle/Controller/MediaController.php @@ -12,7 +12,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Routing\Annotation\Route; /** - * @Route("/media") + * @Route("/files") */ class MediaController extends Controller { diff --git a/src/AppBundle/Controller/LDraw/ModelController.php b/src/AppBundle/Controller/ModelController.php similarity index 77% rename from src/AppBundle/Controller/LDraw/ModelController.php rename to src/AppBundle/Controller/ModelController.php index e607afc..0348867 100644 --- a/src/AppBundle/Controller/LDraw/ModelController.php +++ b/src/AppBundle/Controller/ModelController.php @@ -1,6 +1,6 @@ paginate( $filterBuilder->getQuery(), $request->query->getInt('page', 1)/*page number*/, - $request->query->getInt('limit', 100)/*limit per page*/ + $request->query->getInt('limit', 40)/*limit per page*/ ); - return $this->render('ldraw/model/index.html.twig', [ + return $this->render('model/index.html.twig', [ 'models' => $models, 'form' => $form->createView(), ]); @@ -57,7 +57,7 @@ class ModelController extends Controller /** * Finds and displays a part entity. * - * @Route("/models/{number}", name="model_detail") + * @Route("/{number}", name="model_detail") * @Method("GET") */ public function detailAction($number) @@ -65,11 +65,20 @@ class ModelController extends Controller $em = $this->getDoctrine()->getManager(); if($model = $this->get('manager.ldraw.model')->findByNumber($number)) { + + +// $bin = $this->get('imagine.data.loader.media')->find('/ldraw/images/'.$model->getNumber().'.png'); +// +// $path = $this->get('imagine.cache.resolver.media')->resolve('/ldraw/images/'.$model->getNumber().'.png','model'); +// +// $this->get('imagine.cache.resolver.media')->store($bin,$path,'model'); + + try { $rbParts = $model != null ? $em->getRepository(Part::class)->findAllByModel($model) : null; $sets = $model != null ? $em->getRepository(Set::class)->findAllByModel($model) : null; - return $this->render('ldraw/model/detail.html.twig', [ + return $this->render('model/detail.html.twig', [ 'model' => $model, 'rbParts' => $rbParts, 'sets' => $sets, diff --git a/src/AppBundle/Controller/Rebrickable/SetController.php b/src/AppBundle/Controller/Rebrickable/SetController.php index 9cca944..a9483a2 100644 --- a/src/AppBundle/Controller/Rebrickable/SetController.php +++ b/src/AppBundle/Controller/Rebrickable/SetController.php @@ -67,7 +67,6 @@ class SetController extends Controller 'set' => $set, 'brset' => $brset, 'rbset' => $rbset, - 'parts' => $em->getRepository(Model::class)->findAllBySetNumber($number), 'inventoryParts' => $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number), ]); } diff --git a/src/AppBundle/Menu/Builder.php b/src/AppBundle/Menu/Builder.php index d94fe81..acd48a9 100644 --- a/src/AppBundle/Menu/Builder.php +++ b/src/AppBundle/Menu/Builder.php @@ -35,7 +35,7 @@ class Builder ]); $menu->addChild('Models', [ - 'route' => 'ldraw_model_index', + 'route' => 'model_index', ]); $menu->addChild('Sets', [ From db7195e4d3e7816cc3ec0e4049691fa1bcaca535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Fri, 7 Apr 2017 19:07:39 +0200 Subject: [PATCH 05/42] Add part model relations --- app/Resources/relations/part_model.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Resources/relations/part_model.yml b/app/Resources/relations/part_model.yml index 32e6af6..e8811a3 100644 --- a/app/Resources/relations/part_model.yml +++ b/app/Resources/relations/part_model.yml @@ -21,6 +21,9 @@ 970d09: 3815c36 970d12: 3815c36 +973pr2303c01: 973c00 +973pr2302c01: 973c00 + 57503: 70501a 90391: 90391p01 @@ -153,4 +156,7 @@ wheel2a: 568c01 11895pr0001c01: 11895 92456pr0021c01: 92241p03c01 -15672: 92946 \ No newline at end of file +15672: 92946 + +3614a: 3614 +3614b: 3614 \ No newline at end of file From 88c443b4ed8cc1c2373f7964cf703134728820b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sat, 8 Apr 2017 11:24:20 +0200 Subject: [PATCH 06/42] Add 3D view toggle --- app/Resources/assets/js/ModelViewer.js | 114 ++++++++++++++------ app/Resources/assets/style/modelviewer.scss | 25 +++++ app/Resources/assets/style/style.scss | 1 + app/Resources/views/model/detail.html.twig | 74 ++++++------- app/config/config.yml | 18 +++- gulpfile.js | 3 +- 6 files changed, 159 insertions(+), 76 deletions(-) create mode 100644 app/Resources/assets/style/modelviewer.scss diff --git a/app/Resources/assets/js/ModelViewer.js b/app/Resources/assets/js/ModelViewer.js index 5604ee2..adc096d 100644 --- a/app/Resources/assets/js/ModelViewer.js +++ b/app/Resources/assets/js/ModelViewer.js @@ -1,45 +1,75 @@ -var ModelViewer = function($container) { +var ModelViewer = function($dom_element) { var $this = this; - this.container = $container; + this.container = document.createElement('div'); + + this.dom_element = $dom_element; + + $dom_element.append(this.container); + this.container.style.display = "none"; this.camera = null; this.scene = null; this.renderer = null; this.controls = null; this.object = null; - this.width = parseFloat(this.container.width()); - this.height = parseFloat(this.container.height()); + this.width = parseFloat($dom_element.width()); + this.height = parseFloat($dom_element.height()); this.visible = true; + this.stats = null; this.scale = 1; + this.wireframe = false; + this.rendering = false; + this.initHtml(); this.initScene(); function renderLoop() { - requestAnimationFrame( renderLoop ); - $this.render(); + requestAnimationFrame(renderLoop); + if($this.rendering) { + $this.render(); + } + $this.stats.update(); } renderLoop(); }; +ModelViewer.prototype.initHtml = function () { + $this = this; + + var buttons = document.createElement("div"); + buttons.setAttribute("class", "modelviewer-buttons"); + + var toggleButton = $('
-
+ + {% endblock %} {% block javascripts %} diff --git a/app/Resources/views/macros/elements.html.twig b/app/Resources/views/macros/elements.html.twig index 1eaacb4..9b9c149 100644 --- a/app/Resources/views/macros/elements.html.twig +++ b/app/Resources/views/macros/elements.html.twig @@ -2,8 +2,10 @@ diff --git a/app/Resources/views/model/detail.html.twig b/app/Resources/views/model/detail.html.twig index a057228..1ef114a 100644 --- a/app/Resources/views/model/detail.html.twig +++ b/app/Resources/views/model/detail.html.twig @@ -7,9 +7,9 @@ {% block header %}{{ model.name }}{% endblock %} {% block content %} -
+ +
-
@@ -18,6 +18,7 @@
category:
{{ model.category ? model.category.name }}
model:
{{ model.path }}
author:
{{ model.author }}
+
modified:
{{ model.modified ? model.modified|date('Y-m-d') }}
keywords:
{% for keyword in model.keywords %} @@ -49,17 +50,25 @@ Subparts of this model

- {% for subpart in model.subparts %} - {{ elements.part(subpart.subpart) }} - {% endfor %} +
+ {% for subpart in model.subparts %} +
+ {{ elements.part(subpart.subpart) }} +
+ {% endfor %} +

Model is subpart of

- {% for subpart in model.parents %} - {{ elements.part(subpart.parent) }} - {% endfor %} +
+ {% for subpart in model.parents %} +
+ {{ elements.part(subpart.parent) }} +
+ {% endfor %} +

Sets ({{ sets|length }}) @@ -73,11 +82,9 @@ {% block javascripts %} {{ parent() }} - {% endblock %} \ No newline at end of file diff --git a/app/Resources/views/rebrickable/part/detail.html.twig b/app/Resources/views/rebrickable/part/detail.html.twig index 9f41151..d3b6d6b 100644 --- a/app/Resources/views/rebrickable/part/detail.html.twig +++ b/app/Resources/views/rebrickable/part/detail.html.twig @@ -9,23 +9,21 @@
category:
{{ part.category ? part.category.name }}
-
- {% if apiPart is not null %} -
- -
- {% endif %} - {% if part.model %} - {{ elements.part(part.model) }} - {% endif %} -
+ {% if apiPart is not null %} + Rebrickable + + + {% endif %} + + {% if part.model %} + {{ elements.part(part.model) }} + {% endif %}

Sets

-
{% for set in sets %}
diff --git a/app/Resources/views/rebrickable/set/detail.html.twig b/app/Resources/views/rebrickable/set/detail.html.twig deleted file mode 100644 index 9d6a7a2..0000000 --- a/app/Resources/views/rebrickable/set/detail.html.twig +++ /dev/null @@ -1,41 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block content %} - {% if set is not null %} -
-
number:
{{ set.number }}
-
year:
{{ set.year }}
-
name:
{{ set.name }}
-
theme:
{{ set.theme.name }}
-
count of parts:
{{ set.partCount }}
-
- - - - {{ dump(rbset.imgUrl) }} - -

- Parts {{ inventoryParts|length }} -

- -
- {% for inventoryPart in inventoryParts %} - - {% endfor %} -
- - {% endif %} - - {{ brset ? dump(brset) }} - - -{% endblock %} \ No newline at end of file diff --git a/app/Resources/views/rebrickable/set/inventory_sets.html.twig b/app/Resources/views/rebrickable/set/inventory_sets.html.twig new file mode 100644 index 0000000..098c2fc --- /dev/null +++ b/app/Resources/views/rebrickable/set/inventory_sets.html.twig @@ -0,0 +1,17 @@ +

+ Sets +

+
+ {% for inventorySet in inventorySets %} + + {% endfor %} +
\ No newline at end of file diff --git a/app/Resources/views/rebrickable/set/parts.html.twig b/app/Resources/views/rebrickable/set/parts.html.twig new file mode 100644 index 0000000..a8295f1 --- /dev/null +++ b/app/Resources/views/rebrickable/set/parts.html.twig @@ -0,0 +1,48 @@ +{% extends 'base.html.twig' %} + +{% block content %} + +

+ Regular parts +

+ +
+ {% for inventoryPart in regularParts %} + {% if inventoryPart.part is defined %} + + {% endif %} + {% endfor %} +
+ +

+ Spare parts +

+
+ {% for inventoryPart in spareParts %} + {% if inventoryPart.part is defined %} + + {% endif %} + {% endfor %} +
+ +{% endblock %} \ No newline at end of file diff --git a/app/Resources/views/set/detail.html.twig b/app/Resources/views/set/detail.html.twig new file mode 100644 index 0000000..388e99e --- /dev/null +++ b/app/Resources/views/set/detail.html.twig @@ -0,0 +1,76 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ rbset ? rbset.number }} {{ rbset ? rbset.name }}{% endblock %} + +{% block header %}{{ rbset ? rbset.number }} {{ rbset ? rbset.name }}{% endblock %} + +{% block content %} + {% if brset %} + Brickset + {% endif %} + + + {% if rbset is not null %} + Rebrickable +
+
number:
{{ rbset.number }}
+
year:
{{ rbset.year }}
+
name:
{{ rbset.name }}
+
theme:
{{ rbset.theme.name }}
+ {% if rbset.theme.parent %} +
themeparent:
{{ rbset.theme.parent.name }}
+ {% if rbset.theme.parent.parent %} +
themeparent:
{{ rbset.theme.parent.parent.name }}
+ {% endif %} + {% endif %} +
count of parts:
{{ rbset.partCount }}
+
+ {% endif %} + + {% if brset is not null %} +
+
year:
{{ brset.year }}
+
name:
{{ brset.name }}
+
themegroup:
{{ brset.themeGroup }}
+
theme:
{{ brset.theme }}
+
subtheme:
{{ brset.subtheme }}
+
count of parts:
{{ brset.pieces }}
+
lego id:
{{ brset.legoSetID }}
+
minifigs:
{{ brset.minifigs }}
+
description:
{{ brset.description }}
+
+ {% endif %} + + {% if brset %} + + {% elseif rbset %} + + {% endif %} + + {#{{ brset ? dump(brset) }}#} + + +
+ {% if rbset %} + {#{{ render(controller('AppBundle:Rebrickable/Set:parts', { 'number': rbset.number })) }}#} + {% endif %} +
+ {% if brset %} +
+ {{ render(controller('AppBundle:Brickset/Set:images', { 'id': brset.setID })) }} +
+
+ {{ render(controller('AppBundle:Brickset/Set:instructions', { 'id': brset.setID })) }} +
+
+ {{ render(controller('AppBundle:Brickset/Set:reviews', { 'id': brset.setID })) }} +
+ {% endif %} +{% endblock %} \ No newline at end of file diff --git a/app/Resources/views/rebrickable/set/index.html.twig b/app/Resources/views/set/index.html.twig similarity index 81% rename from app/Resources/views/rebrickable/set/index.html.twig rename to app/Resources/views/set/index.html.twig index 16375e3..7c3f768 100644 --- a/app/Resources/views/rebrickable/set/index.html.twig +++ b/app/Resources/views/set/index.html.twig @@ -6,8 +6,7 @@ {% for set in sets %} diff --git a/app/config/config.yml b/app/config/config.yml index 0f9fbf2..89fd475 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -99,46 +99,44 @@ liip_imagine: filesystem_service: oneup_flysystem.media_filesystem rebrickable: stream: - wrapper: 'http://rebrickable.com/media/' + wrapper: 'http://m.rebrickable.com/media/' resolvers: default: web_path: ~ filter_sets: - model: + model_min: data_loader: media cache: ~ quality: 80 + default_image: '/resources/images/unknown_image_min.png' filters: thumbnail: { size: [200, 200], mode: inset } background: { size: [250, 250], position: center, color: '#FFFFFF' } - model_large: - data_loader: media - cache: ~ - quality: 92 - filters: - thumbnail: { size: [400, 300], mode: inset } - background: { size: [600, 400], position: center, color: '#FFFFFF' } - rebrickable_part_min: - quality: 80 - data_loader: rebrickable - cache: ~ - filters: - thumbnail: { size: [200, 200], mode: inset } - rebrickable_set_mid: - quality: 80 - data_loader: rebrickable - cache: ~ - filters: - thumbnail: { size: [600, 600], mode: inset } - rebrickable_set_min: - quality: 80 - data_loader: rebrickable - cache: ~ - filters: - thumbnail: { size: [300, 300], mode: inset } - + model_large: + data_loader: media + cache: ~ + quality: 92 + filters: + thumbnail: { size: [800, 600], mode: inset } + background: { size: [1100, 800], position: center, color: '#FFFFFF' } + rebrickable: + data_loader: rebrickable + rebrickable_part_min: + quality: 80 + data_loader: rebrickable + cache: ~ + default_image: '/resources/images/unknown_image_min.png' + filters: + thumbnail: { size: [250, 250], mode: inset } + rebrickable_set_min: + quality: 80 + data_loader: rebrickable + default_image: '/resources/images/unknown_image_min.png' + cache: ~ + filters: + thumbnail: { size: [250, 250], mode: inset } oneup_flysystem: adapters: media: diff --git a/src/AppBundle/Controller/ModelController.php b/src/AppBundle/Controller/ModelController.php index 0348867..306f766 100644 --- a/src/AppBundle/Controller/ModelController.php +++ b/src/AppBundle/Controller/ModelController.php @@ -65,15 +65,6 @@ class ModelController extends Controller $em = $this->getDoctrine()->getManager(); if($model = $this->get('manager.ldraw.model')->findByNumber($number)) { - - -// $bin = $this->get('imagine.data.loader.media')->find('/ldraw/images/'.$model->getNumber().'.png'); -// -// $path = $this->get('imagine.cache.resolver.media')->resolve('/ldraw/images/'.$model->getNumber().'.png','model'); -// -// $this->get('imagine.cache.resolver.media')->store($bin,$path,'model'); - - try { $rbParts = $model != null ? $em->getRepository(Part::class)->findAllByModel($model) : null; $sets = $model != null ? $em->getRepository(Set::class)->findAllByModel($model) : null; diff --git a/src/AppBundle/Controller/Rebrickable/InventoryController.php b/src/AppBundle/Controller/Rebrickable/InventoryController.php new file mode 100644 index 0000000..56480e9 --- /dev/null +++ b/src/AppBundle/Controller/Rebrickable/InventoryController.php @@ -0,0 +1,37 @@ +getDoctrine()->getManager(); + + $inventorySets = $em->getRepository(Set::class)->findAllByInventory($inventory); + + return $this->render('rebrickable/set/inventory_sets.html.twig', [ + 'inventorySets' => $inventorySets, + ]); + } +} diff --git a/src/AppBundle/Controller/Rebrickable/PartController.php b/src/AppBundle/Controller/Rebrickable/PartController.php index 189d329..1ca0637 100644 --- a/src/AppBundle/Controller/Rebrickable/PartController.php +++ b/src/AppBundle/Controller/Rebrickable/PartController.php @@ -19,10 +19,10 @@ class PartController extends Controller /** * Finds and displays a part entity. * - * @Route("/{number}", name="rebrickable_part_show") + * @Route("/{number}", name="reb_part_detail") * @Method("GET") */ - public function showAction(Part $part = null) + public function detailAction(Part $part) { $em = $this->getDoctrine()->getManager(); diff --git a/src/AppBundle/Controller/Rebrickable/SetController.php b/src/AppBundle/Controller/Rebrickable/SetController.php index a9483a2..b86ef22 100644 --- a/src/AppBundle/Controller/Rebrickable/SetController.php +++ b/src/AppBundle/Controller/Rebrickable/SetController.php @@ -6,6 +6,7 @@ use AppBundle\Api\Exception\EmptyResponseException; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\Rebrickable\Color; use AppBundle\Entity\Rebrickable\Inventory_Part; +use AppBundle\Entity\Rebrickable\Inventory_Set; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; use AppBundle\Entity\Rebrickable\Theme; @@ -21,87 +22,55 @@ use Symfony\Component\HttpFoundation\Response; class SetController extends Controller { /** - * @Route("/", name="set_index") + * @Route("/{number}/parts", name="rebrickable_set_parts") */ - public function indexAction(Request $request) - { + public function partsAction(Set $set) { $em = $this->getDoctrine()->getManager(); - $qb = $em->getRepository(Set::class)->createQueryBuilder('s'); - - $paginator = $this->get('knp_paginator'); - $sets = $paginator->paginate( - $qb->getQuery(), - $request->query->getInt('page', 1)/*page number*/, - $request->query->getInt('limit', 30)/*limit per page*/ - ); - - return $this->render('rebrickable/set/index.html.twig', [ - 'sets' => $sets, - ]); - } - - /** - * @Route("/detail/{number}_{name}", name="set_detail") - */ - public function detailAction(Request $request, $number, $name = null) - { - $brset = null; - try { - $brset = $this->get('api.manager.brickset')->getSetByNumber($number); - } catch (EmptyResponseException $e) { - $this->addFlash('warning', 'Set not found in Brickset database'); - } catch (\Exception $e) { - $this->addFlash('error', $e->getMessage()); - } - - $set = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number); - - $rbset = $this->get('api.manager.rebrickable')->getSet($number); - - $em = $this->getDoctrine()->getManager(); $em->getRepository(Color::class)->findAll(); - $em->getRepository(Theme::class)->findAll(); + $em->getRepository(Part::class)->findAllBySetNumber($set->getNumber()); - return $this->render('rebrickable/set/detail.html.twig', [ - 'set' => $set, - 'brset' => $brset, - 'rbset' => $rbset, - 'inventoryParts' => $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number), + $regularParts = $em->getRepository(Inventory_Part::class)->findAllRegularBySetNumber($set->getNumber()); + $spareParts = $em->getRepository(Inventory_Part::class)->findAllSpareBySetNumber($set->getNumber()); + + return $this->render('rebrickable/set/parts.html.twig', [ + 'regularParts' => $regularParts, + 'spareParts' => $spareParts, ]); } - /** - * @Route("/download/{number}", name="set_download") - */ - public function downloadZipAction(Request $request, $number) { - $em = $this->getDoctrine()->getManager(); +// /** +// * @Route("/download/{number}", name="set_download") +// */ +// public function downloadZipAction(Request $request, $number) { +// $em = $this->getDoctrine()->getManager(); +// +// $inventoryParts = $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number); +// +// $zip = new \ZipArchive(); +// $zipName = 'set_'.$number.'.zip'; +// $zip->open($zipName, \ZipArchive::CREATE); +// /** @var Inventory_Part $part */ +// foreach ($inventoryParts as $part) { +// $filename = $part->getPart()->getNumber().'_('.$part->getColor()->getName().'_'.$part->getQuantity().'x).stl'; +// +// try { +// if($part->getPart()->getModel()) { +// $zip->addFromString($filename, $this->get('oneup_flysystem.media_filesystem')->read($part->getPart()->getModel()->getPath())); +// } +// } catch (\Exception $e) { +// dump($e); +// } +// } +// $zip->close(); +// +// $response = new Response(file_get_contents($zipName)); +// $response->headers->set('Content-Type', 'application/zip'); +// $response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"'); +// $response->headers->set('Content-length', filesize($zipName)); +// +// return $response; +// } - $inventoryParts = $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number); - - $zip = new \ZipArchive(); - $zipName = 'set_'.$number.'.zip'; - $zip->open($zipName, \ZipArchive::CREATE); - /** @var Inventory_Part $part */ - foreach ($inventoryParts as $part) { - $filename = $part->getPart()->getNumber().'_('.$part->getColor()->getName().'_'.$part->getQuantity().'x).stl'; - - try { - if($part->getPart()->getModel()) { - $zip->addFromString($filename, $this->get('oneup_flysystem.media_filesystem')->read($part->getPart()->getModel()->getPath())); - } - } catch (\Exception $e) { - dump($e); - } - } - $zip->close(); - - $response = new Response(file_get_contents($zipName)); - $response->headers->set('Content-Type', 'application/zip'); - $response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"'); - $response->headers->set('Content-length', filesize($zipName)); - - return $response; - } } diff --git a/src/AppBundle/Controller/SetController.php b/src/AppBundle/Controller/SetController.php new file mode 100644 index 0000000..a2d0d90 --- /dev/null +++ b/src/AppBundle/Controller/SetController.php @@ -0,0 +1,129 @@ +getDoctrine()->getManager(); + + $qb = $em->getRepository(Set::class)->createQueryBuilder('s'); + + $paginator = $this->get('knp_paginator'); + $sets = $paginator->paginate( + $qb->getQuery(), + $request->query->getInt('page', 1)/*page number*/, + $request->query->getInt('limit', 30)/*limit per page*/ + ); + + return $this->render('set/index.html.twig', [ + 'sets' => $sets, + ]); + } + + /** + * @Route("/{number}", name="set_detail") + */ + public function detailAction(Request $request, $number) + { + $brset = null; + $rbset = null; + try { + if(($rbset = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number)) == null) { + $this->addFlash('warning', 'Set not found in Rebrickable database'); + }; + + $brset = $this->get('api.manager.brickset')->getSetByNumber($number); + } catch (EmptyResponseException $e) { + $this->addFlash('warning', 'Set not found in Brickset database'); + } catch (\Exception $e) { + $this->addFlash('error', $e->getMessage()); + } + + return $this->render('set/detail.html.twig', [ + 'rbset' => $rbset, + 'brset' => $brset, + ]); + } +// +// /** +// * @Route("/{number}/parts", name="set_parts") +// */ +// public function partsAction(Set $set) { +// $em = $this->getDoctrine()->getManager(); +// +// $em->getRepository(Color::class)->findAll(); +// $em->getRepository(Part::class)->findAllBySetNumber($set->getNumber()); +// +// $regularParts = $em->getRepository(Inventory_Part::class)->findAllRegularBySetNumber($set->getNumber()); +// $spareParts = $em->getRepository(Inventory_Part::class)->findAllSpareBySetNumber($set->getNumber()); +// +// $count = 0; +// /** @var Inventory_Part $inventoryPart */ +// foreach ($regularParts as $inventoryPart) { +// $count += $inventoryPart->getQuantity(); +// } +// +// dump($count); +// +// return $this->render('rebrickable/set/parts.html.twig', [ +// 'regularParts' => $regularParts, +// 'spareParts' => $spareParts, +// 'totalParts' => $count +// ]); +// } +// +//// /** +//// * @Route("/download/{number}", name="set_download") +//// */ +//// public function downloadZipAction(Request $request, $number) { +//// $em = $this->getDoctrine()->getManager(); +//// +//// $inventoryParts = $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number); +//// +//// $zip = new \ZipArchive(); +//// $zipName = 'set_'.$number.'.zip'; +//// $zip->open($zipName, \ZipArchive::CREATE); +//// /** @var Inventory_Part $part */ +//// foreach ($inventoryParts as $part) { +//// $filename = $part->getPart()->getNumber().'_('.$part->getColor()->getName().'_'.$part->getQuantity().'x).stl'; +//// +//// try { +//// if($part->getPart()->getModel()) { +//// $zip->addFromString($filename, $this->get('oneup_flysystem.media_filesystem')->read($part->getPart()->getModel()->getPath())); +//// } +//// } catch (\Exception $e) { +//// dump($e); +//// } +//// } +//// $zip->close(); +//// +//// $response = new Response(file_get_contents($zipName)); +//// $response->headers->set('Content-Type', 'application/zip'); +//// $response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"'); +//// $response->headers->set('Content-length', filesize($zipName)); +//// +//// return $response; +//// } + +} From ceda83225ce0eb3fb8587950f2b5a4a369bff9ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Mon, 10 Apr 2017 17:03:07 +0200 Subject: [PATCH 17/42] Add part and model relations --- app/Resources/relations/alias_model.yml | 18 +++++++- app/Resources/relations/part_model.yml | 58 +++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/app/Resources/relations/alias_model.yml b/app/Resources/relations/alias_model.yml index c602122..1e763f0 100644 --- a/app/Resources/relations/alias_model.yml +++ b/app/Resources/relations/alias_model.yml @@ -46,6 +46,8 @@ 4168950: 3815c01 73418: 3815c01 +82359: 3626b + 122c02: 122c01 # Bucket 1 x 1 x 1 Cylindrical @@ -164,4 +166,18 @@ u588p02c02: u588p01c02 # Hinge Control Stick and Base 73587: 4592c01 -4296152: 4592c01 \ No newline at end of file +4296152: 4592c01 + +4505: 4505a +6093: 6093a + +90462a: 90462 + +# Minifig Mechanical SW Battle Droid +30375cs1: 30375cs0 +30375cs2: 30375cs0 +30375cs3: 30375cs0 + +2958c01: 2723 + +32181c03: 32181c01 \ No newline at end of file diff --git a/app/Resources/relations/part_model.yml b/app/Resources/relations/part_model.yml index e8811a3..3d38b35 100644 --- a/app/Resources/relations/part_model.yml +++ b/app/Resources/relations/part_model.yml @@ -21,9 +21,6 @@ 970d09: 3815c36 970d12: 3815c36 -973pr2303c01: 973c00 -973pr2302c01: 973c00 - 57503: 70501a 90391: 90391p01 @@ -159,4 +156,57 @@ wheel2a: 568c01 15672: 92946 3614a: 3614 -3614b: 3614 \ No newline at end of file +3614b: 3614 + +75215: 4694c01 + +6093: 6093a + +3626bpr0895: 82359 + +6272c01: 6272 + +76320: 32181c02 + +76320: 32181c02 + +58123b: 58123p01 + +95292c01: 75348 +2909c03: 75348 +2909c02: 75348 + +# Technic figures +tech001: 2698c01 +tech002: 2698c01 +tech003: 2698c01 +tech004: 2698c01 +tech005: 2698c01 +tech006: 2698c01 +tech007: 2698c01 +tech008: 2698c01 +tech009: 2698c01 +tech010: 2698c01 +tech011: 2698c01 +tech011a: 2698c01 +tech012: 2698c01 +tech013: 2698c01 +tech014: 2698c01 +tech014a: 2698c01 +tech015: 2698c01 +tech016: 2698c01 +tech016a: 2698c01 +tech017: 2698c01 +tech017a: 2698c01 +tech018: 2698c01 +tech019: 2698c01 +tech020: 2698c01 +tech021: 2698c01 +tech022: 2698c01 +tech023: 2698c01 +tech024: 2698c01 +tech025: 2698c01 +tech026: 2698c01 +tech027: 2698c01 +tech028: 2698c01 +tech029: 2698c01 From 814138aeb2299563ca30cc8c86e9808996c138b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Tue, 11 Apr 2017 17:27:02 +0200 Subject: [PATCH 18/42] Improve Dat Parser --- src/AppBundle/Utils/DatParser.php | 98 +++++++++++++++---------------- 1 file changed, 46 insertions(+), 52 deletions(-) diff --git a/src/AppBundle/Utils/DatParser.php b/src/AppBundle/Utils/DatParser.php index f80bc16..99f4b84 100644 --- a/src/AppBundle/Utils/DatParser.php +++ b/src/AppBundle/Utils/DatParser.php @@ -3,6 +3,7 @@ namespace AppBundle\Utils; use AppBundle\Exception\FileNotFoundException; +use AppBundle\Exception\ParseErrorException; use League\Flysystem\File; use Symfony\Component\Asset\Exception\LogicException; @@ -25,12 +26,23 @@ class DatParser * LDraw.org Standards: Official Library Header Specification (http://www.ldraw.org/article/398.html) * * @return array + * @throws FileNotFoundException|ParseErrorException */ public function parse($file) { - $header = []; - if(file_exists($file)) { + $model = [ + 'id' => null, + 'name' => null, + 'category' => null, + 'keywords' => [], + 'author' => null, + 'modified' => null, + 'type' => null, + 'subparts' => [], + 'parent' => null + ]; + try { $handle = fopen($file, 'r'); @@ -47,86 +59,68 @@ class DatParser // 0 if (!$firstLine) { $array = explode(' ', ltrim(trim($line, 2), '=_~')); - $header['category'] = isset($array[0]) ? $array[0] : ''; - $header['name'] = preg_replace('/ {2,}/', ' ', ltrim($line, '=_')); + $model['category'] = isset($array[0]) ? $array[0] : ''; + $model['name'] = preg_replace('/ {2,}/', ' ', ltrim($line, '=_')); $firstLine = true; } // 0 !CATEGORY elseif (strpos($line, '!CATEGORY ') === 0) { - $header['category'] = trim(preg_replace('/^!CATEGORY /', '', $line)); + $model['category'] = trim(preg_replace('/^!CATEGORY /', '', $line)); } // 0 !KEYWORDS , , ..., elseif (strpos($line, '!KEYWORDS ') === 0) { - $header['keywords'] = explode(', ', preg_replace('/^!KEYWORDS /', '', $line)); + $model['keywords'] = explode(', ', preg_replace('/^!KEYWORDS /', '', $line)); } // 0 Name: .dat - elseif (strpos($line, 'Name: ') === 0) { - if (!isset($header['id'])) { - $header['id'] = preg_replace('/(^Name: )(.*)(.dat)/', '$2', $line); - } + elseif (strpos($line, 'Name: ') === 0 && !isset($header['id'])) { + $model['id'] = preg_replace('/(^Name: )(.*)(.dat)/', '$2', $line); } // 0 Author: [] elseif (strpos($line, 'Author: ') === 0) { - $header['author'] = preg_replace('/^Author: /', '', $line); + $model['author'] = preg_replace('/^Author: /', '', $line); } // 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR elseif (strpos($line, '!LDRAW_ORG ') === 0) { $type = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE| ORIGINAL)(.*)/', '$2', $line); - $header['type'] = $type; + $model['type'] = $type; // Last modification date in format YYYY-RR $date = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE | ORIGINAL )(.*)/', '$4', $line); if (preg_match('/^[1-2][0-9]{3}-[0-9]{2}$/', $date)) { - $header['modified'] = \DateTime::createFromFormat('Y-m-d H:i:s', $date . '-01 00:00:00'); - } else { - $header['modified'] = null; + $model['modified'] = \DateTime::createFromFormat('Y-m-d H:i:s', $date . '-01 00:00:00'); } } } elseif (strpos($line, '1 ') === 0) { - $id = $this->getAlias($line); + $id = $this->getReferencedModelNumber($line); - if(isset($header['subparts'][$id])) { - $header['subparts'][$id] = $header['subparts'][$id] + 1; + if(isset($model['subparts'][$id])) { + $model['subparts'][$id] = $model['subparts'][$id] + 1; } else { - $header['subparts'][$id] = 1; + $model['subparts'][$id] = 1; } } } - if ($this->isStickerShortcutPart($header['name'], $header['id'])) { - $header['type'] = 'Sticker'; - } elseif (isset($header['subparts']) && count($header['subparts']) == 1 && in_array($header['type'], ['Part Alias', 'Shortcut Physical_Colour', 'Shortcut Alias', 'Part Physical_Colour'])) { - $header['parent'] = array_keys($header['subparts'])[0]; - $header['subparts'] = null; - } elseif ($parent = $this->getPrintedParentId($header['id'])) { - $header['type'] = 'Printed'; - $header['subparts'] = null; - $header['parent'] = $parent; - } elseif ($parent = $this->getObsoleteParentId($header['name'])) { - $header['type'] = 'Alias'; - $header['subparts'] = null; - $header['parent'] = $parent; - } elseif (strpos($header['name'], '~') === 0 && $header['type'] != 'Alias') { - $header['type'] = 'Obsolete/Subpart'; + if ($this->isSticker($model['name'], $model['id'])) { + $model['type'] = 'Sticker'; + } elseif (count($model['subparts']) == 1 && in_array($model['type'], ['Part Alias', 'Shortcut Physical_Colour', 'Shortcut Alias', 'Part Physical_Colour'])) { + $model['parent'] = array_keys($model['subparts'])[0]; + } elseif ($parent = $this->getPrintedModelParentNumber($model['id'])) { + $model['type'] = 'Printed'; + $model['parent'] = $parent; + } elseif ($parent = $this->getObsoleteModelParentNumber($model['name'])) { + $model['type'] = 'Alias'; + $model['parent'] = $parent; + } elseif (strpos($model['name'], '~') === 0 && $model['type'] != 'Alias') { + $model['type'] = 'Obsolete/Subpart'; } - if(!isset($header['type'])) { - $header['type'] = 'Unknown'; - } - - if(!isset($header['modified'])) { - $header['modified'] = null; - } - -// $header['name'] = ltrim($header['name'], '~'); - fclose($handle); - return $header; + return $model; } } catch (\Exception $exception) { - dump($exception); - throw new LogicException('Error parsing '.$file); + throw new ParseErrorException($file); } } - throw new LogicException('File not found '.$file); + throw new FileNotFoundException($file); } /** @@ -141,7 +135,7 @@ class DatParser * * @return string|null Filename of referenced part */ - public function getAlias($line) + public function getReferencedModelNumber($line) { if(preg_match('/^1 16 0 0 0 -1 0 0 0 1 0 0 0 1 (.*)\.(dat|DAT)$/', $line, $matches)) return null; @@ -164,7 +158,7 @@ class DatParser * * @return string|null LDraw number of printed part parent */ - public function getPrintedParentId($id) + public function getPrintedModelParentNumber($id) { if (preg_match('/(^.*)(p[0-9a-z][0-9a-z][0-9a-z]{0,1})$/', $id, $matches)) { return $matches[1]; @@ -186,7 +180,7 @@ class DatParser * * @return string|null LDraw number of printed part parent */ - public function isStickerShortcutPart($name, $number) + public function isSticker($name, $number) { if (strpos($name, 'Sticker') === 0) { return true; @@ -208,7 +202,7 @@ class DatParser * * @return string|null Filename of referenced part */ - public function getObsoleteParentId($name) + public function getObsoleteModelParentNumber($name) { if (preg_match('/^(~Moved to )(.*)$/', $name, $matches)) { return $matches[2]; From 59eff0e0bd8638a16a1ee40a30e14e55fc7b7359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Tue, 11 Apr 2017 17:28:29 +0200 Subject: [PATCH 19/42] Add loader exceptions --- app/config/service/loader.yml | 2 +- .../Exception/ConvertingFailedException.php | 36 +++++++++++++++++++ .../Exception/FileNotFoundException.php | 8 +---- .../Exception/ParseErrorException.php | 32 +++++++++++++++++ .../Rebrickable/InventoryRepository.php | 11 ++++++ src/AppBundle/Service/LDViewService.php | 19 +++++----- 6 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 src/AppBundle/Exception/ConvertingFailedException.php create mode 100644 src/AppBundle/Exception/ParseErrorException.php diff --git a/app/config/service/loader.yml b/app/config/service/loader.yml index 583be0c..2fb30a7 100644 --- a/app/config/service/loader.yml +++ b/app/config/service/loader.yml @@ -7,7 +7,7 @@ services: service.ldview: class: AppBundle\Service\LDViewService - arguments: ['%ldview_bin%', '@oneup_flysystem.media_filesystem', '@oneup_flysystem.ldraw_library_filesystem'] + arguments: ['%ldview_bin%', '@oneup_flysystem.media_filesystem'] service.loader.rebrickable: class: AppBundle\Service\Loader\RebrickableLoaderService diff --git a/src/AppBundle/Exception/ConvertingFailedException.php b/src/AppBundle/Exception/ConvertingFailedException.php new file mode 100644 index 0000000..d16c2be --- /dev/null +++ b/src/AppBundle/Exception/ConvertingFailedException.php @@ -0,0 +1,36 @@ +filepath = $filepath; + } + + /** + * @return mixed + */ + public function getFilepath() + { + return $this->filepath; + } + + /** + * @param mixed $filepath + */ + public function setFilepath($filepath) + { + $this->filepath = $filepath; + } + + +} \ No newline at end of file diff --git a/src/AppBundle/Exception/FileNotFoundException.php b/src/AppBundle/Exception/FileNotFoundException.php index dbacf85..3e12a65 100644 --- a/src/AppBundle/Exception/FileNotFoundException.php +++ b/src/AppBundle/Exception/FileNotFoundException.php @@ -1,15 +1,9 @@ filepath = $filepath; + } + + /** + * @return mixed + */ + public function getFilepath() + { + return $this->filepath; + } + + /** + * @param mixed $filepath + */ + public function setFilepath($filepath) + { + $this->filepath = $filepath; + } +} \ No newline at end of file diff --git a/src/AppBundle/Repository/Rebrickable/InventoryRepository.php b/src/AppBundle/Repository/Rebrickable/InventoryRepository.php index 6776c3b..0158186 100644 --- a/src/AppBundle/Repository/Rebrickable/InventoryRepository.php +++ b/src/AppBundle/Repository/Rebrickable/InventoryRepository.php @@ -2,8 +2,19 @@ namespace AppBundle\Repository\Rebrickable; +use AppBundle\Entity\Rebrickable\Set; use AppBundle\Repository\BaseRepository; class InventoryRepository extends BaseRepository { + public function findNewestInventoryBySetNumber($number) { + + $queryBuilder = $this->createQueryBuilder('inventory') + ->where('inventory.set = :setNumber') + ->setParameter('setNumber', $number) + ->orderBy('inventory.version','DESC') + ->setMaxResults(1); + + return $queryBuilder->getQuery()->getOneOrNullResult(); + } } diff --git a/src/AppBundle/Service/LDViewService.php b/src/AppBundle/Service/LDViewService.php index 033dbb5..8f1631f 100644 --- a/src/AppBundle/Service/LDViewService.php +++ b/src/AppBundle/Service/LDViewService.php @@ -2,10 +2,13 @@ namespace AppBundle\Service; +use AppBundle\Exception\ConvertingFailedException; +use AppBundle\Exception\FileNotFoundException; use League\Flysystem\File; use League\Flysystem\Filesystem; use Symfony\Component\Asset\Exception\LogicException; use Symfony\Component\Finder\Finder; +use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\ProcessBuilder; //TODO enable file overwrite @@ -33,13 +36,11 @@ class LDViewService * * @param string $ldview Path to LDView OSMesa binary file * @param Filesystem $mediaFilesystem Filesystem for generated web assets - * @param Filesystem $ldrawLibraryFilesystem Filesystem with ldraw source files library */ - public function __construct($ldview, $mediaFilesystem, $ldrawLibraryFilesystem) + public function __construct($ldview, $mediaFilesystem) { $this->ldview = $ldview; $this->mediaFilesystem = $mediaFilesystem; - $this->ldrawLibraryFilesystem = $ldrawLibraryFilesystem; } /** @@ -54,9 +55,10 @@ class LDViewService * Convert LDraw model from .dat format to .stl by using LDView * stores created file to $stlStorage filesystem. * - * @param Filesystem $LDrawDir + * @param $file * * @return File + * @throws ConvertingFailedException */ public function datToStl($file) { @@ -79,7 +81,7 @@ class LDViewService // Check if file created successfully if (!$this->mediaFilesystem->has($newFile)) { - throw new LogicException($newFile.': new file not found'); //TODO + throw new ConvertingFailedException($newFile); } } @@ -90,9 +92,10 @@ class LDViewService * Convert LDraw model from .dat format to .stl by using LDView * stores created file to $stlStorage filesystem. * - * @param Filesystem $LDrawDir + * @param $file * * @return File + * @throws ConvertingFailedException */ public function datToPng($file) { @@ -124,7 +127,7 @@ class LDViewService // Check if file created successfully if (!$this->mediaFilesystem->has($newFile)) { - throw new LogicException($newFile.': new file not found'); //TODO + throw new ConvertingFailedException($newFile); } } @@ -147,7 +150,7 @@ class LDViewService $process->run(); if (!$process->isSuccessful()) { - throw new LogicException($process->getOutput()); //TODO + throw new ProcessFailedException($process); //TODO } } } From 794202d05de2ae626ba2d3d48f8b94aa941468b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Tue, 11 Apr 2017 17:30:20 +0200 Subject: [PATCH 20/42] Move ldraw dir dependency from config to command --- app/config/config.yml | 7 +- app/config/parameters.yml.dist | 5 +- app/config/service/loader.yml | 6 +- src/AppBundle/Command/LoadModelsCommand.php | 37 +++-- src/AppBundle/Manager/BaseManager.php | 13 +- src/AppBundle/Manager/RebrickableManager.php | 12 +- ...aderService.php => ModelLoaderService.php} | 144 ++++++++---------- 7 files changed, 111 insertions(+), 113 deletions(-) rename src/AppBundle/Service/Loader/{LDrawLoaderService.php => ModelLoaderService.php} (56%) diff --git a/app/config/config.yml b/app/config/config.yml index 89fd475..58be23c 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -142,11 +142,6 @@ oneup_flysystem: media: local: directory: "%kernel.root_dir%/../var/media/" - ldraw_library: - local: - directory: "%ldraw_library%" filesystems: media: - adapter: media - ldraw_library: - adapter: ldraw_library \ No newline at end of file + adapter: media \ No newline at end of file diff --git a/app/config/parameters.yml.dist b/app/config/parameters.yml.dist index 451dd92..be71996 100644 --- a/app/config/parameters.yml.dist +++ b/app/config/parameters.yml.dist @@ -25,7 +25,4 @@ parameters: rebrickable_apikey: ~ # Absolute path to OSMesa port of LDView - ldview_bin: /usr/bin/ldview - - # Path to LDraw library - ldraw_library: ~ \ No newline at end of file + ldview_bin: /usr/bin/ldview \ No newline at end of file diff --git a/app/config/service/loader.yml b/app/config/service/loader.yml index 2fb30a7..30b4804 100644 --- a/app/config/service/loader.yml +++ b/app/config/service/loader.yml @@ -14,9 +14,9 @@ services: arguments: ['%rebrickable_url%'] parent: service.loader - service.loader.ldraw: - class: AppBundle\Service\Loader\LDrawLoaderService - arguments: ['@oneup_flysystem.ldraw_library_filesystem','@service.ldview', '@manager.ldraw', '@app.relation.mapper'] + service.loader.model: + class: AppBundle\Service\Loader\ModelLoaderService + arguments: ['@service.ldview', '@manager.ldraw', '@app.relation.mapper'] parent: service.loader service.loader.relation: diff --git a/src/AppBundle/Command/LoadModelsCommand.php b/src/AppBundle/Command/LoadModelsCommand.php index f206e47..c8ac9f1 100644 --- a/src/AppBundle/Command/LoadModelsCommand.php +++ b/src/AppBundle/Command/LoadModelsCommand.php @@ -3,6 +3,7 @@ namespace AppBundle\Command; use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; +use Symfony\Component\Console\Command\LockableTrait; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputInterface; @@ -11,6 +12,8 @@ use Symfony\Component\Console\Output\OutputInterface; class LoadModelsCommand extends ContainerAwareCommand { + use LockableTrait; + protected function configure() { $this @@ -18,35 +21,45 @@ class LoadModelsCommand extends ContainerAwareCommand ->setDescription('Loads LDraw library models into database') ->setHelp('This command allows you to load LDraw library models into while converting .dat files to .stl') ->setDefinition( - new InputDefinition(array( - new InputOption('images', 'i'), - new InputOption('ldraw', 'l', InputOption::VALUE_REQUIRED), - new InputOption('file', 'f', InputOption::VALUE_REQUIRED), - )) + new InputDefinition([ + new InputArgument('ldraw', InputArgument::REQUIRED, 'Path to LDraw library directory'), + new InputOption('images', 'i',InputOption::VALUE_NONE, 'Do you want to generate images of models?'), + new InputOption('all','a',InputOption::VALUE_NONE, 'Do you want to load whole LDraw libary?'), + new InputOption('file','f',InputOption::VALUE_REQUIRED, 'Path to DAT file that should be loaded into database') + ]) ); } + //TODO log errors protected function execute(InputInterface $input, OutputInterface $output) { - $ldrawLoader = $this->getContainer()->get('service.loader.ldraw'); - $ldrawLoader->setOutput($output); + if (!$this->lock()) { + $output->writeln('The command is already running in another process.'); + + return 0; + } + + $ldrawLoader = $this->getContainer()->get('service.loader.model'); + $ldrawLoader->setOutput($output); + $ldrawLoader->setLDrawLibraryContext(realpath($input->getArgument('ldraw'))); - //TODO log errors try { - // TODO handle relative path to dir if (($ldrawPath = $input->getOption('file')) != null) { - $ldrawLoader->loadFileContext($ldrawPath); + $ldrawLoader->loadFileContext(dirname(realpath($ldrawPath))); $model = $ldrawLoader->loadModel($ldrawPath); if($model !== null) { $this->getContainer()->get('manager.ldraw.model')->getRepository()->save($model); } - } else { - $ldrawLoader->loadAllModels(); + } + if ($input->getOption('all')) { + $ldrawLoader->loadAllModels(); } } catch (\Exception $e) { printf($e->getMessage()); } + + $this->release(); } } diff --git a/src/AppBundle/Manager/BaseManager.php b/src/AppBundle/Manager/BaseManager.php index cfeb49c..df466fe 100644 --- a/src/AppBundle/Manager/BaseManager.php +++ b/src/AppBundle/Manager/BaseManager.php @@ -2,15 +2,26 @@ namespace AppBundle\Manager; +use AppBundle\Repository\BaseRepository; +use Doctrine\ORM\EntityManager; + class BaseManager { + /** @var EntityManager $em */ + protected $em; + protected $repository; /** - * @return mixed + * @return BaseRepository */ public function getRepository() { return $this->repository; } + + public function setEntityManager(EntityManager $entityManager) + { + $this->em = $entityManager; + } } diff --git a/src/AppBundle/Manager/RebrickableManager.php b/src/AppBundle/Manager/RebrickableManager.php index 9896d53..38e2fc6 100644 --- a/src/AppBundle/Manager/RebrickableManager.php +++ b/src/AppBundle/Manager/RebrickableManager.php @@ -1,18 +1,12 @@ em->getRepository(Theme::class)->findAll(); } } diff --git a/src/AppBundle/Service/Loader/LDrawLoaderService.php b/src/AppBundle/Service/Loader/ModelLoaderService.php similarity index 56% rename from src/AppBundle/Service/Loader/LDrawLoaderService.php rename to src/AppBundle/Service/Loader/ModelLoaderService.php index 08679fd..2d5ba87 100644 --- a/src/AppBundle/Service/Loader/LDrawLoaderService.php +++ b/src/AppBundle/Service/Loader/ModelLoaderService.php @@ -5,22 +5,23 @@ namespace AppBundle\Service\Loader; use AppBundle\Entity\LDraw\Alias; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\LDraw\Type; +use AppBundle\Exception\ConvertingFailedException; use AppBundle\Manager\LDrawManager; use AppBundle\Service\LDViewService; use AppBundle\Utils\DatParser; use AppBundle\Utils\RelationMapper; use League\Flysystem\Adapter\Local; use League\Flysystem\Filesystem; - -//use Symfony\Component\Asset\Exception\LogicException; +use Symfony\Component\Finder\Finder; +use Symfony\Component\Finder\SplFileInfo; //TODO refactor -class LDrawLoaderService extends BaseLoaderService +class ModelLoaderService extends BaseLoaderService { /** * @var Filesystem */ - private $ldrawLibrary; + private $ldrawLibraryContext; /** * @var Filesystem @@ -43,57 +44,54 @@ class LDrawLoaderService extends BaseLoaderService /** @var RelationMapper */ protected $relationMapper; + /** @var Finder */ + private $finder; + /** * LDrawLoaderService constructor. - * @param Filesystem $ldrawLibraryFilesystem * @param LDViewService $LDViewService * @param LDrawManager $ldrawService * @param RelationMapper $relationMapper */ - public function __construct($ldrawLibraryFilesystem, $LDViewService, $ldrawService, $relationMapper) + public function __construct($LDViewService, $ldrawService, $relationMapper) { $this->LDViewService = $LDViewService; $this->ldrawService = $ldrawService; - $this->datParser = new DatParser(); - $this->ldrawLibrary = $ldrawLibraryFilesystem; $this->relationMapper = $relationMapper; + + $this->datParser = new DatParser(); + $this->finder = new Finder(); } - private function dumpModel($model, $level = 1) { - $level = $level + 1; - dump(str_repeat("-", 2*$level).'> '.$model->getNumber()); - foreach ($model->getSubparts() as $subpart) { - $this->dumpModel($subpart->getSubpart(), $level); - } + public function setLDrawLibraryContext($ldrawLibrary) + { + $adapter = new Local($ldrawLibrary); + $this->ldrawLibraryContext = new Filesystem($adapter); + + $this->LDViewService->setLdrawFilesystem($this->ldrawLibraryContext); + } + + public function loadFileContext($file) { + $adapter = new Local($file); + $this->fileContext = new Filesystem($adapter); } public function loadAllModels() { - $files = $this->ldrawLibrary->get('parts')->getContents(); + $files = $this->finder->in(['/home/hubnedav/Documents/ldraw'])->path('parts/')->name('*.dat')->depth(1)->files(); + $modelManager = $this->ldrawService->getModelManager(); - $this->initProgressBar(count($files)); + $this->initProgressBar($files->count()); + /** @var SplFileInfo $file */ foreach ($files as $file) { - if ($file['type'] == 'file' && $file['extension'] == 'dat') { + $this->newModels = []; - $this->newModels = []; + $model = $this->loadModel($file->getRealPath()); - $model = $this->loadModel($this->ldrawLibrary->getAdapter()->getPathPrefix().$file['path']); - - if($model !== null) { -// dump($model->getNumber()); - try { - $modelManager->getRepository()->save($model); - } catch (\Exception $exception) { - dump($exception); -// dump($model); - - $this->dumpModel($model); - - exit(); - } - } + if($model !== null) { + $modelManager->getRepository()->save($model); } $this->progressBar->advance(); @@ -116,33 +114,22 @@ class LDrawLoaderService extends BaseLoaderService $subpartManager = $this->ldrawService->getSubpartManager(); if(($model = $modelManager->findByNumber(basename($file,'.dat'))) || ($model = isset($this->newModels[basename($file,'.dat')]) ? $this->newModels[basename($file,'.dat')] : null)) { - $this->LDViewService->datToPng($file); return $model; } try { - $header = $this->datParser->parse($file); + $modelArray = $this->datParser->parse($file); } catch (\Exception $e) { - dump($e); + dump($e->getMessage()); return null; } - if ($this->isModelIncluded($header)) { + if ($this->isModelIncluded($modelArray)) { - if($this->relationMapper->find($header['id'], 'alias_model') !== $header['id']) { - $parentFile = $this->findModelFile($this->relationMapper->find($header['id'], 'alias_model')); - } else { - $parentFile = isset($header['parent']) && strpos($header['parent'], 's\\') === false ? $this->findModelFile($header['parent']) : null; - } - - if ($parentFile) { + if ($parentModelFile = $this->getParentModelFile($modelArray)) { try { - $parentModel = $this->loadModel($parentFile); - - if($parentModel) { - $alias = new Alias(); - $alias->setNumber($header['id']); - $alias->setModel($parentModel); + if(($parentModel = $this->loadModel($parentModelFile))!= null) { + $alias = $this->ldrawService->getAliasManager()->create($modelArray['id'], $parentModel); $parentModel->addAlias($alias); $this->newModels[$parentModel->getNumber()] = $parentModel; @@ -154,26 +141,23 @@ class LDrawLoaderService extends BaseLoaderService return null; } } else { - $model = $modelManager->create($header['id']); - $model->setName($header['name']); - $model->setCategory($this->ldrawService->getCategoryManager()->create($header['category'])); + $model = $modelManager->create($modelArray['id']); - if (isset($header['keywords'])) { - foreach ($header['keywords'] as $keyword) { + if (isset($modelArray['keywords'])) { + foreach ($modelArray['keywords'] as $keyword) { $keyword = stripslashes(strtolower(trim($keyword))); $model->addKeyword($this->ldrawService->getKeywordManager()->create($keyword)); } } - if (isset($header['subparts'])) { - foreach ($header['subparts'] as $subpartId => $count) { + if (isset($modelArray['subparts'])) { + foreach ($modelArray['subparts'] as $subpartId => $count) { if(strpos($subpartId, 's\\') === false) { if(($subpartFile = $this->findModelFile($subpartId)) != null) { try { - $subModel = $this->loadModel($subpartFile); - - if ($subModel) { + if ($subModel = $this->loadModel($subpartFile)) { $subpart = $subpartManager->create($model,$subModel,$count); + $model->addSubpart($subpart); } } catch (\Exception $e) { @@ -185,9 +169,14 @@ class LDrawLoaderService extends BaseLoaderService } } - $model->setAuthor($header['author']); - $model->setModified($header['modified']); - $model->setPath($this->loadStlModel($file)); + $model + ->setName($modelArray['name']) + ->setCategory($this->ldrawService->getCategoryManager()->create($modelArray['category'])) + ->setAuthor($modelArray['author']) + ->setModified($modelArray['modified']) + ->setPath($this->loadStlModel($file)); + + $this->LDViewService->datToPng($file); $this->newModels[$model->getNumber()] = $model; } @@ -196,6 +185,14 @@ class LDrawLoaderService extends BaseLoaderService return $model; } + private function getParentModelFile($modelArray) { + if($this->relationMapper->find($modelArray['id'], 'alias_model') !== $modelArray['id']) { + return $this->findModelFile($this->relationMapper->find($modelArray['id'], 'alias_model')); + } else { + return strpos($modelArray['parent'], 's\\') === false ? $this->findModelFile($modelArray['parent']) : null; + } + } + /** * Find model file on ldraw filesystem. * @@ -209,33 +206,24 @@ class LDrawLoaderService extends BaseLoaderService if($this->fileContext && $this->fileContext->has($filename)) { return $this->fileContext->getAdapter()->getPathPrefix().$filename; - } else if ($this->ldrawLibrary->has('parts/'.$filename)) { - return $this->ldrawLibrary->getAdapter()->getPathPrefix().'parts/'.$filename; + } else if ($this->ldrawLibraryContext->has('parts/'.$filename)) { + return $this->ldrawLibraryContext->getAdapter()->getPathPrefix().'parts/'.$filename; } return null; } - public function loadFileContext($file) { - $adapter = new Local(dirname(realpath($file))); - $this->fileContext = new Filesystem($adapter); - } - /** * Determine if model file should be loaded into database. * - * @param $header + * @param $modelArray * * @return bool */ - private function isModelIncluded($header) + private function isModelIncluded($modelArray) { // Do not include sticker parts and incomplete parts - if ( - $header['type'] != 'Subpart' - && $header['type'] != 'Sticker' -// && !(($header['type'] == 'Printed') && $this->findModelFile($header['parent'])) - ) { + if ( $modelArray['type'] != 'Subpart' && $modelArray['type'] != 'Sticker' ) { return true; } @@ -255,7 +243,7 @@ class LDrawLoaderService extends BaseLoaderService { try { return $this->LDViewService->datToStl($file)->getPath(); - } catch (\Exception $e) { + } catch (ConvertingFailedException $e) { throw $e; //TODO } } From 3135105d0bca1bb0800b4330e621c250a552419f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Tue, 11 Apr 2017 17:31:09 +0200 Subject: [PATCH 21/42] Remove forgotten type Repository --- src/AppBundle/Repository/LDraw/ModelRepository.php | 13 ++----------- src/AppBundle/Repository/LDraw/TypeRepository.php | 13 ------------- 2 files changed, 2 insertions(+), 24 deletions(-) delete mode 100644 src/AppBundle/Repository/LDraw/TypeRepository.php diff --git a/src/AppBundle/Repository/LDraw/ModelRepository.php b/src/AppBundle/Repository/LDraw/ModelRepository.php index 0cd4e99..7eeae5e 100644 --- a/src/AppBundle/Repository/LDraw/ModelRepository.php +++ b/src/AppBundle/Repository/LDraw/ModelRepository.php @@ -2,10 +2,10 @@ namespace AppBundle\Repository\LDraw; +use AppBundle\Entity\LDraw\Category; use AppBundle\Entity\Rebrickable\Set; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\LDraw\Alias; -use AppBundle\Entity\LDraw\Type; use AppBundle\Entity\Rebrickable\Inventory; use AppBundle\Entity\Rebrickable\Inventory_Part; use AppBundle\Repository\BaseRepository; @@ -13,19 +13,10 @@ use Doctrine\ORM\Query\Expr\Join; class ModelRepository extends BaseRepository { - public function findAllByType($type) - { - $queryBuilder = $this->createQueryBuilder('model') - ->join(Type::class, 'type', Join::LEFT_JOIN, 'model.type = :type') - ->setParameter('type', $type); - - return $queryBuilder->getQuery(); - } - public function findAllByCategory($category) { $queryBuilder = $this->createQueryBuilder('model') - ->join(Type::class, 'type', Join::LEFT_JOIN, 'model.category = :category') + ->join(Category::class, 'type', Join::LEFT_JOIN, 'model.category = :category') ->setParameter('category', $category); return $queryBuilder->getQuery(); diff --git a/src/AppBundle/Repository/LDraw/TypeRepository.php b/src/AppBundle/Repository/LDraw/TypeRepository.php deleted file mode 100644 index a4acdba..0000000 --- a/src/AppBundle/Repository/LDraw/TypeRepository.php +++ /dev/null @@ -1,13 +0,0 @@ -findOneBy(['name' => $name]); - } -} From c755f0f41846c6a70a1014b28ab827c7e46dc07d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Tue, 11 Apr 2017 17:31:44 +0200 Subject: [PATCH 22/42] Fix Inventory_Set entity --- .../Entity/Rebrickable/Inventory.php | 25 +++++++++++++++++++ .../Entity/Rebrickable/Inventory_Set.php | 2 +- src/AppBundle/Entity/Rebrickable/Set.php | 1 + .../Loader/RebrickableLoaderService.php | 5 ++-- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/AppBundle/Entity/Rebrickable/Inventory.php b/src/AppBundle/Entity/Rebrickable/Inventory.php index 36777df..3a8efe8 100644 --- a/src/AppBundle/Entity/Rebrickable/Inventory.php +++ b/src/AppBundle/Entity/Rebrickable/Inventory.php @@ -38,9 +38,18 @@ class Inventory */ protected $inventoryParts; + /** + * @var Collection + * + * @ORM\OneToMany(targetEntity="AppBundle\Entity\Rebrickable\Inventory_Set", mappedBy="inventory") + */ + protected $inventorySets; + + public function __construct() { $this->inventoryParts = new ArrayCollection(); + $this->inventorySets = new ArrayCollection(); } /** @@ -90,4 +99,20 @@ class Inventory { $this->inventoryParts = $inventoryParts; } + + /** + * @return Collection + */ + public function getInventorySets() + { + return $this->inventorySets; + } + + /** + * @param Collection $inventorySets + */ + public function setInventorySets($inventorySets) + { + $this->inventorySets = $inventorySets; + } } diff --git a/src/AppBundle/Entity/Rebrickable/Inventory_Set.php b/src/AppBundle/Entity/Rebrickable/Inventory_Set.php index 13d8acd..bb2e67f 100644 --- a/src/AppBundle/Entity/Rebrickable/Inventory_Set.php +++ b/src/AppBundle/Entity/Rebrickable/Inventory_Set.php @@ -15,7 +15,7 @@ class Inventory_Set /** * @var Inventory * @ORM\Id - * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Rebrickable\Inventory", inversedBy="inventoryParts") + * @ORM\ManyToOne(targetEntity="AppBundle\Entity\Rebrickable\Inventory", inversedBy="inventorySets") */ protected $inventory; diff --git a/src/AppBundle/Entity/Rebrickable/Set.php b/src/AppBundle/Entity/Rebrickable/Set.php index 74d567c..7a56ff4 100644 --- a/src/AppBundle/Entity/Rebrickable/Set.php +++ b/src/AppBundle/Entity/Rebrickable/Set.php @@ -60,6 +60,7 @@ class Set public function __construct() { $this->inventories = new ArrayCollection(); + $this->inventorySets = new ArrayCollection(); } /** diff --git a/src/AppBundle/Service/Loader/RebrickableLoaderService.php b/src/AppBundle/Service/Loader/RebrickableLoaderService.php index 0af4394..5f9c0a4 100644 --- a/src/AppBundle/Service/Loader/RebrickableLoaderService.php +++ b/src/AppBundle/Service/Loader/RebrickableLoaderService.php @@ -45,8 +45,9 @@ class RebrickableLoaderService extends BaseLoaderService private function truncateTables() { - $query = - 'TRUNCATE TABLE rebrickable_inventory_parts; + $query =' + TRUNCATE TABLE rebrickable_inventory_parts; + TRUNCATE TABLE rebrickable_inventory_sets; TRUNCATE TABLE rebrickable_color; TRUNCATE TABLE rebrickable_inventory; TRUNCATE TABLE rebrickable_set; From 23acea2a1861bb86b327a907ce8e7e878916df81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Tue, 11 Apr 2017 17:32:45 +0200 Subject: [PATCH 23/42] Add Set filter form, Move Model filter --- app/config/service/form.yml | 17 +++++- app/config/service/manager.yml | 12 +++- app/config/service/service.yml | 6 -- .../Controller/Brickset/SetController.php | 16 +++-- src/AppBundle/Controller/ModelController.php | 4 +- src/AppBundle/Controller/SetController.php | 22 ++++++- .../Filter/{ => Model}/CategoryFilterType.php | 2 +- .../Filter/{ => Model}/ModelFilterType.php | 2 +- .../Form/Filter/Set/SetFilterType.php | 58 +++++++++++++++++++ .../Form/Filter/Set/ThemeFilterType.php | 58 +++++++++++++++++++ 10 files changed, 174 insertions(+), 23 deletions(-) rename src/AppBundle/Form/Filter/{ => Model}/CategoryFilterType.php (97%) rename src/AppBundle/Form/Filter/{ => Model}/ModelFilterType.php (98%) create mode 100644 src/AppBundle/Form/Filter/Set/SetFilterType.php create mode 100644 src/AppBundle/Form/Filter/Set/ThemeFilterType.php diff --git a/app/config/service/form.yml b/app/config/service/form.yml index 8dcc806..5693b10 100644 --- a/app/config/service/form.yml +++ b/app/config/service/form.yml @@ -1,7 +1,20 @@ services: form.filter.category: - class: AppBundle\Form\Filter\CategoryFilterType + class: AppBundle\Form\Filter\Model\CategoryFilterType arguments: - '@manager.ldraw.category' tags: - - { name: form.type } \ No newline at end of file + - { name: form.type } + + form.filter.brickset: + class: AppBundle\Form\FilterSetType + arguments: ['@api.manager.brickset'] + tags: + - { name: form.type } + + form.filter.theme: + class: AppBundle\Form\Filter\Set\ThemeFilterType + arguments: + - '@local.manager.rebrickable' + tags: + - { name: form.type } diff --git a/app/config/service/manager.yml b/app/config/service/manager.yml index e1a56f1..152fe18 100644 --- a/app/config/service/manager.yml +++ b/app/config/service/manager.yml @@ -1,4 +1,14 @@ services: + local.manager.base: + abstract: true + class: AppBundle\Manager\BaseManager + calls: + - [setEntityManager, ['@doctrine.orm.entity_manager']] + + local.manager.rebrickable: + class: AppBundle\Manager\RebrickableManager + parent: local.manager.base + manager.ldraw.keyword: class: AppBundle\Manager\LDraw\KeywordManager arguments: @@ -20,5 +30,3 @@ services: arguments: - "@repository.ldraw.alias" - - diff --git a/app/config/service/service.yml b/app/config/service/service.yml index 8c294b5..e33fd1b 100644 --- a/app/config/service/service.yml +++ b/app/config/service/service.yml @@ -8,12 +8,6 @@ services: - '@manager.ldraw.model' - '@manager.ldraw.alias' - app.form.filter_set: - class: AppBundle\Form\FilterSetType - arguments: ['@api.manager.brickset'] - tags: - - { name: form.type } - app.twig_extension: class: AppBundle\Twig\AppExtension public: false diff --git a/src/AppBundle/Controller/Brickset/SetController.php b/src/AppBundle/Controller/Brickset/SetController.php index 294a0b9..96658d3 100644 --- a/src/AppBundle/Controller/Brickset/SetController.php +++ b/src/AppBundle/Controller/Brickset/SetController.php @@ -31,11 +31,17 @@ class SetController extends Controller if ($form->isSubmitted() && $form->isValid()) { $data = $form->getData(); - $sets = $this->get('api.client.brickset')->getSets([ - 'theme' => $data['theme'] ? $data['theme']->getTheme() : '', - 'subtheme' => $data['subtheme'] ? $data['subtheme']->getSubtheme() : '', - 'year' => $data['years'] ? $data['years']->getYear() : '', - ]); + try { + $sets = $this->get('api.client.brickset')->getSets([ + 'theme' => $data['theme'] ? $data['theme']->getTheme() : '', + 'subtheme' => $data['subtheme'] ? $data['subtheme']->getSubtheme() : '', + 'year' => $data['years'] ? $data['years']->getYear() : '', + ]); + } catch (EmptyResponseException $e) { + $this->addFlash('warning', 'No set found on '.$e->getService()); + } catch (\Exception $e) { + $this->addFlash('error', $e->getMessage()); + } } return $this->render('brickset/browse.html.twig', [ diff --git a/src/AppBundle/Controller/ModelController.php b/src/AppBundle/Controller/ModelController.php index 306f766..3a41fbb 100644 --- a/src/AppBundle/Controller/ModelController.php +++ b/src/AppBundle/Controller/ModelController.php @@ -5,7 +5,7 @@ namespace AppBundle\Controller; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; -use AppBundle\Form\Filter\ModelFilterType; +use AppBundle\Form\Filter\Model\ModelFilterType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -26,8 +26,6 @@ class ModelController extends Controller */ public function indexAction(Request $request) { - $em = $this->getDoctrine()->getManager(); - $form = $this->get('form.factory')->create(ModelFilterType::class); $filterBuilder = $this->get('repository.ldraw.model') diff --git a/src/AppBundle/Controller/SetController.php b/src/AppBundle/Controller/SetController.php index a2d0d90..c9bd5f3 100644 --- a/src/AppBundle/Controller/SetController.php +++ b/src/AppBundle/Controller/SetController.php @@ -6,9 +6,11 @@ use AppBundle\Api\Exception\EmptyResponseException; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\Rebrickable\Color; use AppBundle\Entity\Rebrickable\Inventory_Part; +use AppBundle\Entity\Rebrickable\Inventory_Set; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; use AppBundle\Entity\Rebrickable\Theme; +use AppBundle\Form\Filter\Set\SetFilterType; use AppBundle\Form\FilterSetType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -25,19 +27,29 @@ class SetController extends Controller */ public function indexAction(Request $request) { - $em = $this->getDoctrine()->getManager(); + $form = $this->get('form.factory')->create(SetFilterType::class); - $qb = $em->getRepository(Set::class)->createQueryBuilder('s'); + $filterBuilder = $this->get('repository.rebrickable.set') + ->createQueryBuilder('s'); + + if ($request->query->has($form->getName())) { + // manually bind values from the request + $form->submit($request->query->get($form->getName())); + + // build the query from the given form object + $this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $filterBuilder); + } $paginator = $this->get('knp_paginator'); $sets = $paginator->paginate( - $qb->getQuery(), + $filterBuilder->getQuery(), $request->query->getInt('page', 1)/*page number*/, $request->query->getInt('limit', 30)/*limit per page*/ ); return $this->render('set/index.html.twig', [ 'sets' => $sets, + 'form' => $form->createView() ]); } @@ -48,6 +60,9 @@ class SetController extends Controller { $brset = null; $rbset = null; + $inventorySets = null; + + $inventorySets = $this->getDoctrine()->getManager()->getRepository(Inventory_Set::class)->findAllBySetNumber($number); try { if(($rbset = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number)) == null) { $this->addFlash('warning', 'Set not found in Rebrickable database'); @@ -62,6 +77,7 @@ class SetController extends Controller return $this->render('set/detail.html.twig', [ 'rbset' => $rbset, + 'inventorySets' => $inventorySets, 'brset' => $brset, ]); } diff --git a/src/AppBundle/Form/Filter/CategoryFilterType.php b/src/AppBundle/Form/Filter/Model/CategoryFilterType.php similarity index 97% rename from src/AppBundle/Form/Filter/CategoryFilterType.php rename to src/AppBundle/Form/Filter/Model/CategoryFilterType.php index 90f068f..d21ab29 100644 --- a/src/AppBundle/Form/Filter/CategoryFilterType.php +++ b/src/AppBundle/Form/Filter/Model/CategoryFilterType.php @@ -1,6 +1,6 @@ add('search', Filters\TextFilterType::class, [ + 'apply_filter' => [$this, 'setSearchCallback'], + 'label' => 'filter.part.search', + ]); + + $builder->add('theme', ThemeFilterType::class, [ + 'add_shared' => function (FilterBuilderExecuterInterface $builderExecuter) { + $builderExecuter->addOnce($builderExecuter->getAlias().'.theme', 'c', function (QueryBuilder $filterBuilder, $alias, $joinAlias, $expr) { + $filterBuilder->leftJoin($alias.'.theme', $joinAlias); + }); + }, + ]); + } + + public function getBlockPrefix() + { + return 'model_filter'; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'csrf_protection' => false, + 'validation_groups' => ['filtering'], // avoid NotBlank() constraint-related message + ]); + } + + public function setSearchCallback(QueryInterface $filterQuery, $field, $values) + { + if (empty($values['value'])) { + return null; + } + + // expression that represent the condition + $expression = $filterQuery->getExpr()->orX( + $filterQuery->getExpr()->like('s.name', ':value'), + $filterQuery->getExpr()->like('s.number', ':value') + ); + + return $filterQuery->createCondition($expression, ['value' => '%'.$values['value'].'%']); + } +} diff --git a/src/AppBundle/Form/Filter/Set/ThemeFilterType.php b/src/AppBundle/Form/Filter/Set/ThemeFilterType.php new file mode 100644 index 0000000..6657665 --- /dev/null +++ b/src/AppBundle/Form/Filter/Set/ThemeFilterType.php @@ -0,0 +1,58 @@ +rebrickableManager = $rebrickableManager; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('id', Filters\ChoiceFilterType::class, [ + 'choices' => $this->rebrickableManager->FindAllThemes(), + 'choice_label' => function ($allChoices, $currentChoiceKey) { + + dump($currentChoiceKey); + + $parent = $allChoices->getParent(); + + return $parent ? $parent->getName().' > '.$allChoices->getName() : $allChoices->getName(); + }, + 'label' => 'filter.set.theme', + ]); + } + + public function getParent() + { + return Filters\SharedableFilterType::class; // this allow us to use the "add_shared" option + } + + public function getBlockPrefix() + { + return 'theme_filter'; + } + + public function setDefaultOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'data_class' => Theme::class, + 'csrf_protection' => false, + 'validation_groups' => ['filtering'], // avoid NotBlank() constraint-related message + 'method' => 'GET', + ]); + } +} From 2dcde6c742035b316089998c5a9e04ce3eefc602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Tue, 11 Apr 2017 17:33:18 +0200 Subject: [PATCH 24/42] Improve Rebrickable repositories --- .../Rebrickable/Inventory_PartRepository.php | 32 ++++--------------- .../Rebrickable/Inventory_SetRepository.php | 14 ++++++++ .../Repository/Rebrickable/SetRepository.php | 9 ++---- 3 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php index e64e130..de53238 100644 --- a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php +++ b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php @@ -14,24 +14,14 @@ class Inventory_PartRepository extends BaseRepository { $queryBuilder = $this->createQueryBuilder('inventory_part'); - $version = $this->getEntityManager()->getRepository(Inventory::class)->createQueryBuilder('inventory') - ->select('MAX(inventory.version)') - ->groupBy('inventory.set') - ->where('inventory.set = :setNumber') - ->setParameter('setNumber',$number) - ->getQuery()->getResult(); + $inventory = $this->getEntityManager()->getRepository(Inventory::class)->findNewestInventoryBySetNumber($number); $queryBuilder - ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = inventory.id') - ->where('inventory.set = :number') - ->setParameter('number', $number) + ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = :inventory') + ->setParameter('inventory', $inventory->getId()) ->andWhere('inventory_part.spare = FALSE') - ->andWhere('inventory.version = :version') - ->setParameter('version',$version) ->distinct(true); - - return $queryBuilder->getQuery()->getResult(); } @@ -39,22 +29,12 @@ class Inventory_PartRepository extends BaseRepository { $queryBuilder = $this->createQueryBuilder('inventory_part'); - $version = $this->getEntityManager()->getRepository(Inventory::class)->createQueryBuilder('inventory') - ->select('MAX(inventory.version)') - ->groupBy('inventory.set') - ->where('inventory.set = :setNumber') - ->setParameter('setNumber',$number) - ->getQuery()->getResult(); - + $inventory = $this->getEntityManager()->getRepository(Inventory::class)->findNewestInventoryBySetNumber($number); $queryBuilder - ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = inventory.id') - ->where('inventory.set = :number') - ->setParameter('number', $number) + ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = :inventory') + ->setParameter('inventory', $inventory->getId()) ->andWhere('inventory_part.spare = TRUE') - ->andWhere('inventory.version = 1') - ->andWhere('inventory.version = :version') - ->setParameter('version',$version) ->distinct(true); return $queryBuilder->getQuery()->getResult(); diff --git a/src/AppBundle/Repository/Rebrickable/Inventory_SetRepository.php b/src/AppBundle/Repository/Rebrickable/Inventory_SetRepository.php index 840e81e..cf0b97d 100644 --- a/src/AppBundle/Repository/Rebrickable/Inventory_SetRepository.php +++ b/src/AppBundle/Repository/Rebrickable/Inventory_SetRepository.php @@ -10,4 +10,18 @@ use Doctrine\ORM\Query\Expr\Join; class Inventory_SetRepository extends BaseRepository { + public function findAllBySetNumber($number) { + + $inventory = $this->getEntityManager()->getRepository(Inventory::class)->findNewestInventoryBySetNumber($number); + + if($inventory) { + $queryBuilder = $this->createQueryBuilder('inventory_set') + ->where('inventory_set.inventory = :inventory') + ->setParameter('inventory',$inventory->getId()); + + return $queryBuilder->getQuery()->getResult(); + } + + return null; + } } diff --git a/src/AppBundle/Repository/Rebrickable/SetRepository.php b/src/AppBundle/Repository/Rebrickable/SetRepository.php index c9a1320..ae6cfee 100644 --- a/src/AppBundle/Repository/Rebrickable/SetRepository.php +++ b/src/AppBundle/Repository/Rebrickable/SetRepository.php @@ -7,6 +7,7 @@ use AppBundle\Entity\Rebrickable\Inventory; use AppBundle\Entity\Rebrickable\Inventory_Part; use AppBundle\Entity\Rebrickable\Inventory_Set; use AppBundle\Entity\Rebrickable\Part; +use AppBundle\Entity\Rebrickable\Set; use AppBundle\Repository\BaseRepository; use Doctrine\ORM\Query\Expr\Join; @@ -14,9 +15,7 @@ class SetRepository extends BaseRepository { public function findAllByPartNumber($number) { - $queryBuilder = $this->createQueryBuilder('s'); - - $queryBuilder + $queryBuilder = $this->createQueryBuilder('s') ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory.set = s.number') ->join(Inventory_Part::class, 'inventory_part', JOIN::WITH, 'inventory.id = inventory_part.inventory') ->join(Part::class, 'part', Join::WITH, 'inventory_part.part = part.number') @@ -29,9 +28,7 @@ class SetRepository extends BaseRepository public function findAllByModel(Model $model) { - $queryBuilder = $this->createQueryBuilder('s'); - - $queryBuilder + $queryBuilder = $this->createQueryBuilder('s') ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory.set = s.number') ->join(Inventory_Part::class, 'inventory_part', JOIN::WITH, 'inventory.id = inventory_part.inventory') ->join(Part::class, 'part', Join::WITH, 'inventory_part.part = part.number') From 7a2afca00a678b6500f43c136fbaf8ecf868196a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Tue, 11 Apr 2017 17:33:56 +0200 Subject: [PATCH 25/42] Update Twig Extension --- app/Resources/relations/alias_model.yml | 5 +++- app/Resources/views/base.html.twig | 26 +++---------------- app/Resources/views/brickset/browse.html.twig | 4 ++- .../views/brickset/instructions.html.twig | 2 +- app/Resources/views/error/error.html.twig | 4 +-- app/Resources/views/html.html.twig | 25 ++++++++++++++++++ app/Resources/views/model/detail.html.twig | 2 +- .../views/rebrickable/part/detail.html.twig | 2 +- .../views/rebrickable/set/parts.html.twig | 12 +++------ app/Resources/views/set/detail.html.twig | 22 ++++++++++++++-- app/Resources/views/set/index.html.twig | 18 ++++++++++++- src/AppBundle/Twig/AppExtension.php | 8 +++--- 12 files changed, 84 insertions(+), 46 deletions(-) create mode 100644 app/Resources/views/html.html.twig diff --git a/app/Resources/relations/alias_model.yml b/app/Resources/relations/alias_model.yml index 1e763f0..2a972f2 100644 --- a/app/Resources/relations/alias_model.yml +++ b/app/Resources/relations/alias_model.yml @@ -180,4 +180,7 @@ u588p02c02: u588p01c02 2958c01: 2723 -32181c03: 32181c01 \ No newline at end of file +32181c03: 32181c01 + +76019: 76244 +4107539: 76244 \ No newline at end of file diff --git a/app/Resources/views/base.html.twig b/app/Resources/views/base.html.twig index d8d0c9a..89487ea 100644 --- a/app/Resources/views/base.html.twig +++ b/app/Resources/views/base.html.twig @@ -1,16 +1,7 @@ +{% extends 'html.html.twig' %} + {% import 'macros/elements.html.twig' as elements %} - - - - - {% block title %}PrintABrick{% endblock %} - {% block stylesheets %} - - {% endblock %} - - - {% block body %}
-{% endblock %} - -{% block javascripts %} - - - - - - -{% endblock %} - - +{% endblock %} \ No newline at end of file diff --git a/app/Resources/views/brickset/browse.html.twig b/app/Resources/views/brickset/browse.html.twig index 8bab86c..70cd9a4 100644 --- a/app/Resources/views/brickset/browse.html.twig +++ b/app/Resources/views/brickset/browse.html.twig @@ -14,12 +14,14 @@
{{ form_end(form) }} +

{{ sets|length }}

+
{% for set in sets %} diff --git a/app/Resources/views/brickset/instructions.html.twig b/app/Resources/views/brickset/instructions.html.twig index 5b6e760..43c983d 100644 --- a/app/Resources/views/brickset/instructions.html.twig +++ b/app/Resources/views/brickset/instructions.html.twig @@ -10,7 +10,7 @@ {{ 'set.instructions.description' | trans }} {{ 'set.instructions.filesize' | trans }} - {{ 'set.instructions.download' | trans }} + {{ 'set.instructions.filename' | trans }} diff --git a/app/Resources/views/error/error.html.twig b/app/Resources/views/error/error.html.twig index b92b910..737f0ff 100644 --- a/app/Resources/views/error/error.html.twig +++ b/app/Resources/views/error/error.html.twig @@ -3,9 +3,9 @@ {% block title %}{{ 'page.error.title'|trans }}{% endblock %} {% block content %} -

+

{{ 'page.error.large'|trans }} -

+

{{ 'page.error.text'|trans }}

{% endblock %} \ No newline at end of file diff --git a/app/Resources/views/html.html.twig b/app/Resources/views/html.html.twig new file mode 100644 index 0000000..4e28c53 --- /dev/null +++ b/app/Resources/views/html.html.twig @@ -0,0 +1,25 @@ + + + + + {% block title %}PrintABrick{% endblock %} + {% block stylesheets %} + + {% endblock %} + + + +{% block body %} + +{% endblock %} + +{% block javascripts %} + + + + + + +{% endblock %} + + diff --git a/app/Resources/views/model/detail.html.twig b/app/Resources/views/model/detail.html.twig index 1ef114a..8d33ad5 100644 --- a/app/Resources/views/model/detail.html.twig +++ b/app/Resources/views/model/detail.html.twig @@ -38,7 +38,7 @@

{% for alias in rbParts %} - {{ alias.number }} + {{ alias.number }} {% endfor %}

diff --git a/app/Resources/views/rebrickable/part/detail.html.twig b/app/Resources/views/rebrickable/part/detail.html.twig index d3b6d6b..31a2e4a 100644 --- a/app/Resources/views/rebrickable/part/detail.html.twig +++ b/app/Resources/views/rebrickable/part/detail.html.twig @@ -29,7 +29,7 @@
diff --git a/app/Resources/views/rebrickable/set/parts.html.twig b/app/Resources/views/rebrickable/set/parts.html.twig index a8295f1..5e52b8c 100644 --- a/app/Resources/views/rebrickable/set/parts.html.twig +++ b/app/Resources/views/rebrickable/set/parts.html.twig @@ -1,7 +1,3 @@ -{% extends 'base.html.twig' %} - -{% block content %} -

Regular parts

@@ -14,7 +10,7 @@ {{ inventoryPart.quantity }}
- +
{{ inventoryPart.part.number }}
{{ inventoryPart.isSpare ? 'Spare' : 'Regular' }}
@@ -35,7 +31,7 @@ {{ inventoryPart.quantity }}
- +
{{ inventoryPart.part.number }}
{{ inventoryPart.isSpare ? 'Spare' : 'Regular' }}
@@ -43,6 +39,4 @@
{% endif %} {% endfor %} -
- -{% endblock %} \ No newline at end of file +
\ No newline at end of file diff --git a/app/Resources/views/set/detail.html.twig b/app/Resources/views/set/detail.html.twig index 388e99e..0c2e5d3 100644 --- a/app/Resources/views/set/detail.html.twig +++ b/app/Resources/views/set/detail.html.twig @@ -44,7 +44,7 @@ {% if brset %} {% elseif rbset %} - + {% endif %} {#{{ brset ? dump(brset) }}#} @@ -59,7 +59,25 @@
{% if rbset %} - {#{{ render(controller('AppBundle:Rebrickable/Set:parts', { 'number': rbset.number })) }}#} + {{ render(controller('AppBundle:Rebrickable/Set:parts', { 'number': rbset.number })) }} +
+
+ +

+ Sets +

+ + {% for set in inventorySets %} + + {% endfor %} +
+
{% endif %}
{% if brset %} diff --git a/app/Resources/views/set/index.html.twig b/app/Resources/views/set/index.html.twig index 7c3f768..96d3aa1 100644 --- a/app/Resources/views/set/index.html.twig +++ b/app/Resources/views/set/index.html.twig @@ -1,12 +1,28 @@ {% extends 'base.html.twig' %} {% block content %} + + + {{ form_start(form) }} + + {{ form_row(form.search) }} + +
+ +
+ {{ form_rest(form) }} + {{ form_end(form) }} + +
{% for set in sets %} diff --git a/src/AppBundle/Twig/AppExtension.php b/src/AppBundle/Twig/AppExtension.php index 4dd7974..1c0c0b0 100644 --- a/src/AppBundle/Twig/AppExtension.php +++ b/src/AppBundle/Twig/AppExtension.php @@ -37,14 +37,14 @@ class AppExtension extends \Twig_Extension ]; } - public function partImage(Part $part, Color $color = null) + public function partImage($number, $color = null) { - return '/parts/ldraw/'.($color ? $color->getId():'-1').'/'.$part->getNumber().'.png'; + return '/parts/ldraw/'.($color ? $color :'-1').'/'.$number.'.png'; } - public function setImage(Set $set) + public function setImage($number) { - return '/sets/'.strtolower($set->getNumber()).'.jpg'; + return '/sets/'.strtolower($number).'.jpg'; } public function remoteSize($url) { From 5b6bc7b6c86a1aa15ad70f3769dded603b5081db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Thu, 13 Apr 2017 18:48:56 +0200 Subject: [PATCH 26/42] Use local wsdl file to avoid SOAP uncatchable FatalErrorException --- app/config/brickset.xml | 2564 +++++++++++++++++ app/config/config.yml | 2 - app/config/service/api.yml | 2 +- .../Api/Client/Brickset/Brickset.php | 12 +- 4 files changed, 2575 insertions(+), 5 deletions(-) create mode 100644 app/config/brickset.xml diff --git a/app/config/brickset.xml b/app/config/brickset.xml new file mode 100644 index 0000000..f2629a8 --- /dev/null +++ b/app/config/brickset.xml @@ -0,0 +1,2564 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/config/config.yml b/app/config/config.yml index 58be23c..d32cff2 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -9,8 +9,6 @@ parameters: locale: en # rebrickable csv files root URL rebrickable_url: 'http://rebrickable.com/media/downloads/' - # LDraw library zip file URL - ldraw_url: 'http://www.ldraw.org/library/updates/complete.zip' framework: #esi: ~ diff --git a/app/config/service/api.yml b/app/config/service/api.yml index 23290ef..a12b773 100644 --- a/app/config/service/api.yml +++ b/app/config/service/api.yml @@ -1,7 +1,7 @@ services: api.client.brickset: class: AppBundle\Api\Client\Brickset\Brickset - arguments: ['%brickset_apikey%'] + arguments: ['%brickset_apikey%', '%kernel.root_dir%/config/brickset.xml'] api.client.rebrickable: class: AppBundle\Api\Client\Rebrickable\Rebrickable_v3 arguments: ['%rebrickable_apikey%'] diff --git a/src/AppBundle/Api/Client/Brickset/Brickset.php b/src/AppBundle/Api/Client/Brickset/Brickset.php index 45746ea..ace0d9c 100644 --- a/src/AppBundle/Api/Client/Brickset/Brickset.php +++ b/src/AppBundle/Api/Client/Brickset/Brickset.php @@ -47,6 +47,7 @@ class Brickset extends \SoapClient $this->apiKey = $apikey; $options['cache_wsdl'] = WSDL_CACHE_NONE; + $options['exceptions'] = true; foreach (self::$classmap as $key => $value) { if (!isset($options['classmap'][$key])) { @@ -56,7 +57,12 @@ class Brickset extends \SoapClient if (!$wsdl) { $wsdl = self::WSDL; } - parent::__construct($wsdl, $options); + + try { + parent::__construct($wsdl, $options); + } catch (\Exception $exception) { + throw new ApiException(ApiException::BRICKSET); + } } /** @@ -78,6 +84,8 @@ class Brickset extends \SoapClient throw new CallFailedException(ApiException::BRICKSET); } catch (ContextErrorException $e) { throw new EmptyResponseException(ApiException::BRICKSET); + } catch (\Exception $e) { + throw new ApiException(ApiException::BRICKSET); } } @@ -105,7 +113,7 @@ class Brickset extends \SoapClient $response = $this->call('getSets', $parameters)->sets; - return is_array($response) ? $response : [$response]; + return is_array($response) ? $response : [$this->getSet($response->getSetID())]; } /** From 254add244db159d251ccce1b9bb3fc0bef3df36b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Thu, 13 Apr 2017 18:54:28 +0200 Subject: [PATCH 27/42] Add asset images --- app/Resources/assets/images/brickset_logo.png | Bin 0 -> 5427 bytes .../assets/images/rebrickable_logo.png | Bin 0 -> 15991 bytes app/Resources/assets/style/main.scss | 45 +++- app/Resources/views/base.html.twig | 60 +++--- app/Resources/views/default/index.html.twig | 12 +- app/Resources/views/html.html.twig | 2 +- app/Resources/views/model/detail.html.twig | 163 ++++++++------ app/Resources/views/model/index.html.twig | 13 +- .../views/rebrickable/color/index.html.twig | 2 +- .../views/rebrickable/part/index.html.twig | 25 +++ .../views/rebrickable/set/parts.html.twig | 14 +- .../views/rebrickable/set/sets.html.twig | 16 ++ app/Resources/views/set/detail.html.twig | 198 +++++++++++------- gulpfile.js | 7 +- .../Controller/Rebrickable/SetController.php | 54 ++--- src/AppBundle/Controller/SetController.php | 117 +++++------ 16 files changed, 437 insertions(+), 291 deletions(-) create mode 100644 app/Resources/assets/images/brickset_logo.png create mode 100644 app/Resources/assets/images/rebrickable_logo.png create mode 100644 app/Resources/views/rebrickable/part/index.html.twig create mode 100644 app/Resources/views/rebrickable/set/sets.html.twig diff --git a/app/Resources/assets/images/brickset_logo.png b/app/Resources/assets/images/brickset_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f1eee4d95867227d2a715ae8dc20e69cc11791c7 GIT binary patch literal 5427 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 l&HrmS {{ knp_menu_render('mainMenu') }}
-
-
-
- - + {% endblock page %} {% endblock %} \ No newline at end of file diff --git a/app/Resources/views/default/index.html.twig b/app/Resources/views/default/index.html.twig index 918e8e0..4222685 100644 --- a/app/Resources/views/default/index.html.twig +++ b/app/Resources/views/default/index.html.twig @@ -1,6 +1,14 @@ {% extends 'base.html.twig' %} -{% block content %} - +{% block page %} +
+
+
+
+ Hello, world! +
+
+
+
{% endblock %} diff --git a/app/Resources/views/html.html.twig b/app/Resources/views/html.html.twig index 4e28c53..0c017fd 100644 --- a/app/Resources/views/html.html.twig +++ b/app/Resources/views/html.html.twig @@ -8,7 +8,7 @@ {% endblock %} - + {% block body %} {% endblock %} diff --git a/app/Resources/views/model/detail.html.twig b/app/Resources/views/model/detail.html.twig index 8d33ad5..67fe580 100644 --- a/app/Resources/views/model/detail.html.twig +++ b/app/Resources/views/model/detail.html.twig @@ -2,82 +2,111 @@ {% import 'macros/elements.html.twig' as elements %} -{% block title %}{{ model.name }}{% endblock %} +{% block title %}#{{ model.number }} - {{ model.name }}{% endblock %} -{% block header %}{{ model.name }}{% endblock %} +{% block header %}#{{ model.number }} - {{ model.name }}{% endblock %} {% block content %} -
- +
+
+
+ +
+
+
+
+ + + + + + + + + + +
category{{ model.category ? model.category.name }}
model{{ model.path }}
author{{ model.author }}
+ +
+
keywords:
+
+ {% for keyword in model.keywords %} + {{ keyword.name }} + {% endfor %} +
+
aliases:
+
+ {% for alias in model.aliases %} + {{ alias.number }}{% if not loop.last %},{% endif %} + {% endfor %} +
+
Download:
+
{{ model.number }}
+
rebrickable parts ({{ rbParts|length }}):
+
+ {#

#} + {#

#} + {#{% for alias in rbParts %}#} + {#{{ alias.number }}#} + {#{% endfor %}#} + {#
#} + {#

#} +
+
+
+
-
-
number:
{{ model.number }}
-
name:
{{ model.name }}
-
category:
{{ model.category ? model.category.name }}
-
model:
{{ model.path }}
-
author:
{{ model.author }}
-
modified:
{{ model.modified ? model.modified|date('Y-m-d') }}
-
keywords:
-
- {% for keyword in model.keywords %} - {{ keyword.name }} - {% endfor %} -
-
aliases:
-
- {% for alias in model.aliases %} - {{ alias.number }} - {% endfor %} -
-
Download:
-
{{ model.number }}
-
rebrickable parts ({{ rbParts|length }}):
-
-

-

- {% for alias in rbParts %} - {{ alias.number }} - {% endfor %} + + +

+ Subparts of this model +

+ +
+ {% for subpart in model.subparts %} +
+ {{ elements.part(subpart.subpart) }}
-

-
-
- -
-

- Subparts of this model -

- -
- {% for subpart in model.subparts %} -
- {{ elements.part(subpart.subpart) }} -
- {% endfor %} -
- -

- Model is subpart of -

- -
- {% for subpart in model.parents %} -
- {{ elements.part(subpart.parent) }} -
- {% endfor %} -
- -

- Sets ({{ sets|length }}) -

- - {% for set in sets %} - {{ set.number }} {% endfor %}
+ +

+ Model is subpart of +

+ +
+ {% for subpart in model.parents %} +
+ {{ elements.part(subpart.parent) }} +
+ {% endfor %} +
+ + +

+ Related +

+ +
+ {% for subpart in related %} +
+ {{ elements.part(subpart) }} +
+ {% endfor %} +
+ +

+ Sets ({{ sets|length }}) +

+ + {% for set in sets %} + {{ set.number }} + {% endfor %} + + + {% endblock %} {% block javascripts %} diff --git a/app/Resources/views/model/index.html.twig b/app/Resources/views/model/index.html.twig index 11219bf..f175824 100644 --- a/app/Resources/views/model/index.html.twig +++ b/app/Resources/views/model/index.html.twig @@ -2,7 +2,13 @@ {% import 'macros/elements.html.twig' as elements %} +{% block title %}{{ 'page.model.index' | trans }}{% endblock %} + +{% block header %}{{ 'page.model.index' | trans }}{% endblock %} + {% block content %} +
+
{{ form_start(form) }} @@ -16,8 +22,9 @@
{{ form_end(form) }} - - +
+
+ {{ knp_pagination_render(models) }}

{{ models.getTotalItemCount }}

{% for model in models %} @@ -26,4 +33,6 @@
{{ knp_pagination_render(models) }} +
+
{% endblock %} diff --git a/app/Resources/views/rebrickable/color/index.html.twig b/app/Resources/views/rebrickable/color/index.html.twig index e06398d..560f9f1 100644 --- a/app/Resources/views/rebrickable/color/index.html.twig +++ b/app/Resources/views/rebrickable/color/index.html.twig @@ -2,7 +2,7 @@ {% block content %} - +
diff --git a/app/Resources/views/rebrickable/part/index.html.twig b/app/Resources/views/rebrickable/part/index.html.twig new file mode 100644 index 0000000..9b5a206 --- /dev/null +++ b/app/Resources/views/rebrickable/part/index.html.twig @@ -0,0 +1,25 @@ +{% extends 'base.html.twig' %} + +{% block content %} + +
+
+ {% for part in parts %} + + {% endfor %} +
+
+ +

{{ parts.getTotalItemCount }}

+ + {{ knp_pagination_render(parts) }} +{% endblock %} diff --git a/app/Resources/views/rebrickable/set/parts.html.twig b/app/Resources/views/rebrickable/set/parts.html.twig index 5e52b8c..90b6c2a 100644 --- a/app/Resources/views/rebrickable/set/parts.html.twig +++ b/app/Resources/views/rebrickable/set/parts.html.twig @@ -1,8 +1,9 @@ +{% if regularParts|length > 0 %}

Regular parts

-
+
{% for inventoryPart in regularParts %} {% if inventoryPart.part is defined %}
@@ -12,18 +13,20 @@
-
{{ inventoryPart.part.number }}
{{ inventoryPart.isSpare ? 'Spare' : 'Regular' }}
+
{{ inventoryPart.part.number }}
{% endif %} {% endfor %}
+{% endif %} +{% if spareParts|length > 0 %}

Spare parts

-
+
{% for inventoryPart in spareParts %} {% if inventoryPart.part is defined %}
@@ -33,10 +36,11 @@
-
{{ inventoryPart.part.number }}
{{ inventoryPart.isSpare ? 'Spare' : 'Regular' }}
+
{{ inventoryPart.part.number }}
{% endif %} {% endfor %} -
\ No newline at end of file + +{% endif %} diff --git a/app/Resources/views/rebrickable/set/sets.html.twig b/app/Resources/views/rebrickable/set/sets.html.twig new file mode 100644 index 0000000..593b934 --- /dev/null +++ b/app/Resources/views/rebrickable/set/sets.html.twig @@ -0,0 +1,16 @@ +

Sets

+
+
+ {% for set in inventorySets %} + + {% endfor %} +
+
\ No newline at end of file diff --git a/app/Resources/views/set/detail.html.twig b/app/Resources/views/set/detail.html.twig index 0c2e5d3..6b93a16 100644 --- a/app/Resources/views/set/detail.html.twig +++ b/app/Resources/views/set/detail.html.twig @@ -5,90 +5,134 @@ {% block header %}{{ rbset ? rbset.number }} {{ rbset ? rbset.name }}{% endblock %} {% block content %} - {% if brset %} - Brickset - {% endif %} - - {% if rbset is not null %} - Rebrickable -
-
number:
{{ rbset.number }}
-
year:
{{ rbset.year }}
-
name:
{{ rbset.name }}
-
theme:
{{ rbset.theme.name }}
- {% if rbset.theme.parent %} -
themeparent:
{{ rbset.theme.parent.name }}
- {% if rbset.theme.parent.parent %} -
themeparent:
{{ rbset.theme.parent.parent.name }}
+
+
+
+ {% if brset %} + + {% elseif rbset %} + {% endif %} - {% endif %} -
count of parts:
{{ rbset.partCount }}
-
- {% endif %} + + +
+
+
+ + + + + + + + + + + + {% if rbset %} + + {% elseif brset %} + + {% endif %} + + + + + {% if brset %} + + + + + {% endif %} + {% if rbset %} + + + + + {% endif %} +
number{{ brset ? brset.legoSetID : rbset ? rbset.number : null}}
name{{ brset ? brset.name : rbset ? rbset.name : null}}
year{{ brset ? brset.year : rbset ? rbset.year : null}}
theme{{ rbset.theme.parent ? rbset.theme.parent.name }} {{ rbset.theme.name }} {{ brset.theme }}
parts{{ brset ? brset.pieces : rbset ? rbset.partCount }}
Brickset
Rebrickable
- {% if brset is not null %} -
-
year:
{{ brset.year }}
-
name:
{{ brset.name }}
-
themegroup:
{{ brset.themeGroup }}
-
theme:
{{ brset.theme }}
-
subtheme:
{{ brset.subtheme }}
-
count of parts:
{{ brset.pieces }}
-
lego id:
{{ brset.legoSetID }}
-
minifigs:
{{ brset.minifigs }}
-
description:
{{ brset.description }}
-
- {% endif %} - - {% if brset %} - - {% elseif rbset %} - - {% endif %} - - {#{{ brset ? dump(brset) }}#} - - +
-
- {% if rbset %} - {{ render(controller('AppBundle:Rebrickable/Set:parts', { 'number': rbset.number })) }} -
-
-

- Sets -

+
+ +
+ {% if rbset %} - {% for set in inventorySets %} - - {% endfor %} -
+
+ + {#{{ render(controller('AppBundle:Rebrickable/Set:parts', { 'number': rbset.number })) }}#} + + {% endif %} +
+ {% if brset %} +
+ {#
#} + + {{ render(controller('AppBundle:Brickset/Set:images', { 'id': brset.setID })) }} +
+
+ {#
#} + + {{ render(controller('AppBundle:Brickset/Set:instructions', { 'id': brset.setID })) }} +
+
+ {#
#} + + {{ render(controller('AppBundle:Brickset/Set:reviews', { 'id': brset.setID })) }} +
+
+ {{ brset.description }}
{% endif %}
- {% if brset %} -
- {{ render(controller('AppBundle:Brickset/Set:images', { 'id': brset.setID })) }} -
-
- {{ render(controller('AppBundle:Brickset/Set:instructions', { 'id': brset.setID })) }} -
-
- {{ render(controller('AppBundle:Brickset/Set:reviews', { 'id': brset.setID })) }} -
- {% endif %} + + +{% endblock %} + +{% block javascripts %} + {{ parent() }} + + {% endblock %} \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index ea31b09..bc7a03b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -48,9 +48,10 @@ gulp.task('files:semantic', function () { }); gulp.task('files:images', function () { - return gulp.src( - 'node_modules/lightbox2/dist/images/**' - ) + return gulp.src([ + 'node_modules/lightbox2/dist/images/**', + 'app/Resources/assets/images/**' + ]) .pipe(gulp.dest('web/resources/images')); }); diff --git a/src/AppBundle/Controller/Rebrickable/SetController.php b/src/AppBundle/Controller/Rebrickable/SetController.php index b86ef22..485b543 100644 --- a/src/AppBundle/Controller/Rebrickable/SetController.php +++ b/src/AppBundle/Controller/Rebrickable/SetController.php @@ -33,44 +33,32 @@ class SetController extends Controller $regularParts = $em->getRepository(Inventory_Part::class)->findAllRegularBySetNumber($set->getNumber()); $spareParts = $em->getRepository(Inventory_Part::class)->findAllSpareBySetNumber($set->getNumber()); - return $this->render('rebrickable/set/parts.html.twig', [ + $template = $this->render('rebrickable/set/parts.html.twig', [ 'regularParts' => $regularParts, 'spareParts' => $spareParts, ]); + + $json = json_encode($template->getContent()); + $response = new Response($json, 200); + $response->headers->set('Content-Type', 'application/json'); + return $response; } + /** + * @Route("/{number}/sets", name="rebrickable_set_sets") + */ + public function setsAction(Set $set) { + $em = $this->getDoctrine()->getManager(); -// /** -// * @Route("/download/{number}", name="set_download") -// */ -// public function downloadZipAction(Request $request, $number) { -// $em = $this->getDoctrine()->getManager(); -// -// $inventoryParts = $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number); -// -// $zip = new \ZipArchive(); -// $zipName = 'set_'.$number.'.zip'; -// $zip->open($zipName, \ZipArchive::CREATE); -// /** @var Inventory_Part $part */ -// foreach ($inventoryParts as $part) { -// $filename = $part->getPart()->getNumber().'_('.$part->getColor()->getName().'_'.$part->getQuantity().'x).stl'; -// -// try { -// if($part->getPart()->getModel()) { -// $zip->addFromString($filename, $this->get('oneup_flysystem.media_filesystem')->read($part->getPart()->getModel()->getPath())); -// } -// } catch (\Exception $e) { -// dump($e); -// } -// } -// $zip->close(); -// -// $response = new Response(file_get_contents($zipName)); -// $response->headers->set('Content-Type', 'application/zip'); -// $response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"'); -// $response->headers->set('Content-length', filesize($zipName)); -// -// return $response; -// } + $inventorySets = $em->getRepository(Inventory_Set::class)->findAllBySetNumber($set->getNumber()); + $template = $this->render('rebrickable/set/sets.html.twig', [ + 'inventorySets' => $inventorySets, + ]); + + $json = json_encode($template->getContent()); + $response = new Response($json, 200); + $response->headers->set('Content-Type', 'application/json'); + return $response; + } } diff --git a/src/AppBundle/Controller/SetController.php b/src/AppBundle/Controller/SetController.php index c9bd5f3..14a1137 100644 --- a/src/AppBundle/Controller/SetController.php +++ b/src/AppBundle/Controller/SetController.php @@ -2,6 +2,7 @@ namespace AppBundle\Controller; +use AppBundle\Api\Exception\ApiException; use AppBundle\Api\Exception\EmptyResponseException; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\Rebrickable\Color; @@ -58,88 +59,64 @@ class SetController extends Controller */ public function detailAction(Request $request, $number) { - $brset = null; - $rbset = null; - $inventorySets = null; - - $inventorySets = $this->getDoctrine()->getManager()->getRepository(Inventory_Set::class)->findAllBySetNumber($number); + $rebrickableSet = null; + $bricksetSet = null; try { - if(($rbset = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number)) == null) { + if(($rebrickableSet = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number)) == null) { $this->addFlash('warning', 'Set not found in Rebrickable database'); }; - $brset = $this->get('api.manager.brickset')->getSetByNumber($number); + $bricksetSet = $this->get('api.manager.brickset')->getSetByNumber($number); + dump($bricksetSet); } catch (EmptyResponseException $e) { $this->addFlash('warning', 'Set not found in Brickset database'); + } catch (ApiException $e) { + $this->addFlash('error', $e->getService()); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); } + if(!$rebrickableSet && !$bricksetSet) { + return $this->render('error/error.html.twig'); + } + return $this->render('set/detail.html.twig', [ - 'rbset' => $rbset, - 'inventorySets' => $inventorySets, - 'brset' => $brset, + 'rbset' => $rebrickableSet, + 'brset' => $bricksetSet, ]); } -// -// /** -// * @Route("/{number}/parts", name="set_parts") -// */ -// public function partsAction(Set $set) { -// $em = $this->getDoctrine()->getManager(); -// -// $em->getRepository(Color::class)->findAll(); -// $em->getRepository(Part::class)->findAllBySetNumber($set->getNumber()); -// -// $regularParts = $em->getRepository(Inventory_Part::class)->findAllRegularBySetNumber($set->getNumber()); -// $spareParts = $em->getRepository(Inventory_Part::class)->findAllSpareBySetNumber($set->getNumber()); -// -// $count = 0; -// /** @var Inventory_Part $inventoryPart */ -// foreach ($regularParts as $inventoryPart) { -// $count += $inventoryPart->getQuantity(); -// } -// -// dump($count); -// -// return $this->render('rebrickable/set/parts.html.twig', [ -// 'regularParts' => $regularParts, -// 'spareParts' => $spareParts, -// 'totalParts' => $count -// ]); -// } -// -//// /** -//// * @Route("/download/{number}", name="set_download") -//// */ -//// public function downloadZipAction(Request $request, $number) { -//// $em = $this->getDoctrine()->getManager(); -//// -//// $inventoryParts = $em->getRepository(Inventory_Part::class)->findAllBySetNumber($number); -//// -//// $zip = new \ZipArchive(); -//// $zipName = 'set_'.$number.'.zip'; -//// $zip->open($zipName, \ZipArchive::CREATE); -//// /** @var Inventory_Part $part */ -//// foreach ($inventoryParts as $part) { -//// $filename = $part->getPart()->getNumber().'_('.$part->getColor()->getName().'_'.$part->getQuantity().'x).stl'; -//// -//// try { -//// if($part->getPart()->getModel()) { -//// $zip->addFromString($filename, $this->get('oneup_flysystem.media_filesystem')->read($part->getPart()->getModel()->getPath())); -//// } -//// } catch (\Exception $e) { -//// dump($e); -//// } -//// } -//// $zip->close(); -//// -//// $response = new Response(file_get_contents($zipName)); -//// $response->headers->set('Content-Type', 'application/zip'); -//// $response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"'); -//// $response->headers->set('Content-length', filesize($zipName)); -//// -//// return $response; -//// } + + /** + * @Route("/{number}/download", name="set_download") + */ + public function downloadZipAction(Request $request, $number) { + $em = $this->getDoctrine()->getManager(); + + $inventoryParts = $em->getRepository(Inventory_Part::class)->findAllRegularBySetNumber($number); + + $zip = new \ZipArchive(); + $zipName = 'set_'.$number.'.zip'; + $zip->open($zipName, \ZipArchive::CREATE); + /** @var Inventory_Part $part */ + foreach ($inventoryParts as $part) { + $filename = $part->getPart()->getNumber().'_('.$part->getColor()->getName().'_'.$part->getQuantity().'x).stl'; + + try { + if($part->getPart()->getModel()) { + $zip->addFromString($filename, $this->get('oneup_flysystem.media_filesystem')->read($part->getPart()->getModel()->getPath())); + } + } catch (\Exception $e) { + dump($e); + } + } + $zip->close(); + + $response = new Response(file_get_contents($zipName)); + $response->headers->set('Content-Type', 'application/zip'); + $response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"'); + $response->headers->set('Content-length', filesize($zipName)); + + return $response; + } } From 438497f5ca379581b5663b11126bdb575a940013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Thu, 13 Apr 2017 18:55:58 +0200 Subject: [PATCH 28/42] Create missing parts referenced by FK --- .../Form/Filter/Set/ThemeFilterType.php | 20 ++++++++---- .../Rebrickable/Inventory_PartRepository.php | 24 ++++++++------ .../Repository/Rebrickable/SetRepository.php | 13 ++++++++ .../Loader/RebrickableLoaderService.php | 32 ++++++++++++++++--- 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/AppBundle/Form/Filter/Set/ThemeFilterType.php b/src/AppBundle/Form/Filter/Set/ThemeFilterType.php index 6657665..3efe503 100644 --- a/src/AppBundle/Form/Filter/Set/ThemeFilterType.php +++ b/src/AppBundle/Form/Filter/Set/ThemeFilterType.php @@ -24,13 +24,19 @@ class ThemeFilterType extends AbstractType { $builder->add('id', Filters\ChoiceFilterType::class, [ 'choices' => $this->rebrickableManager->FindAllThemes(), - 'choice_label' => function ($allChoices, $currentChoiceKey) { - - dump($currentChoiceKey); - - $parent = $allChoices->getParent(); - - return $parent ? $parent->getName().' > '.$allChoices->getName() : $allChoices->getName(); + 'choice_label' => function ($theme, $currentChoiceKey) { + if($parent = $theme->getParent()) { + if($parentParent = $parent->getParent()) { + if($parentParentParent = $parentParent->getParent()) { + return $parentParentParent->getName().' > '.$parentParent->getName().' > '.$parent->getName().' > '.$theme->getName(); + } + return $parentParent->getName().' > '.$parent->getName().' > '.$theme->getName(); + } else { + return $parent->getName().' > '.$theme->getName(); + } + } else { + return $theme->getName(); + } }, 'label' => 'filter.set.theme', ]); diff --git a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php index de53238..13f6969 100644 --- a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php +++ b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php @@ -2,8 +2,10 @@ namespace AppBundle\Repository\Rebrickable; +use AppBundle\Entity\LDraw\Category; use AppBundle\Entity\Rebrickable\Inventory; use AppBundle\Entity\Rebrickable\Inventory_Part; +use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; use AppBundle\Repository\BaseRepository; use Doctrine\ORM\Query\Expr\Join; @@ -12,13 +14,14 @@ class Inventory_PartRepository extends BaseRepository { public function findAllRegularBySetNumber($number) { - $queryBuilder = $this->createQueryBuilder('inventory_part'); - $inventory = $this->getEntityManager()->getRepository(Inventory::class)->findNewestInventoryBySetNumber($number); - $queryBuilder - ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = :inventory') - ->setParameter('inventory', $inventory->getId()) + $queryBuilder = $this->createQueryBuilder('inventory_part') + ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = inventory') + ->join(Part::class,'part',JOIN::WITH,'inventory_part.part = part.number') + ->where('part.category != 17') + ->andWhere('inventory.id = :inventoryId') + ->setParameter('inventoryId', $inventory->getId()) ->andWhere('inventory_part.spare = FALSE') ->distinct(true); @@ -27,13 +30,14 @@ class Inventory_PartRepository extends BaseRepository public function findAllSpareBySetNumber($number) { - $queryBuilder = $this->createQueryBuilder('inventory_part'); - $inventory = $this->getEntityManager()->getRepository(Inventory::class)->findNewestInventoryBySetNumber($number); - $queryBuilder - ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = :inventory') - ->setParameter('inventory', $inventory->getId()) + $queryBuilder = $this->createQueryBuilder('inventory_part') + ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = inventory') + ->join(Part::class,'part',JOIN::WITH,'inventory_part.part = part.number') + ->where('part.category != 17') + ->andWhere('inventory.id = :inventoryId') + ->setParameter('inventoryId', $inventory->getId()) ->andWhere('inventory_part.spare = TRUE') ->distinct(true); diff --git a/src/AppBundle/Repository/Rebrickable/SetRepository.php b/src/AppBundle/Repository/Rebrickable/SetRepository.php index ae6cfee..2614a47 100644 --- a/src/AppBundle/Repository/Rebrickable/SetRepository.php +++ b/src/AppBundle/Repository/Rebrickable/SetRepository.php @@ -8,11 +8,24 @@ use AppBundle\Entity\Rebrickable\Inventory_Part; use AppBundle\Entity\Rebrickable\Inventory_Set; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; +use AppBundle\Entity\Rebrickable\Theme; use AppBundle\Repository\BaseRepository; use Doctrine\ORM\Query\Expr\Join; class SetRepository extends BaseRepository { + public function findAllByTheme(Theme $theme) { + + dump($this->getEntityManager()->getRepository(Theme::class)->findAllSubthemes($theme)); + + $queryBuilder = $this->createQueryBuilder('s') + ->join(Theme::class, 'theme',Join::WITH, 's.theme = theme') + ->where('theme.id = :id') + ->setParameter('id', $theme->getId()); + + return $queryBuilder->getQuery()->getResult(); + } + public function findAllByPartNumber($number) { $queryBuilder = $this->createQueryBuilder('s') diff --git a/src/AppBundle/Service/Loader/RebrickableLoaderService.php b/src/AppBundle/Service/Loader/RebrickableLoaderService.php index 5f9c0a4..65bfa34 100644 --- a/src/AppBundle/Service/Loader/RebrickableLoaderService.php +++ b/src/AppBundle/Service/Loader/RebrickableLoaderService.php @@ -2,7 +2,11 @@ namespace AppBundle\Service\Loader; +use AppBundle\Entity\Rebrickable\Inventory_Part; +use AppBundle\Entity\Rebrickable\Inventory_Set; +use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; +use Doctrine\ORM\Query\Expr\Join; //TODO Refactor + validate csv files class RebrickableLoaderService extends BaseLoaderService @@ -24,18 +28,22 @@ class RebrickableLoaderService extends BaseLoaderService try { $connection->beginTransaction(); $connection->prepare('SET foreign_key_checks = 0;')->execute(); - $this->truncateTables(); + $connection->prepare('SET foreign_key_checks = 1;')->execute(); + $this->loadColorTable(); - $this->loadPartTable(); $this->loadCategoryTable(); + $this->loadPartTable(); $this->loadThemeTable(); $this->loadSetTable(); $this->loadInventoryTable(); - $this->loadInventoryPartTable(); $this->loadInventorySetTable(); + $connection->prepare('SET foreign_key_checks = 0;')->execute(); + $this->loadInventoryPartTable(); $connection->prepare('SET foreign_key_checks = 1;')->execute(); + $this->addMissingParts(); + $connection->commit(); } catch (\Exception $e) { $connection->rollBack(); @@ -49,11 +57,11 @@ class RebrickableLoaderService extends BaseLoaderService TRUNCATE TABLE rebrickable_inventory_parts; TRUNCATE TABLE rebrickable_inventory_sets; TRUNCATE TABLE rebrickable_color; + TRUNCATE TABLE rebrickable_category; TRUNCATE TABLE rebrickable_inventory; TRUNCATE TABLE rebrickable_set; TRUNCATE TABLE rebrickable_theme; TRUNCATE TABLE rebrickable_part; - TRUNCATE TABLE rebrickable_category; '; return $this->em->getConnection()->prepare($query)->execute(); @@ -70,6 +78,22 @@ class RebrickableLoaderService extends BaseLoaderService return $this->em->getConnection()->prepare($query)->execute(); } + private function addMissingParts() { + $connection = $this->em->getConnection(); + $statement = $connection->prepare( + 'SELECT DISTINCT rebrickable_inventory_parts.part_id FROM rebrickable_inventory_parts + LEFT JOIN rebrickable_part ON rebrickable_inventory_parts.part_id = rebrickable_part.id + WHERE rebrickable_part.id IS NULL'); + $statement->execute(); + $foreignKeys = $statement->fetchAll(); + + foreach ($foreignKeys as $foreignKey) { + $part = new Part(); + $part->setNumber($foreignKey['part_id']); + $this->em->getRepository(Part::class)->save($part); + } + } + private function loadInventoryTable() { $file = $this->downloadFile($this->rebrickable_url.'inventories.csv'); From dd3bb0df17409e6825c23a6d856918bbee6ed55a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Thu, 13 Apr 2017 18:57:25 +0200 Subject: [PATCH 29/42] Add related models list --- src/AppBundle/Controller/ModelController.php | 3 +++ .../Repository/LDraw/ModelRepository.php | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/AppBundle/Controller/ModelController.php b/src/AppBundle/Controller/ModelController.php index 3a41fbb..92ce1d3 100644 --- a/src/AppBundle/Controller/ModelController.php +++ b/src/AppBundle/Controller/ModelController.php @@ -67,10 +67,13 @@ class ModelController extends Controller $rbParts = $model != null ? $em->getRepository(Part::class)->findAllByModel($model) : null; $sets = $model != null ? $em->getRepository(Set::class)->findAllByModel($model) : null; + $related = $em->getRepository(Model::class)->findAllRelatedModels($model->getNumber()); + return $this->render('model/detail.html.twig', [ 'model' => $model, 'rbParts' => $rbParts, 'sets' => $sets, + 'related' => $related ]); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); diff --git a/src/AppBundle/Repository/LDraw/ModelRepository.php b/src/AppBundle/Repository/LDraw/ModelRepository.php index 7eeae5e..433e8d1 100644 --- a/src/AppBundle/Repository/LDraw/ModelRepository.php +++ b/src/AppBundle/Repository/LDraw/ModelRepository.php @@ -3,6 +3,8 @@ namespace AppBundle\Repository\LDraw; use AppBundle\Entity\LDraw\Category; +use AppBundle\Entity\LDraw\Model; +use AppBundle\Entity\LDraw\Subpart; use AppBundle\Entity\Rebrickable\Set; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\LDraw\Alias; @@ -55,4 +57,21 @@ class ModelRepository extends BaseRepository return $queryBuilder->getQuery()->getResult(); } + + public function findAllRelatedModels($number) + { + $queryBuilder = $this->createQueryBuilder('model'); + + $queryBuilder + ->select('related') + ->join(Subpart::class, 'subpart', JOIN::WITH, 'model.number = subpart.subpart') + ->join(Subpart::class, 'parent', JOIN::WITH, 'subpart.parent = parent.parent') + ->join(Model::class, 'related', JOIN::WITH, 'related.number = parent.subpart') + ->where('model.number = :number') + ->setParameter('number', $number) + ->andWhere('related.number != :number') + ->distinct(true); + + return $queryBuilder->getQuery()->getResult(); + } } From aed7bbfc1fb57fe0965bbe94411602b5261da67f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Thu, 13 Apr 2017 18:58:52 +0200 Subject: [PATCH 30/42] FIxtures --- app/Resources/relations/part_model.yml | 3 +++ app/config/config.yml | 6 +++--- gulpfile.js | 2 +- src/AppBundle/Service/Loader/ModelLoaderService.php | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/Resources/relations/part_model.yml b/app/Resources/relations/part_model.yml index 3d38b35..5edac4f 100644 --- a/app/Resources/relations/part_model.yml +++ b/app/Resources/relations/part_model.yml @@ -210,3 +210,6 @@ tech026: 2698c01 tech027: 2698c01 tech028: 2698c01 tech029: 2698c01 + +33299a: 33299 +33299b: 33299 \ No newline at end of file diff --git a/app/config/config.yml b/app/config/config.yml index d32cff2..c6f65ad 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -108,7 +108,7 @@ liip_imagine: data_loader: media cache: ~ quality: 80 - default_image: '/resources/images/unknown_image_min.png' + default_image: '/resources/images/unknown_image.png' filters: thumbnail: { size: [200, 200], mode: inset } background: { size: [250, 250], position: center, color: '#FFFFFF' } @@ -125,13 +125,13 @@ liip_imagine: quality: 80 data_loader: rebrickable cache: ~ - default_image: '/resources/images/unknown_image_min.png' + default_image: '/resources/images/unknown_image.png' filters: thumbnail: { size: [250, 250], mode: inset } rebrickable_set_min: quality: 80 data_loader: rebrickable - default_image: '/resources/images/unknown_image_min.png' + default_image: '/resources/images/unknown_image.png' cache: ~ filters: thumbnail: { size: [250, 250], mode: inset } diff --git a/gulpfile.js b/gulpfile.js index bc7a03b..5dfb49a 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -62,5 +62,5 @@ gulp.task('watch', ['js', 'css', 'three'], function () { }); gulp.task('default', function () { - return gulp.start(['files', 'js', 'css', 'three']); + return gulp.start(['files:semantic', 'files:images', 'js', 'css', 'three']); }); \ No newline at end of file diff --git a/src/AppBundle/Service/Loader/ModelLoaderService.php b/src/AppBundle/Service/Loader/ModelLoaderService.php index 2d5ba87..bbab54d 100644 --- a/src/AppBundle/Service/Loader/ModelLoaderService.php +++ b/src/AppBundle/Service/Loader/ModelLoaderService.php @@ -71,14 +71,14 @@ class ModelLoaderService extends BaseLoaderService $this->LDViewService->setLdrawFilesystem($this->ldrawLibraryContext); } - public function loadFileContext($file) { + public function setFileContext($file) { $adapter = new Local($file); $this->fileContext = new Filesystem($adapter); } public function loadAllModels() { - $files = $this->finder->in(['/home/hubnedav/Documents/ldraw'])->path('parts/')->name('*.dat')->depth(1)->files(); + $files = $this->finder->in([$this->ldrawLibraryContext->getAdapter()->getPathPrefix()])->path('parts/')->name('*.dat')->depth(1)->files(); $modelManager = $this->ldrawService->getModelManager(); From 5f757f97565297ed1d3c7af742a82530d6c58ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Thu, 13 Apr 2017 18:59:23 +0200 Subject: [PATCH 31/42] Make ModelViewer resizable --- app/Resources/assets/js/ModelViewer.js | 18 +++++++++--------- app/Resources/assets/style/modelviewer.scss | 3 +-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/Resources/assets/js/ModelViewer.js b/app/Resources/assets/js/ModelViewer.js index 3bcb7cd..7423b8f 100644 --- a/app/Resources/assets/js/ModelViewer.js +++ b/app/Resources/assets/js/ModelViewer.js @@ -26,9 +26,9 @@ var ModelViewer = function($dom_element, model_url) { this.initScene(); - this.stats = new Stats(); - this.stats.dom.style.position = 'absolute'; - this.container.append(this.stats.dom); + // this.stats = new Stats(); + // this.stats.dom.style.position = 'absolute'; + // this.container.append(this.stats.dom); function renderLoop() { @@ -44,8 +44,11 @@ var ModelViewer = function($dom_element, model_url) { } $this.render(); } + + $this.resize(parseInt($this.dom_element.width()), parseInt($this.dom_element.height())); } + renderLoop(); }; @@ -73,8 +76,8 @@ ModelViewer.prototype.initHtml = function () { ModelViewer.prototype.initCamera = function () { this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, .1, 300); - this.camera.position.z = 35; - this.camera.position.y = -35; + this.camera.position.z = 80; + this.camera.position.y = -45; this.camera.position.x = 35; this.camera.up = new THREE.Vector3(0, 0, 1); }; @@ -112,9 +115,6 @@ ModelViewer.prototype.initScene = function() { this.container.append(this.renderer.domElement); - // this.renderer.shadowMap.enabled = true; - // this.renderer.shadowMap.renderReverseSided = false; - this.controls = new THREE.OrbitControls( this.camera, this.renderer.domElement ); this.controls.enableZoom = true; }; @@ -208,7 +208,7 @@ ModelViewer.prototype.centerCamera = function(mesh) { var distanceZ = (geometry.boundingBox.max.z - geometry.boundingBox.min.z) / 2 / Math.tan(this.camera.fov * Math.PI / 360); var maxDistance = Math.max(Math.max(distanceX, distanceY), distanceZ); - maxDistance *= 1.6 * this.scale; + maxDistance *= 1.9 * this.scale; var cameraPosition = this.camera.position.normalize().multiplyScalar(maxDistance); diff --git a/app/Resources/assets/style/modelviewer.scss b/app/Resources/assets/style/modelviewer.scss index 7ac0019..cf9a0bc 100644 --- a/app/Resources/assets/style/modelviewer.scss +++ b/app/Resources/assets/style/modelviewer.scss @@ -1,6 +1,5 @@ .model-container { - width:700px; - height: 525px; + width: 100%; position: relative; border: 1px solid #DDDDDD; From 6042c05cb03e0075f81766e8a8959a967998e3cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Thu, 13 Apr 2017 18:59:59 +0200 Subject: [PATCH 32/42] Add findSubthemes function --- .../Repository/Rebrickable/ThemeRepository.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/AppBundle/Repository/Rebrickable/ThemeRepository.php b/src/AppBundle/Repository/Rebrickable/ThemeRepository.php index 11b0d88..8752161 100644 --- a/src/AppBundle/Repository/Rebrickable/ThemeRepository.php +++ b/src/AppBundle/Repository/Rebrickable/ThemeRepository.php @@ -2,8 +2,21 @@ namespace AppBundle\Repository\Rebrickable; +use AppBundle\Entity\Rebrickable\Theme; use AppBundle\Repository\BaseRepository; +use Doctrine\ORM\Query\Expr\Join; class ThemeRepository extends BaseRepository { + public function findAllSubthemes(Theme $theme) { + + $subQueryBuilder = $this->createQueryBuilder('subtheme'); + + $queryBuilder = $this->createQueryBuilder('subtheme') + ->leftJoin(Theme::class,'theme', Join::WITH, 'subtheme.parent = theme.id') + ->where('subtheme.parent = :id') + ->setParameter('id', $theme->getId()); + + return $queryBuilder->getQuery()->getResult(); + } } From 9319b737c536cbf3f0c2dcde8575a3fb2657ddb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 18:21:57 +0200 Subject: [PATCH 33/42] Remove Manager classes, Use repositories --- app/config/service/manager.yml | 33 +------ app/config/service/service.yml | 9 -- src/AppBundle/Manager/BaseManager.php | 27 ------ src/AppBundle/Manager/LDraw/AliasManager.php | 40 --------- .../Manager/LDraw/CategoryManager.php | 42 --------- .../Manager/LDraw/KeywordManager.php | 37 -------- src/AppBundle/Manager/LDraw/ModelManager.php | 47 ---------- .../Manager/LDraw/SubpartManager.php | 42 --------- src/AppBundle/Manager/LDrawManager.php | 86 ------------------- src/AppBundle/Manager/RebrickableManager.php | 12 --- .../Repository/LDraw/AliasRepository.php | 20 +++++ .../Repository/LDraw/CategoryRepository.php | 18 ++++ .../Repository/LDraw/KeywordRepository.php | 18 ++++ .../Repository/LDraw/ModelRepository.php | 38 +++++++- .../Repository/LDraw/SubpartRepository.php | 23 +++++ 15 files changed, 115 insertions(+), 377 deletions(-) delete mode 100644 src/AppBundle/Manager/BaseManager.php delete mode 100644 src/AppBundle/Manager/LDraw/AliasManager.php delete mode 100644 src/AppBundle/Manager/LDraw/CategoryManager.php delete mode 100644 src/AppBundle/Manager/LDraw/KeywordManager.php delete mode 100644 src/AppBundle/Manager/LDraw/ModelManager.php delete mode 100644 src/AppBundle/Manager/LDraw/SubpartManager.php delete mode 100644 src/AppBundle/Manager/LDrawManager.php delete mode 100644 src/AppBundle/Manager/RebrickableManager.php diff --git a/app/config/service/manager.yml b/app/config/service/manager.yml index 152fe18..9e4a3f5 100644 --- a/app/config/service/manager.yml +++ b/app/config/service/manager.yml @@ -1,32 +1 @@ -services: - local.manager.base: - abstract: true - class: AppBundle\Manager\BaseManager - calls: - - [setEntityManager, ['@doctrine.orm.entity_manager']] - - local.manager.rebrickable: - class: AppBundle\Manager\RebrickableManager - parent: local.manager.base - - manager.ldraw.keyword: - class: AppBundle\Manager\LDraw\KeywordManager - arguments: - - "@repository.ldraw.keyword" - manager.ldraw.category: - class: AppBundle\Manager\LDraw\CategoryManager - arguments: - - "@repository.ldraw.category" - manager.ldraw.subpart: - class: AppBundle\Manager\LDraw\SubpartManager - arguments: - - "@repository.ldraw.subpart" - manager.ldraw.model: - class: AppBundle\Manager\LDraw\ModelManager - arguments: - - "@repository.ldraw.model" - manager.ldraw.alias: - class: AppBundle\Manager\LDraw\AliasManager - arguments: - - "@repository.ldraw.alias" - +services: \ No newline at end of file diff --git a/app/config/service/service.yml b/app/config/service/service.yml index e33fd1b..88c5c8b 100644 --- a/app/config/service/service.yml +++ b/app/config/service/service.yml @@ -1,13 +1,4 @@ services: - manager.ldraw: - class: AppBundle\Manager\LDrawManager - arguments: - - '@manager.ldraw.category' - - '@manager.ldraw.keyword' - - '@manager.ldraw.subpart' - - '@manager.ldraw.model' - - '@manager.ldraw.alias' - app.twig_extension: class: AppBundle\Twig\AppExtension public: false diff --git a/src/AppBundle/Manager/BaseManager.php b/src/AppBundle/Manager/BaseManager.php deleted file mode 100644 index df466fe..0000000 --- a/src/AppBundle/Manager/BaseManager.php +++ /dev/null @@ -1,27 +0,0 @@ -repository; - } - - public function setEntityManager(EntityManager $entityManager) - { - $this->em = $entityManager; - } -} diff --git a/src/AppBundle/Manager/LDraw/AliasManager.php b/src/AppBundle/Manager/LDraw/AliasManager.php deleted file mode 100644 index fa50d6d..0000000 --- a/src/AppBundle/Manager/LDraw/AliasManager.php +++ /dev/null @@ -1,40 +0,0 @@ -repository = $repository; - } - - /** - * Create new Alias entity. - * - * @param $number - * @param Model $model - * - * @return Alias - */ - public function create($number, $model) - { - if (($alias = $this->repository->findOneBy(['number' => $number, 'model' => $model])) == null) { - $alias = new Alias(); - $alias->setModel($model); - $alias->setNumber($number); - } - - return $alias; - } -} diff --git a/src/AppBundle/Manager/LDraw/CategoryManager.php b/src/AppBundle/Manager/LDraw/CategoryManager.php deleted file mode 100644 index 7df6d71..0000000 --- a/src/AppBundle/Manager/LDraw/CategoryManager.php +++ /dev/null @@ -1,42 +0,0 @@ -repository = $repository; - } - - public function findAll() - { - return $this->repository->findAll(); - } - - /** - * Create new Category entity with $name or retrieve one. - * - * @param $name - * - * @return Category - */ - public function create($name) - { - if (($category = $this->repository->findByName($name)) == null) { - $category = new Category(); - $category->setName($name); - } - - return $category; - } -} diff --git a/src/AppBundle/Manager/LDraw/KeywordManager.php b/src/AppBundle/Manager/LDraw/KeywordManager.php deleted file mode 100644 index a321708..0000000 --- a/src/AppBundle/Manager/LDraw/KeywordManager.php +++ /dev/null @@ -1,37 +0,0 @@ -repository = $repository; - } - - /** - * Create new Keyword entity with $name or retrieve one. - * - * @param $name - * - * @return Keyword - */ - public function create($name) - { - if (($keyword = $this->repository->findByName($name)) == null) { - $keyword = new Keyword(); - $keyword->setName($name); - } - - return $keyword; - } -} diff --git a/src/AppBundle/Manager/LDraw/ModelManager.php b/src/AppBundle/Manager/LDraw/ModelManager.php deleted file mode 100644 index 3c3f8fd..0000000 --- a/src/AppBundle/Manager/LDraw/ModelManager.php +++ /dev/null @@ -1,47 +0,0 @@ -repository = $repository; - } - - /** - * Create new Model entity with $number or retrieve one. - * - * @param $number - * - * @return Model - */ - public function create($number) - { - if (($model = $this->repository->findOneBy(['number' => $number])) == null) { - $model = new Model(); - $model->setNumber($number); - } - - return $model; - } - - public function findByNumber($number) - { - return $this->repository->findOneByNumber($number); - } - - public function findByName($name) - { - return $this->repository->findOneBy(['name' => $name]); - } -} diff --git a/src/AppBundle/Manager/LDraw/SubpartManager.php b/src/AppBundle/Manager/LDraw/SubpartManager.php deleted file mode 100644 index d037780..0000000 --- a/src/AppBundle/Manager/LDraw/SubpartManager.php +++ /dev/null @@ -1,42 +0,0 @@ -repository = $repository; - } - - /** - * Create new Subpart relation entity or retrieve one by foreign keys. - * - * @param $name - * - * @return Subpart - */ - public function create($parent, $child, $count) - { -// if (($subpart = $this->repository->findOneByKeys($parent, $child))) { -// $subpart->setCount($count); -// } else { - $subpart = new Subpart(); - $subpart - ->setParent($parent) - ->setSubpart($child) - ->setCount($count); -// } - - return $subpart; - } -} diff --git a/src/AppBundle/Manager/LDrawManager.php b/src/AppBundle/Manager/LDrawManager.php deleted file mode 100644 index 84fb05b..0000000 --- a/src/AppBundle/Manager/LDrawManager.php +++ /dev/null @@ -1,86 +0,0 @@ -categoryManager = $categoryManager; - $this->keywordManager = $keywordManager; - $this->subpartManager = $subpartManager; - $this->modelManager = $modelManager; - $this->aliasManager = $aliasManager; - } - - /** - * @return mixed - */ - public function getCategoryManager() - { - return $this->categoryManager; - } - - /** - * @return mixed - */ - public function getKeywordManager() - { - return $this->keywordManager; - } - - /** - * @return SubpartManager - */ - public function getSubpartManager() - { - return $this->subpartManager; - } - - /** - * @return ModelManager - */ - public function getModelManager() - { - return $this->modelManager; - } - - /** - * @return AliasManager - */ - public function getAliasManager() - { - return $this->aliasManager; - } -} diff --git a/src/AppBundle/Manager/RebrickableManager.php b/src/AppBundle/Manager/RebrickableManager.php deleted file mode 100644 index 38e2fc6..0000000 --- a/src/AppBundle/Manager/RebrickableManager.php +++ /dev/null @@ -1,12 +0,0 @@ -em->getRepository(Theme::class)->findAll(); - } -} diff --git a/src/AppBundle/Repository/LDraw/AliasRepository.php b/src/AppBundle/Repository/LDraw/AliasRepository.php index 18497f4..3af7989 100644 --- a/src/AppBundle/Repository/LDraw/AliasRepository.php +++ b/src/AppBundle/Repository/LDraw/AliasRepository.php @@ -2,8 +2,28 @@ namespace AppBundle\Repository\LDraw; +use AppBundle\Entity\LDraw\Alias; +use AppBundle\Entity\LDraw\Model; use AppBundle\Repository\BaseRepository; class AliasRepository extends BaseRepository { + /** + * Get existing entity or create new. + * + * @param $number + * @param Model $model + * + * @return Alias + */ + public function getOrCreate($number, $model) + { + if (($alias = $this->findOneBy(['number' => $number, 'model' => $model])) == null) { + $alias = new Alias(); + $alias->setModel($model); + $alias->setNumber($number); + } + + return $alias; + } } diff --git a/src/AppBundle/Repository/LDraw/CategoryRepository.php b/src/AppBundle/Repository/LDraw/CategoryRepository.php index 4b0e43a..54e893e 100644 --- a/src/AppBundle/Repository/LDraw/CategoryRepository.php +++ b/src/AppBundle/Repository/LDraw/CategoryRepository.php @@ -2,6 +2,7 @@ namespace AppBundle\Repository\LDraw; +use AppBundle\Entity\LDraw\Category; use AppBundle\Repository\BaseRepository; class CategoryRepository extends BaseRepository @@ -10,4 +11,21 @@ class CategoryRepository extends BaseRepository { return $this->findOneBy(['name' => $name]); } + + /** + * Get existing entity or create new. + * + * @param $name + * + * @return Category + */ + public function getOrCreate($name) + { + if (($category = $this->findByName($name)) == null) { + $category = new Category(); + $category->setName($name); + } + + return $category; + } } diff --git a/src/AppBundle/Repository/LDraw/KeywordRepository.php b/src/AppBundle/Repository/LDraw/KeywordRepository.php index 43e8a67..019b81a 100644 --- a/src/AppBundle/Repository/LDraw/KeywordRepository.php +++ b/src/AppBundle/Repository/LDraw/KeywordRepository.php @@ -2,6 +2,7 @@ namespace AppBundle\Repository\LDraw; +use AppBundle\Entity\LDraw\Keyword; use AppBundle\Repository\BaseRepository; class KeywordRepository extends BaseRepository @@ -10,4 +11,21 @@ class KeywordRepository extends BaseRepository { return $this->findOneBy(['name' => $name]); } + + /** + * Create new Keyword entity with $name or retrieve one. + * + * @param $name + * + * @return Keyword + */ + public function getOrCreate($name) + { + if (($keyword = $this->findByName($name)) == null) { + $keyword = new Keyword(); + $keyword->setName($name); + } + + return $keyword; + } } diff --git a/src/AppBundle/Repository/LDraw/ModelRepository.php b/src/AppBundle/Repository/LDraw/ModelRepository.php index 433e8d1..27f6c2a 100644 --- a/src/AppBundle/Repository/LDraw/ModelRepository.php +++ b/src/AppBundle/Repository/LDraw/ModelRepository.php @@ -2,19 +2,29 @@ namespace AppBundle\Repository\LDraw; +use AppBundle\Entity\LDraw\Alias; use AppBundle\Entity\LDraw\Category; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\LDraw\Subpart; -use AppBundle\Entity\Rebrickable\Set; -use AppBundle\Entity\Rebrickable\Part; -use AppBundle\Entity\LDraw\Alias; use AppBundle\Entity\Rebrickable\Inventory; use AppBundle\Entity\Rebrickable\Inventory_Part; +use AppBundle\Entity\Rebrickable\Part; +use AppBundle\Entity\Rebrickable\Set; use AppBundle\Repository\BaseRepository; use Doctrine\ORM\Query\Expr\Join; class ModelRepository extends BaseRepository { + public function getFilteredQueryBuilder() + { + $queryBuilder = $this->createQueryBuilder('model') +// ->where('model.name NOT LIKE :obsolete') +// ->setParameter('obsolete','~%') +; + + return $queryBuilder; + } + public function findAllByCategory($category) { $queryBuilder = $this->createQueryBuilder('model') @@ -42,6 +52,11 @@ class ModelRepository extends BaseRepository return $model; } + public function findOneByName($name) + { + return $this->findOneBy(['name' => $name]); + } + public function findAllBySetNumber($number) { $queryBuilder = $this->createQueryBuilder('model'); @@ -74,4 +89,21 @@ class ModelRepository extends BaseRepository return $queryBuilder->getQuery()->getResult(); } + + /** + * Create new Model entity with $number or retrieve one. + * + * @param $number + * + * @return Model + */ + public function getOrCreate($number) + { + if (($model = $this->findOneBy(['number' => $number])) == null) { + $model = new Model(); + $model->setNumber($number); + } + + return $model; + } } diff --git a/src/AppBundle/Repository/LDraw/SubpartRepository.php b/src/AppBundle/Repository/LDraw/SubpartRepository.php index 3674f50..25fa871 100644 --- a/src/AppBundle/Repository/LDraw/SubpartRepository.php +++ b/src/AppBundle/Repository/LDraw/SubpartRepository.php @@ -2,6 +2,7 @@ namespace AppBundle\Repository\LDraw; +use AppBundle\Entity\LDraw\Subpart; use AppBundle\Repository\BaseRepository; class SubpartRepository extends BaseRepository @@ -10,4 +11,26 @@ class SubpartRepository extends BaseRepository { return $this->find(['parent' => $parent, 'subpart' => $child]); } + + /** + * Create new Subpart relation entity or retrieve one by foreign keys. + * + * @param $name + * + * @return Subpart + */ + public function getOrCreate($parent, $child, $count) + { + if (($subpart = $this->findOneByKeys($parent, $child))) { + $subpart->setCount($count); + } else { + $subpart = new Subpart(); + $subpart + ->setParent($parent) + ->setSubpart($child) + ->setCount($count); + } + + return $subpart; + } } From 8e4792a6c4cb8ac31a166835b0813e4462b97bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 21:58:24 +0200 Subject: [PATCH 34/42] Add UniqueNameTrait --- src/AppBundle/Entity/LDraw/Category.php | 4 +-- src/AppBundle/Entity/LDraw/Keyword.php | 4 +-- .../Entity/Traits/UniqueNameTrait.php | 33 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 src/AppBundle/Entity/Traits/UniqueNameTrait.php diff --git a/src/AppBundle/Entity/LDraw/Category.php b/src/AppBundle/Entity/LDraw/Category.php index 549c804..10dda36 100644 --- a/src/AppBundle/Entity/LDraw/Category.php +++ b/src/AppBundle/Entity/LDraw/Category.php @@ -3,7 +3,7 @@ namespace AppBundle\Entity\LDraw; use AppBundle\Entity\Traits\IdentityTrait; -use AppBundle\Entity\Traits\NameTrait; +use AppBundle\Entity\Traits\UniqueNameTrait; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -17,7 +17,7 @@ use Doctrine\ORM\Mapping as ORM; class Category { use IdentityTrait; - use NameTrait; + use UniqueNameTrait; /** * @var Collection diff --git a/src/AppBundle/Entity/LDraw/Keyword.php b/src/AppBundle/Entity/LDraw/Keyword.php index 1a57071..85aeea3 100644 --- a/src/AppBundle/Entity/LDraw/Keyword.php +++ b/src/AppBundle/Entity/LDraw/Keyword.php @@ -3,7 +3,7 @@ namespace AppBundle\Entity\LDraw; use AppBundle\Entity\Traits\IdentityTrait; -use AppBundle\Entity\Traits\NameTrait; +use AppBundle\Entity\Traits\UniqueNameTrait; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\Mapping as ORM; @@ -16,7 +16,7 @@ use Doctrine\ORM\Mapping as ORM; class Keyword { use IdentityTrait; - use NameTrait; + use UniqueNameTrait; /** * @var ArrayCollection diff --git a/src/AppBundle/Entity/Traits/UniqueNameTrait.php b/src/AppBundle/Entity/Traits/UniqueNameTrait.php new file mode 100644 index 0000000..63c742d --- /dev/null +++ b/src/AppBundle/Entity/Traits/UniqueNameTrait.php @@ -0,0 +1,33 @@ +name; + } + + /** + * @param string $name + * + * @return mixed + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } +} From 4742e6eac89fc26f07a083fdf35548fad7804eb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 21:59:59 +0200 Subject: [PATCH 35/42] Add Author entity --- app/Resources/views/model/detail.html.twig | 21 +++++++----- app/config/service/repository.yml | 7 ++++ src/AppBundle/Entity/LDraw/Author.php | 34 +++++++++++++++++++ src/AppBundle/Entity/LDraw/Model.php | 10 +++--- .../Repository/LDraw/AuthorRepository.php | 31 +++++++++++++++++ 5 files changed, 88 insertions(+), 15 deletions(-) create mode 100644 src/AppBundle/Entity/LDraw/Author.php create mode 100644 src/AppBundle/Repository/LDraw/AuthorRepository.php diff --git a/app/Resources/views/model/detail.html.twig b/app/Resources/views/model/detail.html.twig index 67fe580..a4ea0ac 100644 --- a/app/Resources/views/model/detail.html.twig +++ b/app/Resources/views/model/detail.html.twig @@ -24,7 +24,7 @@ model{{ model.path }} - author{{ model.author }} + author{{ model.author.name }} @@ -59,11 +59,15 @@
-

- Subparts of this model + Related Models

+ +
+ Subparts of this model +
+
{% for subpart in model.subparts %}
@@ -72,9 +76,9 @@ {% endfor %}
-

+

Model is subpart of -
+
{% for subpart in model.parents %} @@ -84,10 +88,9 @@ {% endfor %}
- -

- Related -

+
+ Model pairs with +
{% for subpart in related %} diff --git a/app/config/service/repository.yml b/app/config/service/repository.yml index 080bbdf..afb69d5 100644 --- a/app/config/service/repository.yml +++ b/app/config/service/repository.yml @@ -41,6 +41,13 @@ services: arguments: - AppBundle\Entity\LDraw\Alias + repository.ldraw.author: + class: Doctrine\ORM\EntityRepository + factory: ["@doctrine", getRepository] + arguments: + - AppBundle\Entity\LDraw\Author + + repository.rebrickable.category: class: Doctrine\ORM\EntityRepository factory: ["@doctrine", getRepository] diff --git a/src/AppBundle/Entity/LDraw/Author.php b/src/AppBundle/Entity/LDraw/Author.php new file mode 100644 index 0000000..9d87c02 --- /dev/null +++ b/src/AppBundle/Entity/LDraw/Author.php @@ -0,0 +1,34 @@ +models; + } +} diff --git a/src/AppBundle/Entity/LDraw/Model.php b/src/AppBundle/Entity/LDraw/Model.php index f126637..e770b2b 100644 --- a/src/AppBundle/Entity/LDraw/Model.php +++ b/src/AppBundle/Entity/LDraw/Model.php @@ -5,7 +5,6 @@ namespace AppBundle\Entity\LDraw; use AppBundle\Entity\Traits\NumberTrait; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; -use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; /** @@ -68,9 +67,9 @@ class Model private $path; /** - * @var string + * @var Author * - * @ORM\Column(type="string", length=255, nullable=true) + * @ORM\ManyToOne(targetEntity="AppBundle\Entity\LDraw\Author", cascade={"persist"}, inversedBy="models") */ private $author; @@ -116,7 +115,7 @@ class Model /** * Set author. * - * @param string $author + * @param Author $author * * @return Model */ @@ -130,7 +129,7 @@ class Model /** * Get author. * - * @return string + * @return Author */ public function getAuthor() { @@ -270,7 +269,6 @@ class Model return $this; } - /** * @param Subpart $subpart * diff --git a/src/AppBundle/Repository/LDraw/AuthorRepository.php b/src/AppBundle/Repository/LDraw/AuthorRepository.php new file mode 100644 index 0000000..eca33fb --- /dev/null +++ b/src/AppBundle/Repository/LDraw/AuthorRepository.php @@ -0,0 +1,31 @@ +findOneBy(['name' => $name]); + } + + /** + * Get existing entity or create new. + * + * @param $name + * + * @return Author + */ + public function getOrCreate($name) + { + if (($author = $this->findOneByName($name)) == null) { + $author = new Author(); + $author->setName($name); + } + + return $author; + } +} From e8c225c021c78613d017d7f43e704597e45daac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 22:09:30 +0200 Subject: [PATCH 36/42] Remove manager depencies --- app/config/service/form.yml | 4 +- .../Form/Filter/Model/CategoryFilterType.php | 10 ++-- .../Form/Filter/Set/ThemeFilterType.php | 29 ++++++----- .../Service/Loader/RelationLoader.php | 48 ++++++++----------- 4 files changed, 40 insertions(+), 51 deletions(-) diff --git a/app/config/service/form.yml b/app/config/service/form.yml index 5693b10..bcb354d 100644 --- a/app/config/service/form.yml +++ b/app/config/service/form.yml @@ -2,7 +2,7 @@ services: form.filter.category: class: AppBundle\Form\Filter\Model\CategoryFilterType arguments: - - '@manager.ldraw.category' + - '@repository.ldraw.category' tags: - { name: form.type } @@ -15,6 +15,6 @@ services: form.filter.theme: class: AppBundle\Form\Filter\Set\ThemeFilterType arguments: - - '@local.manager.rebrickable' + - '@repository.rebrickable.theme' tags: - { name: form.type } diff --git a/src/AppBundle/Form/Filter/Model/CategoryFilterType.php b/src/AppBundle/Form/Filter/Model/CategoryFilterType.php index d21ab29..82dd9ee 100644 --- a/src/AppBundle/Form/Filter/Model/CategoryFilterType.php +++ b/src/AppBundle/Form/Filter/Model/CategoryFilterType.php @@ -3,7 +3,7 @@ namespace AppBundle\Form\Filter\Model; use AppBundle\Entity\LDraw\Category; -use AppBundle\Manager\LDraw\CategoryManager; +use AppBundle\Repository\LDraw\CategoryRepository; use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -11,17 +11,17 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class CategoryFilterType extends AbstractType { - private $categoryManager; + private $categoryRepository; - public function __construct(CategoryManager $categoryManager) + public function __construct(CategoryRepository $categoryRepository) { - $this->categoryManager = $categoryManager; + $this->categoryRepository = $categoryRepository; } public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('id', Filters\ChoiceFilterType::class, [ - 'choices' => $this->categoryManager->findAll(), + 'choices' => $this->categoryRepository->findAll(), 'choice_label' => 'name', 'label' => 'filter.part.category', ]); diff --git a/src/AppBundle/Form/Filter/Set/ThemeFilterType.php b/src/AppBundle/Form/Filter/Set/ThemeFilterType.php index 3efe503..f215fc4 100644 --- a/src/AppBundle/Form/Filter/Set/ThemeFilterType.php +++ b/src/AppBundle/Form/Filter/Set/ThemeFilterType.php @@ -3,9 +3,7 @@ namespace AppBundle\Form\Filter\Set; use AppBundle\Entity\Rebrickable\Theme; -use AppBundle\Entity\LDraw\Category; -use AppBundle\Manager\LDraw\CategoryManager; -use AppBundle\Manager\RebrickableManager; +use AppBundle\Repository\Rebrickable\ThemeRepository; use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; @@ -13,30 +11,31 @@ use Symfony\Component\OptionsResolver\OptionsResolver; class ThemeFilterType extends AbstractType { - private $rebrickableManager; + private $themeRepository; - public function __construct(RebrickableManager $rebrickableManager) + public function __construct(ThemeRepository $themeRepository) { - $this->rebrickableManager = $rebrickableManager; + $this->themeRepository = $themeRepository; } public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('id', Filters\ChoiceFilterType::class, [ - 'choices' => $this->rebrickableManager->FindAllThemes(), - 'choice_label' => function ($theme, $currentChoiceKey) { - if($parent = $theme->getParent()) { - if($parentParent = $parent->getParent()) { - if($parentParentParent = $parentParent->getParent()) { + 'choices' => $this->themeRepository->findAll(), + 'choice_label' => function ($theme, $currentChoiceKey) { + if ($parent = $theme->getParent()) { + if ($parentParent = $parent->getParent()) { + if ($parentParentParent = $parentParent->getParent()) { return $parentParentParent->getName().' > '.$parentParent->getName().' > '.$parent->getName().' > '.$theme->getName(); } + return $parentParent->getName().' > '.$parent->getName().' > '.$theme->getName(); - } else { - return $parent->getName().' > '.$theme->getName(); } - } else { - return $theme->getName(); + + return $parent->getName().' > '.$theme->getName(); } + + return $theme->getName(); }, 'label' => 'filter.set.theme', ]); diff --git a/src/AppBundle/Service/Loader/RelationLoader.php b/src/AppBundle/Service/Loader/RelationLoader.php index d3be510..7ceaf73 100644 --- a/src/AppBundle/Service/Loader/RelationLoader.php +++ b/src/AppBundle/Service/Loader/RelationLoader.php @@ -5,16 +5,12 @@ namespace AppBundle\Service\Loader; use AppBundle\Api\Manager\RebrickableManager; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\Rebrickable\Part; -use AppBundle\Manager\LDraw\ModelManager; -use AppBundle\Repository\Rebrickable\PartRepository; +use AppBundle\Utils\RelationMapper; -class RelationLoader extends BaseLoaderService +class RelationLoader extends BaseLoader { - /** @var ModelManager */ - private $modelManager; - - /** @var PartRepository */ - private $partRepository; + /** @var RelationMapper */ + private $relationMapper; /** @var RebrickableManager */ private $rebrickableAPIManager; @@ -22,23 +18,18 @@ class RelationLoader extends BaseLoaderService /** * RelationLoader constructor. * - * @param $ldrawPartManager - * @param $rebrickablePartRepository + * @param RebrickableManager $rebrickableApiManager + * @param RelationMapper $relationMapper */ - public function __construct($modelManager, $partRepository, $rebrickableApiManager) + public function __construct($rebrickableApiManager, $relationMapper) { - $this->modelManager = $modelManager; - $this->partRepository = $partRepository; $this->rebrickableAPIManager = $rebrickableApiManager; + $this->relationMapper = $relationMapper; } - - /** - * - */ public function loadAll() { - $parts = $this->partRepository->findAll(); + $parts = $this->em->getRepository(Part::class)->findAll(); $this->initProgressBar(count($parts)); /** @var Part $part */ @@ -50,46 +41,45 @@ class RelationLoader extends BaseLoaderService $this->progressBar->finish(); } - /** - * - */ public function loadNotPaired() { - $parts = $this->partRepository->findAllNotPaired(); + $parts = $this->em->getRepository(Part::class)->findAllNotPaired(); $this->initProgressBar(count($parts)); /** @var Part $part */ foreach ($parts as $part) { $this->load($part); - + $this->progressBar->setMessage($part->getNumber(), 'filename'); $this->progressBar->advance(); } $this->progressBar->finish(); } /** - * Loads relations between Rebrickable part and ldraw models for $parts + * Loads relations between Rebrickable part and ldraw models for $parts. * * @param Part $part * * @return Model $m */ - private function load($part) + private function load(Part $part) { + $modelRepository = $this->em->getRepository(Model::class); + $number = $part->getNumber(); - $model = $this->modelManager->findByNumber($number); + $model = $modelRepository->findOneByNumber($number); if (!$model) { $number = $this->relationMapper->find($this->getPrintedParentId($number), 'part_model'); - $model = $this->modelManager->findByNumber($number); + $model = $modelRepository->findOneByNumber($number); if (!$model) { - $model = $this->modelManager->findByName($part->getName()); + $model = $modelRepository->findOneByName($part->getName()); } } if ($model) { $part->setModel($model); - $this->partRepository->save($part); + $this->em->getRepository(Part::class)->save($part); } } From 30c4eb8c824d52c3a7fdfbda07ca455bc25e1507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 22:12:58 +0200 Subject: [PATCH 37/42] Fix coding style --- .../Api/Client/Brickset/Brickset.php | 4 +- .../Api/Client/Rebrickable/Rebrickable_v3.php | 5 +- src/AppBundle/Api/Manager/BricksetManager.php | 9 ++-- .../Api/Manager/RebrickableManager.php | 9 ++-- src/AppBundle/Controller/MediaController.php | 2 - src/AppBundle/Controller/ModelController.php | 8 ++-- .../Rebrickable/ColorController.php | 8 ++-- .../Controller/Rebrickable/SetController.php | 13 +++-- src/AppBundle/Controller/SetController.php | 48 ++----------------- .../Entity/Rebrickable/Inventory.php | 1 - .../Exception/ConvertingFailedException.php | 11 ++--- .../Rebrickable/InventoryRepository.php | 6 +-- .../Rebrickable/Inventory_PartRepository.php | 5 +- .../Rebrickable/Inventory_SetRepository.php | 11 ++--- .../Repository/Rebrickable/SetRepository.php | 7 ++- .../Rebrickable/ThemeRepository.php | 8 ++-- src/AppBundle/Twig/AppExtension.php | 23 ++++----- 17 files changed, 65 insertions(+), 113 deletions(-) diff --git a/src/AppBundle/Api/Client/Brickset/Brickset.php b/src/AppBundle/Api/Client/Brickset/Brickset.php index ace0d9c..62397f0 100644 --- a/src/AppBundle/Api/Client/Brickset/Brickset.php +++ b/src/AppBundle/Api/Client/Brickset/Brickset.php @@ -13,7 +13,6 @@ use AppBundle\Api\Exception\ApiException; use AppBundle\Api\Exception\AuthenticationFailedException; use AppBundle\Api\Exception\CallFailedException; use AppBundle\Api\Exception\EmptyResponseException; -use Symfony\Component\Asset\Exception\LogicException; use Symfony\Component\Debug\Exception\ContextErrorException; class Brickset extends \SoapClient @@ -79,6 +78,7 @@ class Brickset extends \SoapClient try { $this->checkApiKey(); + return $this->__soapCall($method, [$parameters])->{$method.'Result'}; } catch (\SoapFault $e) { throw new CallFailedException(ApiException::BRICKSET); @@ -266,7 +266,7 @@ class Brickset extends \SoapClient { $parameters['apiKey'] = $this->apiKey; - if($this->__soapCall('checkKey', [$parameters])->checkKeyResult != 'OK') { + if ($this->__soapCall('checkKey', [$parameters])->checkKeyResult != 'OK') { throw new AuthenticationFailedException(ApiException::BRICKSET); } } diff --git a/src/AppBundle/Api/Client/Rebrickable/Rebrickable_v3.php b/src/AppBundle/Api/Client/Rebrickable/Rebrickable_v3.php index 97fa006..24d8667 100644 --- a/src/AppBundle/Api/Client/Rebrickable/Rebrickable_v3.php +++ b/src/AppBundle/Api/Client/Rebrickable/Rebrickable_v3.php @@ -9,7 +9,6 @@ use AppBundle\Api\Exception\EmptyResponseException; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ConnectException; -use Symfony\Component\Asset\Exception\LogicException; class Rebrickable_v3 { @@ -59,11 +58,11 @@ class Rebrickable_v3 } catch (ClientException $e) { if ($e->getCode() == 404) { throw new EmptyResponseException(ApiException::REBRICKABLE); - } else if ($e->getCode() == 401) { + } elseif ($e->getCode() == 401) { throw new AuthenticationFailedException(ApiException::REBRICKABLE); } - throw new ApiException(ApiException::REBRICKABLE,$e,$e->getCode()); + throw new ApiException(ApiException::REBRICKABLE, $e, $e->getCode()); } } } diff --git a/src/AppBundle/Api/Manager/BricksetManager.php b/src/AppBundle/Api/Manager/BricksetManager.php index c9ebd71..805eaa7 100644 --- a/src/AppBundle/Api/Manager/BricksetManager.php +++ b/src/AppBundle/Api/Manager/BricksetManager.php @@ -48,15 +48,18 @@ class BricksetManager return isset($sets[0]) ? $sets[0] : null; } - public function getSetInstructions($id) { + public function getSetInstructions($id) + { return $this->bricksetClient->getInstructions($id); } - public function getSetReviews($id) { + public function getSetReviews($id) + { return $this->bricksetClient->getReviews($id); } - public function getAdditionalImages($id) { + public function getAdditionalImages($id) + { return $this->bricksetClient->getAdditionalImages($id); } } diff --git a/src/AppBundle/Api/Manager/RebrickableManager.php b/src/AppBundle/Api/Manager/RebrickableManager.php index 8fc528f..622e020 100644 --- a/src/AppBundle/Api/Manager/RebrickableManager.php +++ b/src/AppBundle/Api/Manager/RebrickableManager.php @@ -119,18 +119,19 @@ class RebrickableManager return $this->serializer->deserialize($data, PartCategory::class, self::FORMAT); } - public function getPartsByLDrawNumber($number) { + public function getPartsByLDrawNumber($number) + { $options = [ 'query' => [ - 'ldraw_id' => $number + 'ldraw_id' => $number, ], ]; - $response = $this->rebrickableClient->call('GET','lego/parts', $options); + $response = $this->rebrickableClient->call('GET', 'lego/parts', $options); $data = json_decode($response, true)['results']; - return $this->serializer->denormalize($data,Part::class.'[]',self::FORMAT); + return $this->serializer->denormalize($data, Part::class.'[]', self::FORMAT); } /** diff --git a/src/AppBundle/Controller/MediaController.php b/src/AppBundle/Controller/MediaController.php index e9e4b23..8ec06c8 100644 --- a/src/AppBundle/Controller/MediaController.php +++ b/src/AppBundle/Controller/MediaController.php @@ -2,13 +2,11 @@ namespace AppBundle\Controller; -use AppBundle\Entity\LDraw\Model; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; -use Symfony\Component\HttpFoundation\StreamedResponse; use Symfony\Component\Routing\Annotation\Route; /** diff --git a/src/AppBundle/Controller/ModelController.php b/src/AppBundle/Controller/ModelController.php index 92ce1d3..329cd68 100644 --- a/src/AppBundle/Controller/ModelController.php +++ b/src/AppBundle/Controller/ModelController.php @@ -28,8 +28,7 @@ class ModelController extends Controller { $form = $this->get('form.factory')->create(ModelFilterType::class); - $filterBuilder = $this->get('repository.ldraw.model') - ->createQueryBuilder('model'); + $filterBuilder = $this->get('repository.ldraw.model')->getFilteredQueryBuilder(); if ($request->query->has($form->getName())) { // manually bind values from the request @@ -62,7 +61,7 @@ class ModelController extends Controller { $em = $this->getDoctrine()->getManager(); - if($model = $this->get('manager.ldraw.model')->findByNumber($number)) { + if ($model = $this->get('repository.ldraw.model')->findOneByNumber($number)) { try { $rbParts = $model != null ? $em->getRepository(Part::class)->findAllByModel($model) : null; $sets = $model != null ? $em->getRepository(Set::class)->findAllByModel($model) : null; @@ -73,12 +72,11 @@ class ModelController extends Controller 'model' => $model, 'rbParts' => $rbParts, 'sets' => $sets, - 'related' => $related + 'related' => $related, ]); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); } - } return $this->render('error/error.html.twig'); diff --git a/src/AppBundle/Controller/Rebrickable/ColorController.php b/src/AppBundle/Controller/Rebrickable/ColorController.php index 6b0b8ad..518f862 100644 --- a/src/AppBundle/Controller/Rebrickable/ColorController.php +++ b/src/AppBundle/Controller/Rebrickable/ColorController.php @@ -3,8 +3,8 @@ namespace AppBundle\Controller\Rebrickable; use AppBundle\Entity\Rebrickable\Color; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; +use Symfony\Bundle\FrameworkBundle\Controller\Controller; /** * Part controller. @@ -14,10 +14,10 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; class ColorController extends Controller { /** - * * @Route("/", name="color_index") */ - public function indexAction() { + public function indexAction() + { $em = $this->getDoctrine()->getManager(); $colors = $em->getRepository(Color::class)->findAll(); @@ -26,4 +26,4 @@ class ColorController extends Controller 'colors' => $colors, ]); } -} \ No newline at end of file +} diff --git a/src/AppBundle/Controller/Rebrickable/SetController.php b/src/AppBundle/Controller/Rebrickable/SetController.php index 485b543..04f74df 100644 --- a/src/AppBundle/Controller/Rebrickable/SetController.php +++ b/src/AppBundle/Controller/Rebrickable/SetController.php @@ -2,18 +2,13 @@ namespace AppBundle\Controller\Rebrickable; -use AppBundle\Api\Exception\EmptyResponseException; -use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\Rebrickable\Color; use AppBundle\Entity\Rebrickable\Inventory_Part; use AppBundle\Entity\Rebrickable\Inventory_Set; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; -use AppBundle\Entity\Rebrickable\Theme; -use AppBundle\Form\FilterSetType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; /** @@ -24,7 +19,8 @@ class SetController extends Controller /** * @Route("/{number}/parts", name="rebrickable_set_parts") */ - public function partsAction(Set $set) { + public function partsAction(Set $set) + { $em = $this->getDoctrine()->getManager(); $em->getRepository(Color::class)->findAll(); @@ -41,13 +37,15 @@ class SetController extends Controller $json = json_encode($template->getContent()); $response = new Response($json, 200); $response->headers->set('Content-Type', 'application/json'); + return $response; } /** * @Route("/{number}/sets", name="rebrickable_set_sets") */ - public function setsAction(Set $set) { + public function setsAction(Set $set) + { $em = $this->getDoctrine()->getManager(); $inventorySets = $em->getRepository(Inventory_Set::class)->findAllBySetNumber($set->getNumber()); @@ -59,6 +57,7 @@ class SetController extends Controller $json = json_encode($template->getContent()); $response = new Response($json, 200); $response->headers->set('Content-Type', 'application/json'); + return $response; } } diff --git a/src/AppBundle/Controller/SetController.php b/src/AppBundle/Controller/SetController.php index 14a1137..0413212 100644 --- a/src/AppBundle/Controller/SetController.php +++ b/src/AppBundle/Controller/SetController.php @@ -4,15 +4,9 @@ namespace AppBundle\Controller; use AppBundle\Api\Exception\ApiException; use AppBundle\Api\Exception\EmptyResponseException; -use AppBundle\Entity\LDraw\Model; -use AppBundle\Entity\Rebrickable\Color; use AppBundle\Entity\Rebrickable\Inventory_Part; -use AppBundle\Entity\Rebrickable\Inventory_Set; -use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; -use AppBundle\Entity\Rebrickable\Theme; use AppBundle\Form\Filter\Set\SetFilterType; -use AppBundle\Form\FilterSetType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; @@ -50,7 +44,7 @@ class SetController extends Controller return $this->render('set/index.html.twig', [ 'sets' => $sets, - 'form' => $form->createView() + 'form' => $form->createView(), ]); } @@ -62,9 +56,9 @@ class SetController extends Controller $rebrickableSet = null; $bricksetSet = null; try { - if(($rebrickableSet = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number)) == null) { + if (($rebrickableSet = $this->getDoctrine()->getManager()->getRepository(Set::class)->find($number)) == null) { $this->addFlash('warning', 'Set not found in Rebrickable database'); - }; + } $bricksetSet = $this->get('api.manager.brickset')->getSetByNumber($number); dump($bricksetSet); @@ -76,7 +70,7 @@ class SetController extends Controller $this->addFlash('error', $e->getMessage()); } - if(!$rebrickableSet && !$bricksetSet) { + if (!$rebrickableSet && !$bricksetSet) { return $this->render('error/error.html.twig'); } @@ -85,38 +79,4 @@ class SetController extends Controller 'brset' => $bricksetSet, ]); } - - /** - * @Route("/{number}/download", name="set_download") - */ - public function downloadZipAction(Request $request, $number) { - $em = $this->getDoctrine()->getManager(); - - $inventoryParts = $em->getRepository(Inventory_Part::class)->findAllRegularBySetNumber($number); - - $zip = new \ZipArchive(); - $zipName = 'set_'.$number.'.zip'; - $zip->open($zipName, \ZipArchive::CREATE); - /** @var Inventory_Part $part */ - foreach ($inventoryParts as $part) { - $filename = $part->getPart()->getNumber().'_('.$part->getColor()->getName().'_'.$part->getQuantity().'x).stl'; - - try { - if($part->getPart()->getModel()) { - $zip->addFromString($filename, $this->get('oneup_flysystem.media_filesystem')->read($part->getPart()->getModel()->getPath())); - } - } catch (\Exception $e) { - dump($e); - } - } - $zip->close(); - - $response = new Response(file_get_contents($zipName)); - $response->headers->set('Content-Type', 'application/zip'); - $response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"'); - $response->headers->set('Content-length', filesize($zipName)); - - return $response; - } - } diff --git a/src/AppBundle/Entity/Rebrickable/Inventory.php b/src/AppBundle/Entity/Rebrickable/Inventory.php index 3a8efe8..d04c23f 100644 --- a/src/AppBundle/Entity/Rebrickable/Inventory.php +++ b/src/AppBundle/Entity/Rebrickable/Inventory.php @@ -45,7 +45,6 @@ class Inventory */ protected $inventorySets; - public function __construct() { $this->inventoryParts = new ArrayCollection(); diff --git a/src/AppBundle/Exception/ConvertingFailedException.php b/src/AppBundle/Exception/ConvertingFailedException.php index d16c2be..5cf7426 100644 --- a/src/AppBundle/Exception/ConvertingFailedException.php +++ b/src/AppBundle/Exception/ConvertingFailedException.php @@ -2,18 +2,19 @@ namespace AppBundle\Exception; - use Exception; class ConvertingFailedException extends \Exception { private $filepath; - public function __construct($filepath = "", $message = "", $code = 0, Exception $previous = null) + public function __construct($form = '', $to = '', $message = '', $code = 0, Exception $previous = null) { + $message = sprintf('Error converting "%s" file to "%s".', $form, $to); + parent::__construct($message, $code, $previous); - $this->filepath = $filepath; + $this->filepath = $form; } /** @@ -31,6 +32,4 @@ class ConvertingFailedException extends \Exception { $this->filepath = $filepath; } - - -} \ No newline at end of file +} diff --git a/src/AppBundle/Repository/Rebrickable/InventoryRepository.php b/src/AppBundle/Repository/Rebrickable/InventoryRepository.php index 0158186..e542f17 100644 --- a/src/AppBundle/Repository/Rebrickable/InventoryRepository.php +++ b/src/AppBundle/Repository/Rebrickable/InventoryRepository.php @@ -7,12 +7,12 @@ use AppBundle\Repository\BaseRepository; class InventoryRepository extends BaseRepository { - public function findNewestInventoryBySetNumber($number) { - + public function findNewestInventoryBySetNumber($number) + { $queryBuilder = $this->createQueryBuilder('inventory') ->where('inventory.set = :setNumber') ->setParameter('setNumber', $number) - ->orderBy('inventory.version','DESC') + ->orderBy('inventory.version', 'DESC') ->setMaxResults(1); return $queryBuilder->getQuery()->getOneOrNullResult(); diff --git a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php index 13f6969..ef63d02 100644 --- a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php +++ b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php @@ -6,7 +6,6 @@ use AppBundle\Entity\LDraw\Category; use AppBundle\Entity\Rebrickable\Inventory; use AppBundle\Entity\Rebrickable\Inventory_Part; use AppBundle\Entity\Rebrickable\Part; -use AppBundle\Entity\Rebrickable\Set; use AppBundle\Repository\BaseRepository; use Doctrine\ORM\Query\Expr\Join; @@ -18,7 +17,7 @@ class Inventory_PartRepository extends BaseRepository $queryBuilder = $this->createQueryBuilder('inventory_part') ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = inventory') - ->join(Part::class,'part',JOIN::WITH,'inventory_part.part = part.number') + ->join(Part::class, 'part', JOIN::WITH, 'inventory_part.part = part.number') ->where('part.category != 17') ->andWhere('inventory.id = :inventoryId') ->setParameter('inventoryId', $inventory->getId()) @@ -34,7 +33,7 @@ class Inventory_PartRepository extends BaseRepository $queryBuilder = $this->createQueryBuilder('inventory_part') ->join(Inventory::class, 'inventory', JOIN::WITH, 'inventory_part.inventory = inventory') - ->join(Part::class,'part',JOIN::WITH,'inventory_part.part = part.number') + ->join(Part::class, 'part', JOIN::WITH, 'inventory_part.part = part.number') ->where('part.category != 17') ->andWhere('inventory.id = :inventoryId') ->setParameter('inventoryId', $inventory->getId()) diff --git a/src/AppBundle/Repository/Rebrickable/Inventory_SetRepository.php b/src/AppBundle/Repository/Rebrickable/Inventory_SetRepository.php index cf0b97d..808d706 100644 --- a/src/AppBundle/Repository/Rebrickable/Inventory_SetRepository.php +++ b/src/AppBundle/Repository/Rebrickable/Inventory_SetRepository.php @@ -3,21 +3,18 @@ namespace AppBundle\Repository\Rebrickable; use AppBundle\Entity\Rebrickable\Inventory; -use AppBundle\Entity\Rebrickable\Inventory_Part; -use AppBundle\Entity\Rebrickable\Set; use AppBundle\Repository\BaseRepository; -use Doctrine\ORM\Query\Expr\Join; class Inventory_SetRepository extends BaseRepository { - public function findAllBySetNumber($number) { - + public function findAllBySetNumber($number) + { $inventory = $this->getEntityManager()->getRepository(Inventory::class)->findNewestInventoryBySetNumber($number); - if($inventory) { + if ($inventory) { $queryBuilder = $this->createQueryBuilder('inventory_set') ->where('inventory_set.inventory = :inventory') - ->setParameter('inventory',$inventory->getId()); + ->setParameter('inventory', $inventory->getId()); return $queryBuilder->getQuery()->getResult(); } diff --git a/src/AppBundle/Repository/Rebrickable/SetRepository.php b/src/AppBundle/Repository/Rebrickable/SetRepository.php index 2614a47..ce6b591 100644 --- a/src/AppBundle/Repository/Rebrickable/SetRepository.php +++ b/src/AppBundle/Repository/Rebrickable/SetRepository.php @@ -5,7 +5,6 @@ namespace AppBundle\Repository\Rebrickable; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\Rebrickable\Inventory; use AppBundle\Entity\Rebrickable\Inventory_Part; -use AppBundle\Entity\Rebrickable\Inventory_Set; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; use AppBundle\Entity\Rebrickable\Theme; @@ -14,12 +13,12 @@ use Doctrine\ORM\Query\Expr\Join; class SetRepository extends BaseRepository { - public function findAllByTheme(Theme $theme) { - + public function findAllByTheme(Theme $theme) + { dump($this->getEntityManager()->getRepository(Theme::class)->findAllSubthemes($theme)); $queryBuilder = $this->createQueryBuilder('s') - ->join(Theme::class, 'theme',Join::WITH, 's.theme = theme') + ->join(Theme::class, 'theme', Join::WITH, 's.theme = theme') ->where('theme.id = :id') ->setParameter('id', $theme->getId()); diff --git a/src/AppBundle/Repository/Rebrickable/ThemeRepository.php b/src/AppBundle/Repository/Rebrickable/ThemeRepository.php index 8752161..fcdaccd 100644 --- a/src/AppBundle/Repository/Rebrickable/ThemeRepository.php +++ b/src/AppBundle/Repository/Rebrickable/ThemeRepository.php @@ -8,12 +8,12 @@ use Doctrine\ORM\Query\Expr\Join; class ThemeRepository extends BaseRepository { - public function findAllSubthemes(Theme $theme) { - - $subQueryBuilder = $this->createQueryBuilder('subtheme'); + public function findAllSubthemes(Theme $theme) + { + $subQueryBuilder = $this->createQueryBuilder('subtheme'); $queryBuilder = $this->createQueryBuilder('subtheme') - ->leftJoin(Theme::class,'theme', Join::WITH, 'subtheme.parent = theme.id') + ->leftJoin(Theme::class, 'theme', Join::WITH, 'subtheme.parent = theme.id') ->where('subtheme.parent = :id') ->setParameter('id', $theme->getId()); diff --git a/src/AppBundle/Twig/AppExtension.php b/src/AppBundle/Twig/AppExtension.php index 1c0c0b0..74e3cb1 100644 --- a/src/AppBundle/Twig/AppExtension.php +++ b/src/AppBundle/Twig/AppExtension.php @@ -3,9 +3,6 @@ namespace AppBundle\Twig; use AppBundle\Api\Manager\RebrickableManager; -use AppBundle\Entity\Rebrickable\Color; -use AppBundle\Entity\Rebrickable\Part; -use AppBundle\Entity\Rebrickable\Set; class AppExtension extends \Twig_Extension { @@ -30,7 +27,8 @@ class AppExtension extends \Twig_Extension ]; } - public function getFunctions() { + public function getFunctions() + { return [ new \Twig_SimpleFunction('remoteSize', [$this, 'remoteSize']), new \Twig_SimpleFunction('remoteFilename', [$this, 'remoteFilename']), @@ -39,7 +37,7 @@ class AppExtension extends \Twig_Extension public function partImage($number, $color = null) { - return '/parts/ldraw/'.($color ? $color :'-1').'/'.$number.'.png'; + return '/parts/ldraw/'.($color ? $color : '-1').'/'.$number.'.png'; } public function setImage($number) @@ -47,21 +45,24 @@ class AppExtension extends \Twig_Extension return '/sets/'.strtolower($number).'.jpg'; } - public function remoteSize($url) { + public function remoteSize($url) + { $ch = curl_init($url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); - curl_setopt($ch, CURLOPT_HEADER, TRUE); - curl_setopt($ch, CURLOPT_NOBODY, TRUE); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HEADER, true); + curl_setopt($ch, CURLOPT_NOBODY, true); $data = curl_exec($ch); $size = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD); curl_close($ch); + return $size; } - public function remoteFilename($url) { - return basename($url); + public function remoteFilename($url) + { + return basename($url); } } From 8a1a5e2cff4a0eb3966d7687545a428960a7f20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 22:15:54 +0200 Subject: [PATCH 38/42] Improve load models command, log loading process --- README.md | 5 +- app/config/config_dev.yml | 9 +- app/config/service/loader.yml | 22 +- src/AppBundle/Command/LoadModelsCommand.php | 58 ++- .../Exception/ErrorParsingLineException.php | 15 + src/AppBundle/Exception/FileException.php | 17 + .../Exception/FileNotFoundException.php | 10 +- .../Exception/ParseErrorException.php | 29 +- .../Exception/WriteErrorException.php | 15 + src/AppBundle/Service/LDViewService.php | 55 ++- .../{BaseLoaderService.php => BaseLoader.php} | 46 ++- src/AppBundle/Service/Loader/ModelLoader.php | 331 ++++++++++++++++++ .../Service/Loader/ModelLoaderService.php | 250 ------------- .../{DatParser.php => LDModelParser.php} | 68 ++-- 14 files changed, 554 insertions(+), 376 deletions(-) create mode 100644 src/AppBundle/Exception/ErrorParsingLineException.php create mode 100644 src/AppBundle/Exception/FileException.php create mode 100644 src/AppBundle/Exception/WriteErrorException.php rename src/AppBundle/Service/Loader/{BaseLoaderService.php => BaseLoader.php} (62%) create mode 100644 src/AppBundle/Service/Loader/ModelLoader.php delete mode 100644 src/AppBundle/Service/Loader/ModelLoaderService.php rename src/AppBundle/Utils/{DatParser.php => LDModelParser.php} (74%) diff --git a/README.md b/README.md index fb24ea2..67d510c 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ For full requirements see Symfony 3.2 [docs](http://symfony.com/doc/3.2/referenc #### Database 1. Set application parameters in *app/config/parameters.yml* -2. Generate empty database by running command `$ php bin/console doctrine:database:create` -3. Load LDraw models into database by running commad `$ php bin/console app:load:ldraw [ldraw_dir_path]` +2. Generate empty database by running command `$ php bin/console doctrine:database:create` +3. Create database tables by running command `$ bin/console doctrine:schema:create` +3. Load LDraw models into database by running commad `$ php bin/console app:load:models [--all] [--file=FILE] [--update]` 4. Load Rebrickable data into database by running command `$ php bin/console app:load:rebrickable` 5. Load relations between LDraw models and Rebrickable parts by running command `$ php bin/console app:load:relation` \ No newline at end of file diff --git a/app/config/config_dev.yml b/app/config/config_dev.yml index 7eb4739..f4d3c25 100644 --- a/app/config/config_dev.yml +++ b/app/config/config_dev.yml @@ -12,15 +12,22 @@ web_profiler: intercept_redirects: false monolog: + channels: ['loader'] handlers: main: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug - channels: [!event] + channels: [!event, !loader] console: type: console channels: [!event, !doctrine] + loader: + type: rotating_file + path: "%kernel.logs_dir%/loader.log" + level: debug + channels: 'loader' + max_files: 10 # uncomment to get logging in your browser # you may have to allow bigger header sizes in your Web server configuration #firephp: diff --git a/app/config/service/loader.yml b/app/config/service/loader.yml index 30b4804..0dbf6f6 100644 --- a/app/config/service/loader.yml +++ b/app/config/service/loader.yml @@ -1,25 +1,25 @@ services: - service.loader: + service.loader.base: abstract: true - class: AppBundle\Service\Loader\BaseLoaderService + class: AppBundle\Service\Loader\BaseLoader calls: - - [setArguments, ['@doctrine.orm.entity_manager', '@app.relation.mapper']] + - [setArguments, ['@doctrine.orm.entity_manager', '@monolog.logger.loader']] service.ldview: class: AppBundle\Service\LDViewService arguments: ['%ldview_bin%', '@oneup_flysystem.media_filesystem'] service.loader.rebrickable: - class: AppBundle\Service\Loader\RebrickableLoaderService - arguments: ['%rebrickable_url%'] - parent: service.loader + class: AppBundle\Service\Loader\RebrickableLoader + arguments: ['%rebrickable_csv_url%'] + parent: service.loader.base service.loader.model: - class: AppBundle\Service\Loader\ModelLoaderService - arguments: ['@service.ldview', '@manager.ldraw', '@app.relation.mapper'] - parent: service.loader + class: AppBundle\Service\Loader\ModelLoader + arguments: ['@service.ldview', '@app.relation.mapper'] + parent: service.loader.base service.loader.relation: class: AppBundle\Service\Loader\RelationLoader - arguments: ['@manager.ldraw.model', '@repository.rebrickable.part', '@api.manager.rebrickable'] - parent: service.loader \ No newline at end of file + arguments: ['@api.manager.rebrickable', '@app.relation.mapper'] + parent: service.loader.base \ No newline at end of file diff --git a/src/AppBundle/Command/LoadModelsCommand.php b/src/AppBundle/Command/LoadModelsCommand.php index c8ac9f1..c115440 100644 --- a/src/AppBundle/Command/LoadModelsCommand.php +++ b/src/AppBundle/Command/LoadModelsCommand.php @@ -19,18 +19,18 @@ class LoadModelsCommand extends ContainerAwareCommand $this ->setName('app:load:models') ->setDescription('Loads LDraw library models into database') - ->setHelp('This command allows you to load LDraw library models into while converting .dat files to .stl') + ->setHelp('This command allows you to load LDraw library models into database while converting .dat files to .stl format.') ->setDefinition( new InputDefinition([ new InputArgument('ldraw', InputArgument::REQUIRED, 'Path to LDraw library directory'), - new InputOption('images', 'i',InputOption::VALUE_NONE, 'Do you want to generate images of models?'), - new InputOption('all','a',InputOption::VALUE_NONE, 'Do you want to load whole LDraw libary?'), - new InputOption('file','f',InputOption::VALUE_REQUIRED, 'Path to DAT file that should be loaded into database') +// new InputOption('images', 'i', InputOption::VALUE_NONE, 'Do you want to generate images of models?'), + new InputOption('all', 'a', InputOption::VALUE_NONE, 'Load all models from LDraw libary folder (/parts directory)'), + new InputOption('file', 'f', InputOption::VALUE_REQUIRED, 'Load single modle into database'), + new InputOption('update', 'u', InputOption::VALUE_NONE, 'Overwrite already loaded models'), ]) ); } - //TODO log errors protected function execute(InputInterface $input, OutputInterface $output) { if (!$this->lock()) { @@ -39,25 +39,49 @@ class LoadModelsCommand extends ContainerAwareCommand return 0; } - $ldrawLoader = $this->getContainer()->get('service.loader.model'); - $ldrawLoader->setOutput($output); - $ldrawLoader->setLDrawLibraryContext(realpath($input->getArgument('ldraw'))); + $modelLoader = $this->getContainer()->get('service.loader.model'); + $modelLoader->setOutput($output); + $modelLoader->setRewite($input->getOption('update')); - try { - if (($ldrawPath = $input->getOption('file')) != null) { - $ldrawLoader->loadFileContext(dirname(realpath($ldrawPath))); + $ldraw = $input->getArgument('ldraw'); - $model = $ldrawLoader->loadModel($ldrawPath); + if ($ldrawPath = realpath($ldraw)) { + $modelLoader->setLDrawLibraryContext($ldrawPath); - if($model !== null) { - $this->getContainer()->get('manager.ldraw.model')->getRepository()->save($model); + if (($path = $input->getOption('file')) != null) { + if ($file = realpath($path)) { + $output->writeln([ + 'Loading model', + 'path: '.$file, + '------------------------------------------------------------------------------', + ]); + + $modelLoader->loadOneModel($file); + + $errorCount = $this->getContainer()->get('monolog.logger.loader')->countErrors(); + $errors = $errorCount ? ''.$errorCount.'' : '0'; + + $output->writeln(['Done with "'.$errors.'" errors.']); + } else { + $output->writeln("File $path not found"); } } + + // Load all models inside ldraw/parts directory if ($input->getOption('all')) { - $ldrawLoader->loadAllModels(); + $output->writeln([ + 'Loading models from LDraw library: '.$ldrawPath.'', + ]); + + $modelLoader->loadAllModels(); + + $errorCount = $this->getContainer()->get('monolog.logger.loader')->countErrors(); + $errors = $errorCount ? ''.$errorCount.'' : '0'; + + $output->writeln(['Done with "'.$errors.'" errors.']); } - } catch (\Exception $e) { - printf($e->getMessage()); + } else { + $output->writeln($ldraw.' is not valid path'); } $this->release(); diff --git a/src/AppBundle/Exception/ErrorParsingLineException.php b/src/AppBundle/Exception/ErrorParsingLineException.php new file mode 100644 index 0000000..ac9005b --- /dev/null +++ b/src/AppBundle/Exception/ErrorParsingLineException.php @@ -0,0 +1,15 @@ +path = $path; + + parent::__construct($message, $code, $previous); + } +} diff --git a/src/AppBundle/Exception/FileNotFoundException.php b/src/AppBundle/Exception/FileNotFoundException.php index 3e12a65..6a278f4 100644 --- a/src/AppBundle/Exception/FileNotFoundException.php +++ b/src/AppBundle/Exception/FileNotFoundException.php @@ -2,8 +2,14 @@ namespace AppBundle\Exception; +use Throwable; -class FileNotFoundException extends \Symfony\Component\Filesystem\Exception\FileNotFoundException +class FileNotFoundException extends FileException { + public function __construct($path, $message = '', $code = 0, Throwable $previous = null) + { + $message = sprintf('File "%s" not found.', $path); -} \ No newline at end of file + parent::__construct($path, $message, $code, $previous); + } +} diff --git a/src/AppBundle/Exception/ParseErrorException.php b/src/AppBundle/Exception/ParseErrorException.php index cdfa3fb..9552965 100644 --- a/src/AppBundle/Exception/ParseErrorException.php +++ b/src/AppBundle/Exception/ParseErrorException.php @@ -2,31 +2,14 @@ namespace AppBundle\Exception; +use Throwable; -class ParseErrorException extends \Exception +class ParseErrorException extends FileException { - private $filepath; - - public function __construct($filepath = "", $message = "", $code = 0, Exception $previous = null) + public function __construct($path, $message = '', $code = 0, Throwable $previous = null) { - parent::__construct($message, $code, $previous); + $message = sprintf('Error parsing "%s" file.', $path); - $this->filepath = $filepath; + parent::__construct($path, $message, $code, $previous); } - - /** - * @return mixed - */ - public function getFilepath() - { - return $this->filepath; - } - - /** - * @param mixed $filepath - */ - public function setFilepath($filepath) - { - $this->filepath = $filepath; - } -} \ No newline at end of file +} diff --git a/src/AppBundle/Exception/WriteErrorException.php b/src/AppBundle/Exception/WriteErrorException.php new file mode 100644 index 0000000..9346cd5 --- /dev/null +++ b/src/AppBundle/Exception/WriteErrorException.php @@ -0,0 +1,15 @@ +ldrawLibraryFilesystem = $ldrawLibraryFilesystem; + $this->ldrawLibraryContext = $ldrawLibraryContext; } /** @@ -57,35 +52,36 @@ class LDViewService * * @param $file * - * @return File * @throws ConvertingFailedException + * + * @return File */ - public function datToStl($file) + public function datToStl($file, $rewrite = false) { if (!$this->mediaFilesystem->has('ldraw'.DIRECTORY_SEPARATOR.'models')) { $this->mediaFilesystem->createDir('ldraw'.DIRECTORY_SEPARATOR.'models'); } - $newFile = 'ldraw'.DIRECTORY_SEPARATOR.'models'.DIRECTORY_SEPARATOR.basename($file,'.dat').'.stl'; + $newFile = 'ldraw'.DIRECTORY_SEPARATOR.'models'.DIRECTORY_SEPARATOR.basename($file, '.dat').'.stl'; - if (!file_exists($newFile) || $this->rewrite) { + if (!$this->mediaFilesystem->has($newFile) || $rewrite) { $this->runLDView([ $file, - '-LDrawDir='.$this->ldrawLibraryFilesystem->getAdapter()->getPathPrefix(), + '-LDrawDir='.$this->ldrawLibraryContext->getAdapter()->getPathPrefix(), '-ExportFiles=1', '-ExportSuffix=.stl', '-ExportsDir='.$this->mediaFilesystem->getAdapter()->getPathPrefix().'ldraw'.DIRECTORY_SEPARATOR.'models', ]); - - // Check if file created successfully - if (!$this->mediaFilesystem->has($newFile)) { - throw new ConvertingFailedException($newFile); + if ($this->mediaFilesystem->has($newFile)) { + return $this->mediaFilesystem->get($newFile); } + } else { + return $this->mediaFilesystem->get($newFile); } - return $this->mediaFilesystem->get($newFile); + throw new ConvertingFailedException($file, 'STL'); } /** @@ -94,21 +90,22 @@ class LDViewService * * @param $file * - * @return File * @throws ConvertingFailedException + * + * @return File */ - public function datToPng($file) + public function datToPng($file, $rewrite = false) { if (!$this->mediaFilesystem->has('ldraw'.DIRECTORY_SEPARATOR.'images')) { $this->mediaFilesystem->createDir('ldraw'.DIRECTORY_SEPARATOR.'images'); } - $newFile = 'ldraw'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.basename($file,'.dat').'.png'; + $newFile = 'ldraw'.DIRECTORY_SEPARATOR.'images'.DIRECTORY_SEPARATOR.basename($file, '.dat').'.png'; if (!$this->mediaFilesystem->has($newFile) || $this->rewrite) { $this->runLDView([ $file, - '-LDrawDir='.$this->ldrawLibraryFilesystem->getAdapter()->getPathPrefix(), + '-LDrawDir='.$this->ldrawLibraryContext->getAdapter()->getPathPrefix(), '-AutoCrop=0', '-SaveAlpha=0', '-BackgroundColor3=0xFFFFFF', @@ -126,12 +123,14 @@ class LDViewService ]); // Check if file created successfully - if (!$this->mediaFilesystem->has($newFile)) { - throw new ConvertingFailedException($newFile); + if ($this->mediaFilesystem->has($newFile)) { + return $this->mediaFilesystem->get($newFile); } + } else { + return $this->mediaFilesystem->get($newFile); } - return $this->mediaFilesystem->get($newFile); + throw new ConvertingFailedException($file, 'PNG'); } /** diff --git a/src/AppBundle/Service/Loader/BaseLoaderService.php b/src/AppBundle/Service/Loader/BaseLoader.php similarity index 62% rename from src/AppBundle/Service/Loader/BaseLoaderService.php rename to src/AppBundle/Service/Loader/BaseLoader.php index 0143da9..07343ee 100644 --- a/src/AppBundle/Service/Loader/BaseLoaderService.php +++ b/src/AppBundle/Service/Loader/BaseLoader.php @@ -2,15 +2,17 @@ namespace AppBundle\Service\Loader; -use AppBundle\Utils\RelationMapper; +use AppBundle\Exception\FileNotFoundException; +use AppBundle\Exception\WriteErrorException; use Doctrine\ORM\EntityManager; +use Monolog\Logger; use Symfony\Bundle\FrameworkBundle\Translation\Translator; use Symfony\Component\Asset\Exception\LogicException; use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Debug\Exception\ContextErrorException; -abstract class BaseLoaderService +abstract class BaseLoader { /** * @var EntityManager @@ -27,8 +29,8 @@ abstract class BaseLoaderService */ protected $progressBar; - /** @var RelationMapper */ - protected $relationMapper; + /** @var Logger */ + protected $logger; /** * Loader constructor. @@ -36,11 +38,12 @@ abstract class BaseLoaderService * @param EntityManager $em * @param Translator $translator */ - public function setArguments(EntityManager $em, $relationMapper) + public function setArguments(EntityManager $em, $logger) { $this->em = $em; - $this->relationMapper = $relationMapper; $this->em->getConnection()->getConfiguration()->setSQLLogger(null); + + $this->logger = $logger; } public function setOutput(OutputInterface $output) @@ -49,11 +52,17 @@ abstract class BaseLoaderService $this->output->setDecorated(true); } + /** + * Initialize new progress bar. + * + * @param $total + */ protected function initProgressBar($total) { $this->progressBar = new ProgressBar($this->output, $total); - $this->progressBar->setFormat('very_verbose'); - $this->progressBar->setFormat('%current%/%max% [%bar%]%percent:3s%% (%elapsed:6s%/%estimated:-6s%)'.PHP_EOL); +// $this->progressBar->setFormat('very_verbose'); + $this->progressBar->setFormat('[%current%/%max%] [%bar%] %percent:3s%% (%elapsed:6s%/%estimated:-6s%) (%filename%)'.PHP_EOL); + $this->progressBar->setBarWidth(70); $this->progressBar->start(); } @@ -62,20 +71,31 @@ abstract class BaseLoaderService switch ($notification_code) { case STREAM_NOTIFY_FILE_SIZE_IS: $this->initProgressBar($bytes_max); + $this->progressBar->setFormat('[%current%/%max%] [%bar%] %percent:3s%% (%elapsed:6s%/%estimated:-6s%)'.PHP_EOL); break; case STREAM_NOTIFY_PROGRESS: $this->progressBar->setProgress($bytes_transferred); break; case STREAM_NOTIFY_COMPLETED: - $this->progressBar->setProgress($bytes_transferred); + $this->progressBar->setMessage('Done'); + $this->progressBar->setProgress($bytes_max); $this->progressBar->finish(); break; } } + /** + * Download file from $url, save it to system temp directory and return filepath. + * + * @param $url + * + * @throws FileNotFoundException + * + * @return bool|string + */ protected function downloadFile($url) { - $this->output->writeln('Downloading file from: '.$url.''); + $this->output->writeln('Loading file from: '.$url.''); $temp = tempnam(sys_get_temp_dir(), 'printabrick.'); $ctx = stream_context_create([], [ @@ -84,12 +104,12 @@ abstract class BaseLoaderService try { if (false === file_put_contents($temp, fopen($url, 'r', 0, $ctx))) { - throw new LogicException('error writing file'); //TODO + throw new WriteErrorException($temp); } } catch (ContextErrorException $e) { - throw new LogicException('wrong url'); //TODO + throw new FileNotFoundException($url); } catch (\Exception $e) { - throw new LogicException('exception: '.$e->getMessage()); //TODO + throw new LogicException($e); } return $temp; diff --git a/src/AppBundle/Service/Loader/ModelLoader.php b/src/AppBundle/Service/Loader/ModelLoader.php new file mode 100644 index 0000000..6e47507 --- /dev/null +++ b/src/AppBundle/Service/Loader/ModelLoader.php @@ -0,0 +1,331 @@ +LDViewService = $LDViewService; + $this->relationMapper = $relationMapper; + $this->ldModelParser = new LDModelParser(); + $this->finder = new Finder(); + } + + /** + * @param bool $rewite + */ + public function setRewite($rewite) + { + $this->rewite = $rewite; + } + + /** + * @param $ldrawLibrary + */ + public function setLDrawLibraryContext($ldrawLibrary) + { + try { + $adapter = new Local($ldrawLibrary); + $this->ldrawLibraryContext = new Filesystem($adapter); + $this->LDViewService->setLDrawLibraryContext($this->ldrawLibraryContext); + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + } + } + + public function loadOneModel($file) + { + $connection = $this->em->getConnection(); + try { + $connection->beginTransaction(); + + $this->loadModel($file); + + $connection->commit(); + } catch (\Exception $e) { + $connection->rollBack(); + $this->logger->error($e->getMessage()); + } + } + + public function loadAllModels() + { + $files = $this->finder->in([ + $this->ldrawLibraryContext->getAdapter()->getPathPrefix(), + ])->path('parts/')->name('*.dat')->depth(1)->files(); + + $this->initProgressBar($files->count()); + + /** @var SplFileInfo $file */ + foreach ($files as $file) { + $connection = $this->em->getConnection(); + $connection->beginTransaction(); + + try { + $this->progressBar->setMessage($file->getFilename(), 'filename'); + + $this->loadModel($file->getRealPath()); + + $connection->commit(); + } catch (\Exception $exception) { + $connection->rollBack(); + $this->logger->error($exception->getMessage()); + } + + $connection->close(); + + $this->progressBar->advance(); + } + $this->progressBar->finish(); + } + + /** + * Load Model entity into database. + * + * @param $file + * + * @return Model|null|false + */ + public function loadModel($file) + { + $fileContext = $this->getFileContext($file); + + // Return model from database if rewrite is not enabled + if (!$this->rewite && $model = $this->em->getRepository(Model::class)->findOneByNumber(basename($file, '.dat'))) { + return $model; + } + + // Parse model file save data to $modelArray + try { + $modelArray = $this->ldModelParser->parse($file); + } catch (ParseErrorException $e) { + $this->logger->error($e->getMessage(), [$file]); + + return null; + } catch (FileException $e) { + $this->logger->error($e->getMessage(), [$file]); + + return null; + } + + // Check if model fulfills rules and should be loaded + if ($this->isModelIncluded($modelArray)) { + // Recursively load model parent (if any) and add model id as alias of parent + if (($parentId = $this->getParentId($modelArray)) && ($parentModelFile = $this->findSubmodelFile($parentId, $fileContext)) !== null) { + $parentModel = $this->loadModel($parentModelFile); + + if ($parentModel) { + $alias = $this->em->getRepository(Alias::class)->getOrCreate($modelArray['id'], $parentModel); + $parentModel->addAlias($alias); + + $this->em->getRepository(Model::class)->save($parentModel); + } else { + $this->logger->info('Model skipped. ', ['number' => $modelArray['id'], 'parent' => $modelArray['parent']]); + } + + return $parentModel; + } + // Load model + + $model = $this->em->getRepository(Model::class)->getOrCreate($modelArray['id']); + + // Recursively load models of subparts + if (isset($modelArray['subparts'])) { + foreach ($modelArray['subparts'] as $subpartId => $count) { + // Try to find model of subpart + if (($subpartFile = $this->findSubmodelFile($subpartId, $fileContext)) !== null) { + $subModel = $this->loadModel($subpartFile); + if ($subModel) { + $subpart = $this->em->getRepository(Subpart::class)->getOrCreate($model, $subModel, $count); + $model->addSubpart($subpart); + } + } else { + $this->logger->error('Subpart file not found', ['subpart' => $subpartId, 'model' => $modelArray]); + } + } + } + + // Add Keywords to model + if (isset($modelArray['keywords'])) { + foreach ($modelArray['keywords'] as $keyword) { + $keyword = $this->em->getRepository(Keyword::class)->getOrCreate(stripslashes(strtolower(trim($keyword)))); + $model->addKeyword($keyword); + } + } + + try { + // update model only if newer version + if (!$model->getModified() || ($model->getModified() < $modelArray['modified'])) { + $stl = $this->LDViewService->datToStl($file, $rewrite)->getPath(); + $model->setPath($stl); + + $model + ->setName($modelArray['name']) + ->setCategory($this->em->getRepository(Category::class)->getOrCreate($modelArray['category'])) + ->setAuthor($this->em->getRepository(Author::class)->getOrCreate($modelArray['author'])) + ->setModified($modelArray['modified']); + + $this->em->getRepository(Model::class)->save($model); + } + } catch (ConvertingFailedException $e) { + $this->logger->error($e->getMessage()); + + return null; + } + + return $model; + } + + return false; + } + + /** + * Get parent id of model from alias_model.yml if defined or return parent id loaded by LDModelParser. + * + * Used to eliminate duplicites of models in library, that could not be determined automatically + * + * @param $modelArray + * + * @return string + */ + private function getParentId($modelArray) + { + if ($this->relationMapper->find($modelArray['id'], 'alias_model') !== $modelArray['id']) { + return $this->relationMapper->find($modelArray['id'], 'alias_model'); + } + + return $modelArray['parent']; + } + + /** + * Find submodel file inside model context or inside LDraw library. + * + * + * LDraw.org Standards: File Format 1.0.2 (http://www.ldraw.org/article/218.html) + * + * "Sub-files can be located in the LDRAW\PARTS sub-directory, the LDRAW\P sub-directory, the LDRAW\MODELS sub-directory, + * the current file's directory, a path relative to one of these directories, or a full path may be specified. Sub-parts are typically + * stored in the LDRAW\PARTS\S sub-directory and so are referenced as s\subpart.dat, while hi-res primitives are stored in the + * LDRAW\P\48 sub-directory and so referenced as 48\hires.dat" + * + * + * @param $id + * @param Filesystem $context + * + * @return string + */ + private function findSubmodelFile($id, $context) + { + // Replace "\" directory separator used inside ldraw model files with system directoru separator + $filename = str_replace('\\', DIRECTORY_SEPARATOR, strtolower($id).'.dat'); + + // Try to find model in current file's directory + if ($context->has($filename)) { + return $context->getAdapter()->getPathPrefix().$filename; + } + // Try to find model in current LDRAW\PARTS sub-directory + elseif ($this->ldrawLibraryContext->has('parts/'.$filename)) { + return $this->ldrawLibraryContext->getAdapter()->getPathPrefix().'parts'.DIRECTORY_SEPARATOR.$filename; + } + // Try to find model in current LDRAW\P sub-directory + elseif ($this->ldrawLibraryContext->has('p'.DIRECTORY_SEPARATOR.$filename)) { + return $this->ldrawLibraryContext->getAdapter()->getPathPrefix().'p'.DIRECTORY_SEPARATOR.$filename; + } + + return null; + } + + /** + * Get new filesystem context of current file. + * + * @param $file + * + * @return Filesystem + */ + private function getFileContext($file) + { + try { + $adapter = new Local(dirname($file)); + + return new Filesystem($adapter); + } catch (Exception $exception) { + $this->logger->error($exception->getMessage()); + } + } + + /** + * Determine if model file should be loaded into database. + * + * @param $modelArray + * + * @return bool + */ + private function isModelIncluded($modelArray) + { + // Do not include part primitives and subparts + if (in_array($modelArray['type'], ['48_Primitive', '8_Primitive', 'Primitive', 'Subpart'])) { + return false; + } + // Do not include sticker models + elseif ($modelArray['type'] == 'Sticker') { + $this->logger->info('Model skipped.', ['number' => $modelArray['id'], 'type' => $modelArray['type']]); + + return false; + } + // Do not include models without permission to redistribute + elseif ($modelArray['license'] != 'Redistributable under CCAL version 2.0') { + $this->logger->info('Model skipped.', ['number' => $modelArray['id'], 'license' => $modelArray['license']]); + + return false; + } + + return true; + } +} diff --git a/src/AppBundle/Service/Loader/ModelLoaderService.php b/src/AppBundle/Service/Loader/ModelLoaderService.php deleted file mode 100644 index bbab54d..0000000 --- a/src/AppBundle/Service/Loader/ModelLoaderService.php +++ /dev/null @@ -1,250 +0,0 @@ -LDViewService = $LDViewService; - $this->ldrawService = $ldrawService; - $this->relationMapper = $relationMapper; - - $this->datParser = new DatParser(); - $this->finder = new Finder(); - } - - public function setLDrawLibraryContext($ldrawLibrary) - { - $adapter = new Local($ldrawLibrary); - $this->ldrawLibraryContext = new Filesystem($adapter); - - $this->LDViewService->setLdrawFilesystem($this->ldrawLibraryContext); - } - - public function setFileContext($file) { - $adapter = new Local($file); - $this->fileContext = new Filesystem($adapter); - } - - public function loadAllModels() - { - $files = $this->finder->in([$this->ldrawLibraryContext->getAdapter()->getPathPrefix()])->path('parts/')->name('*.dat')->depth(1)->files(); - - $modelManager = $this->ldrawService->getModelManager(); - - $this->initProgressBar($files->count()); - - /** @var SplFileInfo $file */ - foreach ($files as $file) { - $this->newModels = []; - - $model = $this->loadModel($file->getRealPath()); - - if($model !== null) { - $modelManager->getRepository()->save($model); - } - - $this->progressBar->advance(); - } - $this->progressBar->finish(); - } - - /** - * Load Model entity into database. - * - * @param $file - * - * @return Model|null - */ - public function loadModel($file) - { - $model = null; - - $modelManager = $this->ldrawService->getModelManager(); - $subpartManager = $this->ldrawService->getSubpartManager(); - - if(($model = $modelManager->findByNumber(basename($file,'.dat'))) || ($model = isset($this->newModels[basename($file,'.dat')]) ? $this->newModels[basename($file,'.dat')] : null)) { - return $model; - } - - try { - $modelArray = $this->datParser->parse($file); - } catch (\Exception $e) { - dump($e->getMessage()); - return null; - } - - if ($this->isModelIncluded($modelArray)) { - - if ($parentModelFile = $this->getParentModelFile($modelArray)) { - try { - if(($parentModel = $this->loadModel($parentModelFile))!= null) { - $alias = $this->ldrawService->getAliasManager()->create($modelArray['id'], $parentModel); - $parentModel->addAlias($alias); - - $this->newModels[$parentModel->getNumber()] = $parentModel; - return $parentModel; - } - } catch (\Exception $e) { - dump('b'); - dump($e->getMessage()); - return null; - } - } else { - $model = $modelManager->create($modelArray['id']); - - if (isset($modelArray['keywords'])) { - foreach ($modelArray['keywords'] as $keyword) { - $keyword = stripslashes(strtolower(trim($keyword))); - $model->addKeyword($this->ldrawService->getKeywordManager()->create($keyword)); - } - } - - if (isset($modelArray['subparts'])) { - foreach ($modelArray['subparts'] as $subpartId => $count) { - if(strpos($subpartId, 's\\') === false) { - if(($subpartFile = $this->findModelFile($subpartId)) != null) { - try { - if ($subModel = $this->loadModel($subpartFile)) { - $subpart = $subpartManager->create($model,$subModel,$count); - - $model->addSubpart($subpart); - } - } catch (\Exception $e) { - dump('c'); - dump($e->getMessage()); - } - } - } - } - } - - $model - ->setName($modelArray['name']) - ->setCategory($this->ldrawService->getCategoryManager()->create($modelArray['category'])) - ->setAuthor($modelArray['author']) - ->setModified($modelArray['modified']) - ->setPath($this->loadStlModel($file)); - - $this->LDViewService->datToPng($file); - - $this->newModels[$model->getNumber()] = $model; - } - } - - return $model; - } - - private function getParentModelFile($modelArray) { - if($this->relationMapper->find($modelArray['id'], 'alias_model') !== $modelArray['id']) { - return $this->findModelFile($this->relationMapper->find($modelArray['id'], 'alias_model')); - } else { - return strpos($modelArray['parent'], 's\\') === false ? $this->findModelFile($modelArray['parent']) : null; - } - } - - /** - * Find model file on ldraw filesystem. - * - * @param $id - * - * @return string - */ - private function findModelFile($id) - { - $filename = strtolower($id).'.dat'; - - if($this->fileContext && $this->fileContext->has($filename)) { - return $this->fileContext->getAdapter()->getPathPrefix().$filename; - } else if ($this->ldrawLibraryContext->has('parts/'.$filename)) { - return $this->ldrawLibraryContext->getAdapter()->getPathPrefix().'parts/'.$filename; - } - - return null; - } - - /** - * Determine if model file should be loaded into database. - * - * @param $modelArray - * - * @return bool - */ - private function isModelIncluded($modelArray) - { - // Do not include sticker parts and incomplete parts - if ( $modelArray['type'] != 'Subpart' && $modelArray['type'] != 'Sticker' ) { - return true; - } - - return false; - } - - /** - * Load stl model by calling LDViewSevice and create new Model. - * - * @param $file - * - * @throws \Exception - * - * @return string path of stl file - */ - private function loadStlModel($file) - { - try { - return $this->LDViewService->datToStl($file)->getPath(); - } catch (ConvertingFailedException $e) { - throw $e; //TODO - } - } -} diff --git a/src/AppBundle/Utils/DatParser.php b/src/AppBundle/Utils/LDModelParser.php similarity index 74% rename from src/AppBundle/Utils/DatParser.php rename to src/AppBundle/Utils/LDModelParser.php index 99f4b84..a789fc5 100644 --- a/src/AppBundle/Utils/DatParser.php +++ b/src/AppBundle/Utils/LDModelParser.php @@ -5,13 +5,11 @@ namespace AppBundle\Utils; use AppBundle\Exception\FileNotFoundException; use AppBundle\Exception\ParseErrorException; use League\Flysystem\File; -use Symfony\Component\Asset\Exception\LogicException; -class DatParser +class LDModelParser { /** - * Parse LDraw .dat file header identifying model store data to array. - * + * Parse LDraw model .dat file and return associative array in format: * [ * 'id' => string * 'name' => string @@ -20,17 +18,19 @@ class DatParser * 'author' => string * 'modified' => DateTime * 'type' => string - * 'subparts' => [] - * ] + * 'subparts' => [], + * 'licence' => string + * ]. * * LDraw.org Standards: Official Library Header Specification (http://www.ldraw.org/article/398.html) * - * @return array * @throws FileNotFoundException|ParseErrorException + * + * @return array */ public function parse($file) { - if(file_exists($file)) { + if (file_exists($file)) { $model = [ 'id' => null, 'name' => null, @@ -40,7 +40,8 @@ class DatParser 'modified' => null, 'type' => null, 'subparts' => [], - 'parent' => null + 'parent' => null, + 'license' => null, ]; try { @@ -63,19 +64,24 @@ class DatParser $model['name'] = preg_replace('/ {2,}/', ' ', ltrim($line, '=_')); $firstLine = true; - } // 0 !CATEGORY + } + // 0 !CATEGORY elseif (strpos($line, '!CATEGORY ') === 0) { $model['category'] = trim(preg_replace('/^!CATEGORY /', '', $line)); - } // 0 !KEYWORDS , , ..., + } + // 0 !KEYWORDS , , ..., elseif (strpos($line, '!KEYWORDS ') === 0) { $model['keywords'] = explode(', ', preg_replace('/^!KEYWORDS /', '', $line)); - } // 0 Name: .dat + } + // 0 Name: .dat elseif (strpos($line, 'Name: ') === 0 && !isset($header['id'])) { $model['id'] = preg_replace('/(^Name: )(.*)(.dat)/', '$2', $line); - } // 0 Author: [] + } + // 0 Author: [] elseif (strpos($line, 'Author: ') === 0) { $model['author'] = preg_replace('/^Author: /', '', $line); - } // 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR + } + // 0 !LDRAW_ORG Part|Subpart|Primitive|48_Primitive|Shortcut (optional qualifier(s)) ORIGINAL|UPDATE YYYY-RR elseif (strpos($line, '!LDRAW_ORG ') === 0) { $type = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE| ORIGINAL)(.*)/', '$2', $line); @@ -84,17 +90,23 @@ class DatParser // Last modification date in format YYYY-RR $date = preg_replace('/(^!LDRAW_ORG )(.*)( UPDATE | ORIGINAL )(.*)/', '$4', $line); if (preg_match('/^[1-2][0-9]{3}-[0-9]{2}$/', $date)) { - $model['modified'] = \DateTime::createFromFormat('Y-m-d H:i:s', $date . '-01 00:00:00'); + $model['modified'] = \DateTime::createFromFormat('Y-m-d H:i:s', $date.'-01 00:00:00'); } } + // 0 !LICENSE Redistributable under CCAL version 2.0 : see CAreadme.txt | 0 !LICENSE Not redistributable : see NonCAreadme.txt + elseif (strpos($line, '!LICENSE ') === 0) { + $model['license'] = preg_replace('/(^!LICENSE )(.*) : (.*)$/', '$2', $line); + } } elseif (strpos($line, '1 ') === 0) { - $id = $this->getReferencedModelNumber($line); + $id = strtolower($this->getReferencedModelNumber($line)); - if(isset($model['subparts'][$id])) { + if (isset($model['subparts'][$id])) { $model['subparts'][$id] = $model['subparts'][$id] + 1; } else { $model['subparts'][$id] = 1; } + } elseif (!empty($line) && !in_array($line[0], ['2', '3', '4', '5'])) { + throw new ErrorParsingLineException($file,$line); } } @@ -102,14 +114,12 @@ class DatParser $model['type'] = 'Sticker'; } elseif (count($model['subparts']) == 1 && in_array($model['type'], ['Part Alias', 'Shortcut Physical_Colour', 'Shortcut Alias', 'Part Physical_Colour'])) { $model['parent'] = array_keys($model['subparts'])[0]; - } elseif ($parent = $this->getPrintedModelParentNumber($model['id'])) { + } elseif (($parent = $this->getPrintedModelParentNumber($model['id'])) && !in_array($model['type'], ['48_Primitive', '8_Primitive', 'Primitive', 'Subpart'])) { $model['type'] = 'Printed'; $model['parent'] = $parent; } elseif ($parent = $this->getObsoleteModelParentNumber($model['name'])) { $model['type'] = 'Alias'; $model['parent'] = $parent; - } elseif (strpos($model['name'], '~') === 0 && $model['type'] != 'Alias') { - $model['type'] = 'Obsolete/Subpart'; } fclose($handle); @@ -137,10 +147,10 @@ class DatParser */ public function getReferencedModelNumber($line) { - if(preg_match('/^1 16 0 0 0 -1 0 0 0 1 0 0 0 1 (.*)\.(dat|DAT)$/', $line, $matches)) - return null; - if (preg_match('/^1(.*) (.*)\.(dat|DAT)$/', $line, $matches)) { - return $matches[2]; + $line = ($line); + + if (preg_match('/^1(.*) (.*)\.dat$/', strtolower($line), $matches)) { + return str_replace('\\', DIRECTORY_SEPARATOR, $matches[2]); } return null; @@ -160,7 +170,7 @@ class DatParser */ public function getPrintedModelParentNumber($id) { - if (preg_match('/(^.*)(p[0-9a-z][0-9a-z][0-9a-z]{0,1})$/', $id, $matches)) { + if (preg_match('/(^.*)(p[0-9a-z]{2,3})$/', $id, $matches)) { return $matches[1]; } @@ -178,7 +188,7 @@ class DatParser * @param $name * @param $number * - * @return string|null LDraw number of printed part parent + * @return bool */ public function isSticker($name, $number) { @@ -186,8 +196,8 @@ class DatParser return true; } - // Check if in format nnnDaa == sticker - return preg_match('/(^.*)(d[a-z0-9][a-z0-9])$/', $number); + // Check if in format n*Daa == sticker + return preg_match('/(^.*)(d[0-9]{2})$/', $number); } /** @@ -205,7 +215,7 @@ class DatParser public function getObsoleteModelParentNumber($name) { if (preg_match('/^(~Moved to )(.*)$/', $name, $matches)) { - return $matches[2]; + return str_replace('\\', DIRECTORY_SEPARATOR, $matches[2]); } return null; From 809b86ce195ba2a5e7ca01db3dc42e0c5f009b6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 22:18:05 +0200 Subject: [PATCH 39/42] Improve load rebrickable data commad --- app/config/config.yml | 4 +- .../Command/LoadRebrickableDataCommand.php | 4 +- .../Service/Loader/RebrickableLoader.php | 166 ++++++++++++++++++ .../Loader/RebrickableLoaderService.php | 152 ---------------- 4 files changed, 170 insertions(+), 156 deletions(-) create mode 100644 src/AppBundle/Service/Loader/RebrickableLoader.php delete mode 100644 src/AppBundle/Service/Loader/RebrickableLoaderService.php diff --git a/app/config/config.yml b/app/config/config.yml index c6f65ad..9bf88c9 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -7,8 +7,8 @@ imports: # http://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration parameters: locale: en - # rebrickable csv files root URL - rebrickable_url: 'http://rebrickable.com/media/downloads/' + # rebrickable csv files root URL (http://rebrickable.com/media/downloads/ or local dir containing csv files) + rebrickable_csv_url: 'http://rebrickable.com/media/downloads/' framework: #esi: ~ diff --git a/src/AppBundle/Command/LoadRebrickableDataCommand.php b/src/AppBundle/Command/LoadRebrickableDataCommand.php index a9c441e..71d7135 100644 --- a/src/AppBundle/Command/LoadRebrickableDataCommand.php +++ b/src/AppBundle/Command/LoadRebrickableDataCommand.php @@ -12,8 +12,8 @@ class LoadRebrickableDataCommand extends ContainerAwareCommand { $this ->setName('app:load:rebrickable') - ->setDescription('Loads Rebrickable database') - ->setHelp('This command allows you to..'); + ->setDescription('Loads Rebrickable data about sets and parts into database.') + ->setHelp('This command allows you to load Rebrickable CSV files containing information about sets and parts into database.'); } protected function execute(InputInterface $input, OutputInterface $output) diff --git a/src/AppBundle/Service/Loader/RebrickableLoader.php b/src/AppBundle/Service/Loader/RebrickableLoader.php new file mode 100644 index 0000000..7df7f91 --- /dev/null +++ b/src/AppBundle/Service/Loader/RebrickableLoader.php @@ -0,0 +1,166 @@ +rebrickable_url = $rebrickable_url; + } + + public function loadTables() + { + $connection = $this->em->getConnection(); + $connection->beginTransaction(); + + try { + $this->loadCsvFiles(); + + $connection->prepare('SET foreign_key_checks = 0;')->execute(); + $this->truncateTables(); + $connection->prepare('SET foreign_key_checks = 1;')->execute(); + + $this->output->writeln([ + 'Truncated rebrickable database tables.', + '------------------------------------------------------------------------------', + 'Loading CSV files into database...', + ]); + + $this->loadColorTable($this->csvFile['colors']); + $this->loadCategoryTable($this->csvFile['part_categories']); + $this->loadPartTable($this->csvFile['parts']); + $this->loadThemeTable($this->csvFile['themes']); + $this->loadSetTable($this->csvFile['sets']); + $this->loadInventoryTable($this->csvFile['inventories']); + $this->loadInventorySetTable($this->csvFile['inventory_sets']); + + $connection->prepare('SET foreign_key_checks = 0;')->execute(); + $this->loadInventoryPartTable($this->csvFile['inventory_parts']); + $connection->prepare('SET foreign_key_checks = 1;')->execute(); + $this->addMissingParts(); + + $connection->commit(); + + $this->output->writeln('Rebrickable database loaded successfully!'); + } catch (\Exception $e) { + $connection->rollBack(); + throw $e; + } + } + + private function loadCSVFiles() + { + $array = ['inventories', 'inventory_parts', 'inventory_sets', 'sets', 'themes', 'parts', 'part_categories', 'colors']; + + $this->output->writeln([ + 'Loading Rebrickable CSV files', + '------------------------------------------------------------------------------', + ]); + + foreach ($array as $item) { + $this->csvFile[$item] = $this->downloadFile($this->rebrickable_url.$item.'.csv'); + } + + $this->output->writeln([ + 'Done', + '------------------------------------------------------------------------------', + ]); + } + + private function truncateTables() + { + $query = ' + TRUNCATE TABLE rebrickable_inventory_parts; + TRUNCATE TABLE rebrickable_inventory_sets; + TRUNCATE TABLE rebrickable_inventory; + TRUNCATE TABLE rebrickable_set; + TRUNCATE TABLE rebrickable_theme; + TRUNCATE TABLE rebrickable_part; + TRUNCATE TABLE rebrickable_category; + TRUNCATE TABLE rebrickable_color; + '; + + return $this->em->getConnection()->prepare($query)->execute(); + } + + private function loadCsvFile($file, $table, $columns) + { + $query = sprintf("LOAD DATA LOCAL INFILE '%s' + REPLACE INTO TABLE %s + FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' + LINES TERMINATED BY '\\n' + IGNORE 1 LINES %s", addslashes($file), $table, $columns); + + return $this->em->getConnection()->prepare($query)->execute(); + } + + private function addMissingParts() + { + $connection = $this->em->getConnection(); + $statement = $connection->prepare( + 'SELECT DISTINCT rebrickable_inventory_parts.part_id FROM rebrickable_inventory_parts + LEFT JOIN rebrickable_part ON rebrickable_inventory_parts.part_id = rebrickable_part.id + WHERE rebrickable_part.id IS NULL'); + $statement->execute(); + $foreignKeys = $statement->fetchAll(); + + foreach ($foreignKeys as $foreignKey) { + $part = new Part(); + $part->setNumber($foreignKey['part_id']); + $this->em->getRepository(Part::class)->save($part); + } + } + + private function loadInventoryTable($csv) + { + return $this->loadCsvFile($csv, 'rebrickable_inventory', '(`id`,`version`,`set_id`)'); + } + + private function loadInventoryPartTable($csv) + { + return $this->loadCsvFile($csv, 'rebrickable_inventory_parts', '(`inventory_id`,`part_id`,`color_id`,`quantity`, @var) SET spare = IF(@var=\'t\',1,0)'); + } + + private function loadInventorySetTable($csv) + { + return $this->loadCsvFile($csv, 'rebrickable_inventory_sets', '(`inventory_id`,`set_id`,`quantity`)'); + } + + private function loadSetTable($csv) + { + return $this->loadCsvFile($csv, 'rebrickable_set', '(`id`,`name`,`year`,`theme_id`,`num_parts`)'); + } + + private function loadThemeTable($csv) + { + return $this->loadCsvFile($csv, 'rebrickable_theme', '(`id`,`name`,@var) SET parent_id = nullif(@var,\'\')'); + } + + private function loadPartTable($csv) + { + return $this->loadCsvFile($csv, 'rebrickable_part', '(`id`,`name`,`category_id`)'); + } + + private function loadCategoryTable($csv) + { + return $this->loadCsvFile($csv, 'rebrickable_category', '(`id`,`name`)'); + } + + private function loadColorTable($csv) + { + return $this->loadCsvFile($csv, 'rebrickable_color', '(`id`,`name`,`rgb`, @var) SET transparent = IF(@var=\'t\',1,0)'); + } +} diff --git a/src/AppBundle/Service/Loader/RebrickableLoaderService.php b/src/AppBundle/Service/Loader/RebrickableLoaderService.php deleted file mode 100644 index 65bfa34..0000000 --- a/src/AppBundle/Service/Loader/RebrickableLoaderService.php +++ /dev/null @@ -1,152 +0,0 @@ -rebrickable_url = $rebrickable_url; - } - - public function loadTables() - { - $connection = $this->em->getConnection(); - - try { - $connection->beginTransaction(); - $connection->prepare('SET foreign_key_checks = 0;')->execute(); - $this->truncateTables(); - $connection->prepare('SET foreign_key_checks = 1;')->execute(); - - $this->loadColorTable(); - $this->loadCategoryTable(); - $this->loadPartTable(); - $this->loadThemeTable(); - $this->loadSetTable(); - $this->loadInventoryTable(); - $this->loadInventorySetTable(); - - $connection->prepare('SET foreign_key_checks = 0;')->execute(); - $this->loadInventoryPartTable(); - $connection->prepare('SET foreign_key_checks = 1;')->execute(); - $this->addMissingParts(); - - $connection->commit(); - } catch (\Exception $e) { - $connection->rollBack(); - throw $e; - } - } - - private function truncateTables() - { - $query =' - TRUNCATE TABLE rebrickable_inventory_parts; - TRUNCATE TABLE rebrickable_inventory_sets; - TRUNCATE TABLE rebrickable_color; - TRUNCATE TABLE rebrickable_category; - TRUNCATE TABLE rebrickable_inventory; - TRUNCATE TABLE rebrickable_set; - TRUNCATE TABLE rebrickable_theme; - TRUNCATE TABLE rebrickable_part; - '; - - return $this->em->getConnection()->prepare($query)->execute(); - } - - private function loadCsvFile($file, $table, $columns) - { - $query = sprintf("LOAD DATA LOCAL INFILE '%s' - REPLACE INTO TABLE %s - FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' - LINES TERMINATED BY '\\n' - IGNORE 1 LINES %s", addslashes($file), $table, $columns); - - return $this->em->getConnection()->prepare($query)->execute(); - } - - private function addMissingParts() { - $connection = $this->em->getConnection(); - $statement = $connection->prepare( - 'SELECT DISTINCT rebrickable_inventory_parts.part_id FROM rebrickable_inventory_parts - LEFT JOIN rebrickable_part ON rebrickable_inventory_parts.part_id = rebrickable_part.id - WHERE rebrickable_part.id IS NULL'); - $statement->execute(); - $foreignKeys = $statement->fetchAll(); - - foreach ($foreignKeys as $foreignKey) { - $part = new Part(); - $part->setNumber($foreignKey['part_id']); - $this->em->getRepository(Part::class)->save($part); - } - } - - private function loadInventoryTable() - { - $file = $this->downloadFile($this->rebrickable_url.'inventories.csv'); - - return $this->loadCsvFile($file, 'rebrickable_inventory', '(`id`,`version`,`set_id`)'); - } - - private function loadInventoryPartTable() - { - $file = $this->downloadFile($this->rebrickable_url.'inventory_parts.csv'); - - return $this->loadCsvFile($file, 'rebrickable_inventory_parts', '(`inventory_id`,`part_id`,`color_id`,`quantity`, @var) SET spare = IF(@var=\'t\',1,0)'); - } - - private function loadInventorySetTable() - { - $file = $this->downloadFile($this->rebrickable_url.'inventory_sets.csv'); - - return $this->loadCsvFile($file, 'rebrickable_inventory_sets', '(`inventory_id`,`set_id`,`quantity`)'); - } - - private function loadSetTable() - { - $file = $this->downloadFile($this->rebrickable_url.'sets.csv'); - - return $this->loadCsvFile($file, 'rebrickable_set', '(`id`,`name`,`year`,`theme_id`,`num_parts`)'); - } - - private function loadThemeTable() - { - $file = $this->downloadFile($this->rebrickable_url.'themes.csv'); - - return $this->loadCsvFile($file, 'rebrickable_theme', '(`id`,`name`,@var) SET parent_id = nullif(@var,\'\')'); - } - - private function loadPartTable() - { - $file = $this->downloadFile($this->rebrickable_url.'parts.csv'); - - return $this->loadCsvFile($file, 'rebrickable_part', '(`id`,`name`,`category_id`)'); - } - - private function loadCategoryTable() - { - $file = $this->downloadFile($this->rebrickable_url.'part_categories.csv'); - - return $this->loadCsvFile($file, 'rebrickable_category', '(`id`,`name`)'); - } - - private function loadColorTable() - { - $file = $this->downloadFile($this->rebrickable_url.'colors.csv'); - - return $this->loadCsvFile($file, 'rebrickable_color', '(`id`,`name`,`rgb`, @var) SET transparent = IF(@var=\'t\',1,0)'); - } -} From e5451ff9cd69e7776149baccf791433861b29f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 22:19:11 +0200 Subject: [PATCH 40/42] Remove inventory controller --- .../rebrickable/set/inventory_sets.html.twig | 17 --------- .../Controller/Brickset/SetController.php | 23 +++++------- .../Rebrickable/InventoryController.php | 37 ------------------- .../Controller/Rebrickable/PartController.php | 26 ++++++++++++- 4 files changed, 35 insertions(+), 68 deletions(-) delete mode 100644 app/Resources/views/rebrickable/set/inventory_sets.html.twig delete mode 100644 src/AppBundle/Controller/Rebrickable/InventoryController.php diff --git a/app/Resources/views/rebrickable/set/inventory_sets.html.twig b/app/Resources/views/rebrickable/set/inventory_sets.html.twig deleted file mode 100644 index 098c2fc..0000000 --- a/app/Resources/views/rebrickable/set/inventory_sets.html.twig +++ /dev/null @@ -1,17 +0,0 @@ -

- Sets -

-
- {% for inventorySet in inventorySets %} - - {% endfor %} -
\ No newline at end of file diff --git a/src/AppBundle/Controller/Brickset/SetController.php b/src/AppBundle/Controller/Brickset/SetController.php index 96658d3..25336ee 100644 --- a/src/AppBundle/Controller/Brickset/SetController.php +++ b/src/AppBundle/Controller/Brickset/SetController.php @@ -3,9 +3,6 @@ namespace AppBundle\Controller\Brickset; use AppBundle\Api\Exception\EmptyResponseException; -use AppBundle\Entity\Rebrickable\Color; -use AppBundle\Entity\Rebrickable\Inventory_Part; -use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; use AppBundle\Entity\Rebrickable\Theme; use AppBundle\Form\FilterSetType; @@ -37,7 +34,7 @@ class SetController extends Controller 'subtheme' => $data['subtheme'] ? $data['subtheme']->getSubtheme() : '', 'year' => $data['years'] ? $data['years']->getYear() : '', ]); - } catch (EmptyResponseException $e) { + } catch (EmptyResponseException $e) { $this->addFlash('warning', 'No set found on '.$e->getService()); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); @@ -58,13 +55,13 @@ class SetController extends Controller $instructions = []; try { $instructions = $this->get('api.manager.brickset')->getSetInstructions($id); - } catch (EmptyResponseException $e) { -// $this->addFlash('warning', 'No instruction found on Brickset.com'); + } catch (EmptyResponseException $e) { + // $this->addFlash('warning', 'No instruction found on Brickset.com'); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); } - return $this->render('brickset/instructions.html.twig',[ + return $this->render('brickset/instructions.html.twig', [ 'instructions' => $instructions, ]); } @@ -77,13 +74,13 @@ class SetController extends Controller $reviews = []; try { $reviews = $this->get('api.manager.brickset')->getSetReviews($id); - } catch (EmptyResponseException $e) { -// $this->addFlash('warning', 'No review found on Brickset.com'); + } catch (EmptyResponseException $e) { + // $this->addFlash('warning', 'No review found on Brickset.com'); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); } - return $this->render('brickset/reviews.html.twig',[ + return $this->render('brickset/reviews.html.twig', [ 'reviews' => $reviews, ]); } @@ -96,13 +93,13 @@ class SetController extends Controller $images = []; try { $images = $this->get('api.manager.brickset')->getAdditionalImages($id); - } catch (EmptyResponseException $e) { -// $this->addFlash('warning', 'No images found on Brickset.com'); + } catch (EmptyResponseException $e) { + // $this->addFlash('warning', 'No images found on Brickset.com'); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); } - return $this->render('brickset/images.html.twig',[ + return $this->render('brickset/images.html.twig', [ 'images' => $images, ]); } diff --git a/src/AppBundle/Controller/Rebrickable/InventoryController.php b/src/AppBundle/Controller/Rebrickable/InventoryController.php deleted file mode 100644 index 56480e9..0000000 --- a/src/AppBundle/Controller/Rebrickable/InventoryController.php +++ /dev/null @@ -1,37 +0,0 @@ -getDoctrine()->getManager(); - - $inventorySets = $em->getRepository(Set::class)->findAllByInventory($inventory); - - return $this->render('rebrickable/set/inventory_sets.html.twig', [ - 'inventorySets' => $inventorySets, - ]); - } -} diff --git a/src/AppBundle/Controller/Rebrickable/PartController.php b/src/AppBundle/Controller/Rebrickable/PartController.php index 1ca0637..421251c 100644 --- a/src/AppBundle/Controller/Rebrickable/PartController.php +++ b/src/AppBundle/Controller/Rebrickable/PartController.php @@ -3,11 +3,13 @@ namespace AppBundle\Controller\Rebrickable; use AppBundle\Api\Exception\EmptyResponseException; +use AppBundle\Entity\Rebrickable\Category; use AppBundle\Entity\Rebrickable\Part; use AppBundle\Entity\Rebrickable\Set; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\Request; /** * Part controller. @@ -16,6 +18,28 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; */ class PartController extends Controller { + /** + * @Route("/", name="part_index") + */ + public function indexAction(Request $request) + { + $em = $this->getDoctrine()->getManager(); + + $queryBuilder = $em->getRepository(Part::class)->createQueryBuilder('part'); + $queryBuilder->where('part.category = 17'); + + $paginator = $this->get('knp_paginator'); + $sets = $paginator->paginate( + $queryBuilder->getQuery(), + $request->query->getInt('page', 1)/*page number*/, + $request->query->getInt('limit', 30)/*limit per page*/ + ); + + return $this->render(':rebrickable/part:index.html.twig', [ + 'parts' => $sets, + ]); + } + /** * Finds and displays a part entity. * @@ -28,7 +52,7 @@ class PartController extends Controller $apiPart = null; - if($part) { + if ($part) { try { $apiPart = $this->get('api.manager.rebrickable')->getPart($part->getNumber()); } catch (EmptyResponseException $e) { From cafc6e55b09c8043289d5ba105247dbc75e7dca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 22:19:41 +0200 Subject: [PATCH 41/42] Add load relations command description, add relations --- app/Resources/relations/alias_model.yml | 36 ++++++++++++++++++- app/Resources/relations/part_model.yml | 5 --- src/AppBundle/Command/LoadRelationCommand.php | 4 +-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/app/Resources/relations/alias_model.yml b/app/Resources/relations/alias_model.yml index 2a972f2..095ec1f 100644 --- a/app/Resources/relations/alias_model.yml +++ b/app/Resources/relations/alias_model.yml @@ -121,6 +121,22 @@ u9147p01c06: u9147p01c02 u9147p02c02: u9147p01c02 u9147p03c02: u9147p01c02 +# Figure Fabuland Monkey +u595p01c08: u595p01c04 +u595p01c07: u595p01c04 +u595p01c06: u595p01c04 +u595p01c05: u595p01c04 + +# Figure Fabuland Monkey Head +u595p02c03: u595p02c02 + +# Figure Fabuland Monkey 2 +u595p02c05: u595p02c04 + +# Figure Fabuland Mouse 1 +u9105p01c06: u9105p01c02 +u9105p01c03: u9105p01c02 + # Figure Fabuland Elephant Head u588p02c02: u588p01c02 @@ -183,4 +199,22 @@ u588p02c02: u588p01c02 32181c03: 32181c01 76019: 76244 -4107539: 76244 \ No newline at end of file +4107539: 76244 + +3680c02: 3680c01 +4221774: 3680c01 +9245: 3680c01 +9246: 3680c01 +9258: 3680c01 +9328: 3680c01 +9352: 3680c01 + +4508c01: 251c01 + +4100338: 2557c01 +4100340: 6051c01 + +4100339: 2559c01 +75192: 2977c01 + +4539364: 64776 \ No newline at end of file diff --git a/app/Resources/relations/part_model.yml b/app/Resources/relations/part_model.yml index 5edac4f..31c85be 100644 --- a/app/Resources/relations/part_model.yml +++ b/app/Resources/relations/part_model.yml @@ -89,7 +89,6 @@ 10119: 51704 3149: 3149d 75998: 4493c00 -10119: 51704 30658: 3404 59275: 2599 @@ -168,10 +167,6 @@ wheel2a: 568c01 76320: 32181c02 -76320: 32181c02 - -58123b: 58123p01 - 95292c01: 75348 2909c03: 75348 2909c02: 75348 diff --git a/src/AppBundle/Command/LoadRelationCommand.php b/src/AppBundle/Command/LoadRelationCommand.php index bffaaf8..d90ebeb 100644 --- a/src/AppBundle/Command/LoadRelationCommand.php +++ b/src/AppBundle/Command/LoadRelationCommand.php @@ -12,8 +12,8 @@ class LoadRelationCommand extends ContainerAwareCommand { $this ->setName('app:load:relations') - ->setDescription('Loads relations between LDraw models and Rebrickable parts') - ->setHelp('This command allows you to..'); + ->setDescription('Loads relations between LDraw models and Rebrickable parts.') + ->setHelp('This command allows you to load relation between models and parts into database.'); } protected function execute(InputInterface $input, OutputInterface $output) From d62ec288c384257a78cfeed603af3f470ca50993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Sun, 16 Apr 2017 22:20:08 +0200 Subject: [PATCH 42/42] Add Stats class --- src/AppBundle/Utils/Stats.php | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/AppBundle/Utils/Stats.php diff --git a/src/AppBundle/Utils/Stats.php b/src/AppBundle/Utils/Stats.php new file mode 100644 index 0000000..64d0fee --- /dev/null +++ b/src/AppBundle/Utils/Stats.php @@ -0,0 +1,61 @@ +skipped = 0; + $this->error = 0; + $this->success = 0; + } + + public function success() + { + $this->success = $this->success + 1; + } + + public function error() + { + $this->error = $this->error + 1; + } + + public function skipped() + { + $this->skipped = $this->skipped + 1; + } + + /** + * @return int + */ + public function getSuccess() + { + return $this->success; + } + + /** + * @return int + */ + public function getError() + { + return $this->error; + } + + /** + * @return int + */ + public function getSkipped() + { + return $this->skipped; + } +}