From a20e31da9bb34bd962404f232dde812943c9a35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20H=C3=BCbner?= Date: Fri, 5 May 2017 21:04:25 +0200 Subject: [PATCH] Change ZipStream to ZipArchive --- app/Resources/views/embeds/modal.html.twig | 19 ++++ app/config/service/service.yml | 8 +- src/AppBundle/Controller/ModelController.php | 24 ++++- src/AppBundle/Controller/SetController.php | 31 ++++++- .../Rebrickable/Inventory_PartRepository.php | 34 ++++++- .../Rebrickable/ThemeRepository.php | 8 ++ src/AppBundle/Service/ModelService.php | 35 +++++++ src/AppBundle/Service/SetService.php | 42 +++++++-- src/AppBundle/Service/ZipService.php | 91 +++++++++++++------ 9 files changed, 241 insertions(+), 51 deletions(-) create mode 100644 app/Resources/views/embeds/modal.html.twig create mode 100644 src/AppBundle/Service/ModelService.php diff --git a/app/Resources/views/embeds/modal.html.twig b/app/Resources/views/embeds/modal.html.twig new file mode 100644 index 0000000..619d892 --- /dev/null +++ b/app/Resources/views/embeds/modal.html.twig @@ -0,0 +1,19 @@ + \ No newline at end of file diff --git a/app/config/service/service.yml b/app/config/service/service.yml index c0fc0f2..8b1c41a 100644 --- a/app/config/service/service.yml +++ b/app/config/service/service.yml @@ -17,11 +17,15 @@ services: service.zip: class: AppBundle\Service\ZipService - arguments: ['@oneup_flysystem.media_filesystem', '@service.set'] + arguments: ['@oneup_flysystem.media_filesystem', '@service.set', '@service.model'] service.set: class: AppBundle\Service\SetService - arguments: ['@repository.rebrickable.inventorypart'] arguments: ['@repository.rebrickable.inventorypart'] app.part_image_loader: + arguments: ['@repository.rebrickable.inventorypart'] + + service.model: + class: AppBundle\Service\ModelService + app.part_image_loader: class: AppBundle\Imagine\PartImageLoader arguments: ['@api.manager.rebrickable', '@oneup_flysystem.media_filesystem'] diff --git a/src/AppBundle/Controller/ModelController.php b/src/AppBundle/Controller/ModelController.php index 3cb4cea..015693d 100644 --- a/src/AppBundle/Controller/ModelController.php +++ b/src/AppBundle/Controller/ModelController.php @@ -9,7 +9,9 @@ use AppBundle\Form\Filter\Model\ModelFilterType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; /** * Part controller. @@ -59,11 +61,11 @@ class ModelController extends Controller */ public function detailAction($number) { - $em = $this->getDoctrine()->getManager(); - /** @var Model $model */ if ($model = $this->get('repository.ldraw.model')->findOneByNumber($number)) { try { + $subparts = $this->get('service.model')->getAllSubparts($model); + $rbParts = $model != null ? $this->get('repository.rebrickable.part')->findAllByModel($model) : null; $sets = $model != null ? $this->get('repository.rebrickable.set')->findAllByModel($model) : null; @@ -74,6 +76,7 @@ class ModelController extends Controller 'rbParts' => $rbParts, 'sets' => $sets, 'related' => $related, + 'subparts' => $subparts, ]); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); @@ -87,8 +90,21 @@ class ModelController extends Controller * @Route("/{number}/zip", name="model_zip") * @Method("GET") */ - public function zipAction(Model $model) + public function zipAction(Request $request, Model $model) { - $zip = $this->get('service.zip')->createFromModel($model); + $zip = $this->get('service.zip')->createFromModel($model, true); + + $response = new BinaryFileResponse($zip); + $response->headers->set('Content-Type', 'application/zip'); + + // Create the disposition of the file + $disposition = $response->headers->makeDisposition( + ResponseHeaderBag::DISPOSITION_ATTACHMENT, + "model_{$model->getNumber()}_{$model->getName()}.zip" + ); + + $response->headers->set('Content-Disposition', $disposition); + + return $response; } } diff --git a/src/AppBundle/Controller/SetController.php b/src/AppBundle/Controller/SetController.php index b581a47..98ef29c 100644 --- a/src/AppBundle/Controller/SetController.php +++ b/src/AppBundle/Controller/SetController.php @@ -3,14 +3,15 @@ namespace AppBundle\Controller; use AppBundle\Api\Exception\ApiException; -use AppBundle\Api\Exception\EmptyResponseException; use AppBundle\Entity\Rebrickable\Inventory_Set; use AppBundle\Entity\Rebrickable\Set; use AppBundle\Form\Filter\Set\SetFilterType; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Controller\Controller; +use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpFoundation\ResponseHeaderBag; /** * @Route("/sets") @@ -55,10 +56,12 @@ class SetController extends Controller public function detailAction(Request $request, Set $set) { $bricksetSet = null; - $colors = null; + $partCount = $this->get('repository.rebrickable.inventoryPart')->getPartCount($set->getNumber(), false); try { - $bricksetSet = $this->get('api.manager.brickset')->getSetByNumber($set->getNumber()); + if (!($bricksetSet = $this->get('api.manager.brickset')->getSetByNumber($set->getNumber()))) { + $this->addFlash('warning', "{$set->getNumber()} not found in Brickset database"); + } } catch (ApiException $e) { $this->addFlash('error', $e->getService()); } catch (\Exception $e) { @@ -68,6 +71,7 @@ class SetController extends Controller return $this->render('set/detail.html.twig', [ 'set' => $set, 'brset' => $bricksetSet, + 'partCount' => $partCount, ]); } @@ -113,7 +117,8 @@ class SetController extends Controller try { $models = $this->get('service.set')->getModels($set, false); $spareModels = $this->get('service.set')->getModels($set, true); - $missing = $this->get('repository.rebrickable.inventorypart')->findAllBySetNumber($set->getNumber(), false, false); + $missing = $this->get('service.set')->getParts($set, false, false); +// $missing = $this->get('repository.rebrickable.inventorypart')->findAllBySetNumber($set->getNumber(), false, false); $missingSpare = $this->get('repository.rebrickable.inventorypart')->findAllBySetNumber($set->getNumber(), true, false); } catch (\Exception $e) { $this->addFlash('error', $e->getMessage()); @@ -197,6 +202,22 @@ class SetController extends Controller public function zipAction(Request $request, Set $set) { $sorted = $request->query->get('sorted') == 1 ? true : false; - $this->get('service.zip')->createFromSet($set, $sorted); + + $sort = $sorted ? 'sorted' : 'unsorted'; + + $zip = $this->get('service.zip')->createFromSet($set, $sorted); + + $response = new BinaryFileResponse($zip); + $response->headers->set('Content-Type', 'application/zip'); + + // Create the disposition of the file + $disposition = $response->headers->makeDisposition( + ResponseHeaderBag::DISPOSITION_ATTACHMENT, + "set_{$set->getNumber()}_{$set->getName()}({$sort}).zip" + ); + + $response->headers->set('Content-Disposition', $disposition); + + return $response; } } diff --git a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php index 559fe09..d121c06 100644 --- a/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php +++ b/src/AppBundle/Repository/Rebrickable/Inventory_PartRepository.php @@ -26,7 +26,9 @@ class Inventory_PartRepository extends BaseRepository $queryBuilder = $this->createQueryBuilder('inventory_part') ->where('inventory_part.inventory = :inventory') - ->setParameter('inventory', $inventory); + ->setParameter('inventory', $inventory) + ->join(Part::class, 'part', JOIN::WITH, 'inventory_part.part = part') + ->andWhere('part.category != 17'); if ($spare !== null) { $queryBuilder @@ -35,8 +37,6 @@ class Inventory_PartRepository extends BaseRepository } if ($model !== null) { - $queryBuilder - ->join(Part::class, 'part', JOIN::WITH, 'inventory_part.part = part'); if ($model === true) { $queryBuilder->andWhere('part.model IS NOT NULL'); } else { @@ -59,4 +59,32 @@ class Inventory_PartRepository extends BaseRepository return $queryBuilder->getQuery()->getResult(); } + + public function getPartCount($number, $spare = null, $model = null) + { + $inventory = $this->getEntityManager()->getRepository(Inventory::class)->findNewestInventoryBySetNumber($number); + + $queryBuilder = $this->createQueryBuilder('inventory_part') + ->where('inventory_part.inventory = :inventory') + ->setParameter('inventory', $inventory) + ->join(Part::class, 'part', JOIN::WITH, 'inventory_part.part = part') + ->andWhere('part.category != 17') + ->select('SUM(inventory_part.quantity) as parts'); + + if ($spare !== null) { + $queryBuilder + ->andWhere('inventory_part.spare = :spare') + ->setParameter('spare', $spare); + } + + if ($model !== null) { + if ($model === true) { + $queryBuilder->andWhere('part.model IS NOT NULL'); + } else { + $queryBuilder->andWhere('part.model IS NULL'); + } + } + + return $queryBuilder->getQuery()->getSingleScalarResult(); + } } diff --git a/src/AppBundle/Repository/Rebrickable/ThemeRepository.php b/src/AppBundle/Repository/Rebrickable/ThemeRepository.php index fcdaccd..cddfdf1 100644 --- a/src/AppBundle/Repository/Rebrickable/ThemeRepository.php +++ b/src/AppBundle/Repository/Rebrickable/ThemeRepository.php @@ -19,4 +19,12 @@ class ThemeRepository extends BaseRepository return $queryBuilder->getQuery()->getResult(); } + + public function findAllMain() + { + $queryBuilder = $this->createQueryBuilder('theme') + ->where('theme.parent IS NULL'); + + return $queryBuilder->getQuery()->getResult(); + } } diff --git a/src/AppBundle/Service/ModelService.php b/src/AppBundle/Service/ModelService.php new file mode 100644 index 0000000..ecd6a75 --- /dev/null +++ b/src/AppBundle/Service/ModelService.php @@ -0,0 +1,35 @@ +getSubparts() as $subpart) { + $this->resursiveLoadModels($subpart->getSubpart(), $subpart->getCount()); + } + + return $this->models; + } + + private function resursiveLoadModels(Model $model, $quantity = 1) + { + if (($model->getSubparts()->count() !== 0)) { + foreach ($model->getSubparts() as $subpart) { + $this->resursiveLoadModels($subpart->getSubpart(), $subpart->getCount()); + } + } else { + $q = isset($this->models[$model->getNumber()]['quantity']) ? $this->models[$model->getNumber()]['quantity'] : 0; + + $this->models[$model->getNumber()] = [ + 'quantity' => $q + $quantity, + 'model' => $model, + ]; + } + } +} diff --git a/src/AppBundle/Service/SetService.php b/src/AppBundle/Service/SetService.php index 285e763..6d95efb 100644 --- a/src/AppBundle/Service/SetService.php +++ b/src/AppBundle/Service/SetService.php @@ -3,13 +3,13 @@ namespace AppBundle\Service; use AppBundle\Entity\LDraw\Model; - use AppBundle\Entity\Rebrickable\Inventory_Part; - use AppBundle\Entity\Rebrickable\Set; - use AppBundle\Repository\Rebrickable\Inventory_PartRepository; +use AppBundle\Entity\Rebrickable\Inventory_Part; +use AppBundle\Entity\Rebrickable\Set; +use AppBundle\Repository\Rebrickable\Inventory_PartRepository; - class SetService - { - /** @var Inventory_PartRepository */ +class SetService +{ + /** @var Inventory_PartRepository */ private $inventoryPartRepository; /** @@ -110,7 +110,6 @@ use AppBundle\Entity\LDraw\Model; return $models; } - /** * Get array models grouped by color. * [ @@ -163,4 +162,31 @@ use AppBundle\Entity\LDraw\Model; return $colors; } - } + + /* + * @param Set $set + * @param bool $spare If true - add only spare parts, false - add only regular parts, null - add all parts + * + * @return array + */ + public function getParts(Set $set, $spare = null, $model = false) + { + $parts = []; + + $inventoryParts = $this->inventoryPartRepository->findAllBySetNumber($set->getNumber(), $spare, $model); + + /** @var Inventory_Part $inventoryPart */ + foreach ($inventoryParts as $inventoryPart) { + if (isset($parts[$inventoryPart->getPart()->getNumber()])) { + $parts[$inventoryPart->getPart()->getNumber()]['quantity'] += $inventoryPart->getQuantity(); + } else { + $parts[$inventoryPart->getPart()->getNumber()] = [ + 'part' => $inventoryPart->getPart(), + 'quantity' => $inventoryPart->getQuantity(), + ]; + } + } + + return $parts; + } +} diff --git a/src/AppBundle/Service/ZipService.php b/src/AppBundle/Service/ZipService.php index bc427fe..6a8fc32 100644 --- a/src/AppBundle/Service/ZipService.php +++ b/src/AppBundle/Service/ZipService.php @@ -5,11 +5,10 @@ namespace AppBundle\Service; use AppBundle\Entity\LDraw\Model; use AppBundle\Entity\Rebrickable\Set; use League\Flysystem\Filesystem; -use ZipStream\ZipStream; class ZipService { - /** @var ZipStream */ + /** @var \ZipArchive */ private $archive; /** @var Filesystem */ @@ -18,26 +17,41 @@ class ZipService /** @var SetService */ private $setService; + /** @var ModelService */ + private $modelService; + + private $zipName; + + private $models; + /** * ZipService constructor. * * @param $mediaFilesystem * @param $setService */ - public function __construct($mediaFilesystem, $setService) + public function __construct($mediaFilesystem, $setService, $modelService) { $this->mediaFilesystem = $mediaFilesystem; $this->setService = $setService; + $this->modelService = $modelService; + } + + private function createZip($path) + { + $archive = new \ZipArchive(); + $archive->open($path, \ZipArchive::CREATE); + + return $archive; } public function createFromSet(Set $set, $sorted = false) { $sort = $sorted ? 'sorted' : 'unsorted'; + $this->zipName = "set_{$set->getNumber()}_{$set->getName()}({$sort})"; - $filename = "set_{$set->getNumber()}_{$set->getName()}({$sort}).zip"; - - // Initialize zip stream - $this->archive = new ZipStream($filename); + $zipPath = tempnam(sys_get_temp_dir(), 'printabrick'); + $this->archive = $this->createZip($zipPath); if ($sorted) { $this->addSetGroupedByColor($set); @@ -45,23 +59,35 @@ class ZipService $this->addSet($set); } - $this->archive->finish(); + $this->addLicense(); + $this->archive->close(); - return $this->archive; + return $zipPath; } public function createFromModel(Model $model, $subparts = false) { - $filename = "model_{$model->getNumber()}.zip"; + $this->zipName = "model_{$model->getNumber()}"; - // Initialize zip stream - $this->archive = new ZipStream($filename); + $zipPath = tempnam(sys_get_temp_dir(), 'printabrick'); + $this->archive = $this->createZip($zipPath); - $this->addModel($model); + $filename = "{$this->zipName}/{$model->getNumber()}.stl"; + $this->addModel($filename, $model); - $this->archive->finish(); + if ($subparts) { + foreach ($this->modelService->getAllSubparts($model) as $subpart) { + $submodel = $subpart['model']; + $filename = "{$this->zipName}/submodels/{$submodel->getNumber()}_({$subpart['quantity']}x).stl"; - return $this->archive; + $this->addModel($filename, $submodel); + } + } + + $this->addLicense(); + $this->archive->close(); + + return $zipPath; } /** @@ -81,9 +107,9 @@ class ZipService $model = $modelArray['model']; $quantity = $modelArray['quantity']; - $filename = "{$color->getName()}/{$model->getNumber()}_({$quantity}x).stl"; + $filename = "{$this->zipName}/{$color->getName()}/{$model->getNumber()}_({$quantity}x).stl"; - $this->archive->addFile($filename, $this->mediaFilesystem->read($model->getPath())); + $this->addModel($filename, $model); } } } @@ -99,26 +125,33 @@ class ZipService $models = $this->setService->getModels($set, $spare); foreach ($models as $number => $array) { + $model = $array['model']; $quantity = $array['quantity']; - $filename = $number."_({$quantity}x).stl"; + $filename = "{$this->zipName}/{$number}_({$quantity}x).stl"; + $this->models[$number] = $array['model']; - $this->archive->addFile($filename, $this->mediaFilesystem->read($array['model']->getPath())); + $this->addModel($filename, $model); } } - public function addModel(Model $model, $count = 1, $folder = '') + private function addModel($path, $model) { - $filename = $folder.$model->getNumber()."_({$count}x).stl"; - - $this->archive->addFile($filename, $this->mediaFilesystem->read($model->getPath())); - - foreach ($model->getSubparts() as $subpart) { - $this->addModel($subpart->getSubpart(), $subpart->getCount(), $folder.$model->getNumber().'_subparts/'); - } + $this->archive->addFromString($path, $this->mediaFilesystem->read($model->getPath())); + $this->models[$model->getNumber()] = $model; } - // TODO add licence file and information to zip file - public function addLicence() + private function addLicense() { + $text = sprintf('All stl files in this archive were converted by LDView from LDraw Library http://www.ldraw.org/'."\n\n"); + $text .= sprintf('Files are licensed under the Creative Commons - Attribution license.'."\n"); + $text .= sprintf('http://creativecommons.org/licenses/by/2.0/'."\n\n"); + + $text .= sprintf('Attribution:'."\n"."\n"); + + foreach ($this->models as $model) { + $text .= sprintf('%s - "%s" by %s'."\n", $model->getNumber(), $model->getName(), $model->getAuthor()->getName()); + } + + $this->archive->addFromString("{$this->zipName}/LICENSE.txt", $text); } }