IndexPackagesCommand.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. <?php
  2. /*
  3. * This file is part of Packagist.
  4. *
  5. * (c) Jordi Boggiano <j.boggiano@seld.be>
  6. * Nils Adermann <naderman@naderman.de>
  7. *
  8. * For the full copyright and license information, please view the LICENSE
  9. * file that was distributed with this source code.
  10. */
  11. namespace Packagist\WebBundle\Command;
  12. use Packagist\WebBundle\Entity\Package;
  13. use Packagist\WebBundle\Model\DownloadManager;
  14. use Packagist\WebBundle\Model\FavoriteManager;
  15. use Solarium_Document_ReadWrite;
  16. use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
  17. use Symfony\Component\Console\Input\InputArgument;
  18. use Symfony\Component\Console\Input\InputInterface;
  19. use Symfony\Component\Console\Input\InputOption;
  20. use Symfony\Component\Console\Output\OutputInterface;
  21. /**
  22. * @author Igor Wiedler <igor@wiedler.ch>
  23. */
  24. class IndexPackagesCommand extends ContainerAwareCommand
  25. {
  26. /**
  27. * {@inheritdoc}
  28. */
  29. protected function configure()
  30. {
  31. $this
  32. ->setName('packagist:index')
  33. ->setDefinition(array(
  34. new InputOption('force', null, InputOption::VALUE_NONE, 'Force a re-indexing of all packages'),
  35. new InputOption('all', null, InputOption::VALUE_NONE, 'Index all packages without clearing the index first'),
  36. new InputArgument('package', InputArgument::OPTIONAL, 'Package name to index'),
  37. ))
  38. ->setDescription('Indexes packages in Solr')
  39. ;
  40. }
  41. /**
  42. * {@inheritdoc}
  43. */
  44. protected function execute(InputInterface $input, OutputInterface $output)
  45. {
  46. $verbose = $input->getOption('verbose');
  47. $force = $input->getOption('force');
  48. $indexAll = $input->getOption('all');
  49. $package = $input->getArgument('package');
  50. $deployLock = $this->getContainer()->getParameter('kernel.cache_dir').'/deploy.globallock';
  51. if (file_exists($deployLock)) {
  52. if ($verbose) {
  53. $output->writeln('Aborting, '.$deployLock.' file present');
  54. }
  55. return;
  56. }
  57. $doctrine = $this->getContainer()->get('doctrine');
  58. $solarium = $this->getContainer()->get('solarium.client');
  59. $redis = $this->getContainer()->get('snc_redis.default');
  60. $downloadManager = $this->getContainer()->get('packagist.download_manager');
  61. $favoriteManager = $this->getContainer()->get('packagist.favorite_manager');
  62. $lock = $this->getContainer()->getParameter('kernel.cache_dir').'/composer-indexer.lock';
  63. $timeout = 600;
  64. // another dumper is still active
  65. if (file_exists($lock) && filemtime($lock) > time() - $timeout) {
  66. if ($verbose) {
  67. $output->writeln('Aborting, '.$lock.' file present');
  68. }
  69. return;
  70. }
  71. touch($lock);
  72. if ($package) {
  73. $packages = array(array('id' => $doctrine->getRepository('PackagistWebBundle:Package')->findOneByName($package)->getId()));
  74. } elseif ($force || $indexAll) {
  75. $packages = $doctrine->getManager()->getConnection()->fetchAll('SELECT id FROM package ORDER BY id ASC');
  76. $doctrine->getManager()->getConnection()->executeQuery('UPDATE package SET indexedAt = NULL');
  77. } else {
  78. $packages = $doctrine->getRepository('PackagistWebBundle:Package')->getStalePackagesForIndexing();
  79. }
  80. $ids = array();
  81. foreach ($packages as $row) {
  82. $ids[] = $row['id'];
  83. }
  84. // clear index before a full-update
  85. if ($force && !$package) {
  86. if ($verbose) {
  87. $output->writeln('Deleting existing index');
  88. }
  89. $update = $solarium->createUpdate();
  90. $update->addDeleteQuery('*:*');
  91. $update->addCommit();
  92. $solarium->update($update);
  93. }
  94. $total = count($ids);
  95. $current = 0;
  96. // update package index
  97. while ($ids) {
  98. $packages = $doctrine->getRepository('PackagistWebBundle:Package')->getPackagesWithVersions(array_splice($ids, 0, 50));
  99. $update = $solarium->createUpdate();
  100. foreach ($packages as $package) {
  101. $current++;
  102. if ($verbose) {
  103. $output->writeln('['.sprintf('%'.strlen($total).'d', $current).'/'.$total.'] Indexing '.$package->getName());
  104. }
  105. try {
  106. $document = $update->createDocument();
  107. $this->updateDocumentFromPackage($document, $package, $redis, $downloadManager, $favoriteManager);
  108. $update->addDocument($document);
  109. $package->setIndexedAt(new \DateTime);
  110. } catch (\Exception $e) {
  111. $output->writeln('<error>Exception: '.$e->getMessage().', skipping package '.$package->getName().'.</error>');
  112. }
  113. foreach ($package->getVersions() as $version) {
  114. // abort when a non-dev version shows up since dev ones are ordered first
  115. if (!$version->isDevelopment()) {
  116. break;
  117. }
  118. if (count($provide = $version->getProvide())) {
  119. foreach ($version->getProvide() as $provide) {
  120. try {
  121. $document = $update->createDocument();
  122. $document->setField('id', $provide->getPackageName());
  123. $document->setField('name', $provide->getPackageName());
  124. $document->setField('description', '');
  125. $document->setField('type', 'virtual-package');
  126. $document->setField('trendiness', 100);
  127. $document->setField('repository', '');
  128. $document->setField('abandoned', 0);
  129. $document->setField('replacementPackage', '');
  130. $update->addDocument($document);
  131. } catch (\Exception $e) {
  132. $output->writeln('<error>'.get_class($e).': '.$e->getMessage().', skipping package '.$package->getName().':provide:'.$provide->getPackageName().'</error>');
  133. }
  134. }
  135. }
  136. }
  137. }
  138. $doctrine->getManager()->flush();
  139. $doctrine->getManager()->clear();
  140. unset($packages);
  141. $update->addCommit();
  142. $solarium->update($update);
  143. }
  144. unlink($lock);
  145. }
  146. private function updateDocumentFromPackage(
  147. Solarium_Document_ReadWrite $document,
  148. Package $package,
  149. $redis,
  150. DownloadManager $downloadManager,
  151. FavoriteManager $favoriteManager
  152. ) {
  153. $document->setField('id', $package->getId());
  154. $document->setField('name', $package->getName());
  155. $document->setField('description', $package->getDescription());
  156. $document->setField('type', $package->getType());
  157. $document->setField('trendiness', $redis->zscore('downloads:trending', $package->getId()));
  158. $document->setField('downloads', $downloadManager->getTotalDownloads($package));
  159. $document->setField('favers', $favoriteManager->getFaverCount($package));
  160. $document->setField('repository', $package->getRepository());
  161. if ($package->isAbandoned()) {
  162. $document->setField('abandoned', 1);
  163. $document->setField('replacementPackage', $package->getReplacementPackage() ?: '');
  164. } else {
  165. $document->setField('abandoned', 0);
  166. $document->setField('replacementPackage', '');
  167. }
  168. $tags = array();
  169. foreach ($package->getVersions() as $version) {
  170. foreach ($version->getTags() as $tag) {
  171. $tags[mb_strtolower($tag->getName(), 'UTF-8')] = true;
  172. }
  173. }
  174. $document->setField('tags', array_keys($tags));
  175. }
  176. }