|
@@ -15,7 +15,6 @@ namespace Packagist\WebBundle\Command;
|
|
use Packagist\WebBundle\Entity\Package;
|
|
use Packagist\WebBundle\Entity\Package;
|
|
use Packagist\WebBundle\Model\DownloadManager;
|
|
use Packagist\WebBundle\Model\DownloadManager;
|
|
use Packagist\WebBundle\Model\FavoriteManager;
|
|
use Packagist\WebBundle\Model\FavoriteManager;
|
|
-use Solarium_Document_ReadWrite;
|
|
|
|
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
|
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
|
use Symfony\Component\Console\Input\InputArgument;
|
|
use Symfony\Component\Console\Input\InputArgument;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
use Symfony\Component\Console\Input\InputInterface;
|
|
@@ -24,9 +23,6 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|
use Symfony\Component\Filesystem\LockHandler;
|
|
use Symfony\Component\Filesystem\LockHandler;
|
|
use Doctrine\DBAL\Connection;
|
|
use Doctrine\DBAL\Connection;
|
|
|
|
|
|
-/**
|
|
|
|
- * @author Igor Wiedler <igor@wiedler.ch>
|
|
|
|
- */
|
|
|
|
class IndexPackagesCommand extends ContainerAwareCommand
|
|
class IndexPackagesCommand extends ContainerAwareCommand
|
|
{
|
|
{
|
|
/**
|
|
/**
|
|
@@ -35,13 +31,13 @@ class IndexPackagesCommand extends ContainerAwareCommand
|
|
protected function configure()
|
|
protected function configure()
|
|
{
|
|
{
|
|
$this
|
|
$this
|
|
- ->setName('packagist:index')
|
|
|
|
|
|
+ ->setName('packcagist:index')
|
|
->setDefinition(array(
|
|
->setDefinition(array(
|
|
new InputOption('force', null, InputOption::VALUE_NONE, 'Force a re-indexing of all packages'),
|
|
new InputOption('force', null, InputOption::VALUE_NONE, 'Force a re-indexing of all packages'),
|
|
new InputOption('all', null, InputOption::VALUE_NONE, 'Index all packages without clearing the index first'),
|
|
new InputOption('all', null, InputOption::VALUE_NONE, 'Index all packages without clearing the index first'),
|
|
new InputArgument('package', InputArgument::OPTIONAL, 'Package name to index'),
|
|
new InputArgument('package', InputArgument::OPTIONAL, 'Package name to index'),
|
|
))
|
|
))
|
|
- ->setDescription('Indexes packages in Solr')
|
|
|
|
|
|
+ ->setDescription('Indexes packages in Algolia')
|
|
;
|
|
;
|
|
}
|
|
}
|
|
|
|
|
|
@@ -54,6 +50,7 @@ class IndexPackagesCommand extends ContainerAwareCommand
|
|
$force = $input->getOption('force');
|
|
$force = $input->getOption('force');
|
|
$indexAll = $input->getOption('all');
|
|
$indexAll = $input->getOption('all');
|
|
$package = $input->getArgument('package');
|
|
$package = $input->getArgument('package');
|
|
|
|
+ $indexName = $this->getContainer()->getParameter('algolia.index_name');
|
|
|
|
|
|
$deployLock = $this->getContainer()->getParameter('kernel.cache_dir').'/deploy.globallock';
|
|
$deployLock = $this->getContainer()->getParameter('kernel.cache_dir').'/deploy.globallock';
|
|
if (file_exists($deployLock)) {
|
|
if (file_exists($deployLock)) {
|
|
@@ -64,12 +61,14 @@ class IndexPackagesCommand extends ContainerAwareCommand
|
|
}
|
|
}
|
|
|
|
|
|
$doctrine = $this->getContainer()->get('doctrine');
|
|
$doctrine = $this->getContainer()->get('doctrine');
|
|
- $solarium = $this->getContainer()->get('solarium.client');
|
|
|
|
|
|
+ $algolia = $this->getContainer()->get('packagist.algolia.client');
|
|
|
|
+ $index = $algolia->initIndex($indexName);
|
|
|
|
+
|
|
$redis = $this->getContainer()->get('snc_redis.default');
|
|
$redis = $this->getContainer()->get('snc_redis.default');
|
|
$downloadManager = $this->getContainer()->get('packagist.download_manager');
|
|
$downloadManager = $this->getContainer()->get('packagist.download_manager');
|
|
$favoriteManager = $this->getContainer()->get('packagist.favorite_manager');
|
|
$favoriteManager = $this->getContainer()->get('packagist.favorite_manager');
|
|
|
|
|
|
- $lock = new LockHandler('packagist_package_indexer');
|
|
|
|
|
|
+ $lock = new LockHandler('packagist_algolia_indexer');
|
|
|
|
|
|
// another dumper is still active
|
|
// another dumper is still active
|
|
if (!$lock->lock()) {
|
|
if (!$lock->lock()) {
|
|
@@ -99,11 +98,7 @@ class IndexPackagesCommand extends ContainerAwareCommand
|
|
$output->writeln('Deleting existing index');
|
|
$output->writeln('Deleting existing index');
|
|
}
|
|
}
|
|
|
|
|
|
- $update = $solarium->createUpdate();
|
|
|
|
- $update->addDeleteQuery('*:*');
|
|
|
|
- $update->addCommit();
|
|
|
|
-
|
|
|
|
- $solarium->update($update);
|
|
|
|
|
|
+ $index->clearIndex();
|
|
}
|
|
}
|
|
|
|
|
|
$total = count($ids);
|
|
$total = count($ids);
|
|
@@ -114,9 +109,9 @@ class IndexPackagesCommand extends ContainerAwareCommand
|
|
$indexTime = new \DateTime;
|
|
$indexTime = new \DateTime;
|
|
$idsSlice = array_splice($ids, 0, 50);
|
|
$idsSlice = array_splice($ids, 0, 50);
|
|
$packages = $doctrine->getRepository('PackagistWebBundle:Package')->findById($idsSlice);
|
|
$packages = $doctrine->getRepository('PackagistWebBundle:Package')->findById($idsSlice);
|
|
- $update = $solarium->createUpdate();
|
|
|
|
|
|
|
|
$idsToUpdate = [];
|
|
$idsToUpdate = [];
|
|
|
|
+ $records = [];
|
|
|
|
|
|
foreach ($packages as $package) {
|
|
foreach ($packages as $package) {
|
|
$current++;
|
|
$current++;
|
|
@@ -125,21 +120,9 @@ class IndexPackagesCommand extends ContainerAwareCommand
|
|
}
|
|
}
|
|
|
|
|
|
try {
|
|
try {
|
|
- $document = $update->createDocument();
|
|
|
|
- $tags = $doctrine->getManager()->getConnection()->fetchAll(
|
|
|
|
- 'SELECT t.name FROM package p
|
|
|
|
- JOIN package_version pv ON p.id = pv.package_id
|
|
|
|
- JOIN version_tag vt ON vt.version_id = pv.id
|
|
|
|
- JOIN tag t ON t.id = vt.tag_id
|
|
|
|
- WHERE p.id = :id
|
|
|
|
- GROUP BY t.id, t.name',
|
|
|
|
- ['id' => $package->getId()]
|
|
|
|
- );
|
|
|
|
- foreach ($tags as $idx => $tag) {
|
|
|
|
- $tags[$idx] = $tag['name'];
|
|
|
|
- }
|
|
|
|
- $this->updateDocumentFromPackage($document, $package, $tags, $redis, $downloadManager, $favoriteManager);
|
|
|
|
- $update->addDocument($document);
|
|
|
|
|
|
+ $tags = $this->getTags($doctrine, $package);
|
|
|
|
+
|
|
|
|
+ $records[] = $this->packageToSearchableArray($package, $tags, $redis, $downloadManager, $favoriteManager);
|
|
|
|
|
|
$idsToUpdate[] = $package->getId();
|
|
$idsToUpdate[] = $package->getId();
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
@@ -148,39 +131,14 @@ class IndexPackagesCommand extends ContainerAwareCommand
|
|
continue;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
|
|
- $providers = $doctrine->getManager()->getConnection()->fetchAll(
|
|
|
|
- 'SELECT lp.packageName
|
|
|
|
- FROM package p
|
|
|
|
- JOIN package_version pv ON p.id = pv.package_id
|
|
|
|
- JOIN link_provide lp ON lp.version_id = pv.id
|
|
|
|
- WHERE p.id = :id
|
|
|
|
- AND pv.development = true
|
|
|
|
- GROUP BY lp.packageName',
|
|
|
|
- ['id' => $package->getId()]
|
|
|
|
- );
|
|
|
|
|
|
+ $providers = $this->getProviders($doctrine, $package);
|
|
foreach ($providers as $provided) {
|
|
foreach ($providers as $provided) {
|
|
- $provided = $provided['packageName'];
|
|
|
|
- try {
|
|
|
|
- $document = $update->createDocument();
|
|
|
|
- $document->setField('id', $provided);
|
|
|
|
- $document->setField('name', $provided);
|
|
|
|
- $document->setField('package_name', '');
|
|
|
|
- $document->setField('description', '');
|
|
|
|
- $document->setField('type', 'virtual-package');
|
|
|
|
- $document->setField('trendiness', 100);
|
|
|
|
- $document->setField('repository', '');
|
|
|
|
- $document->setField('abandoned', 0);
|
|
|
|
- $document->setField('replacementPackage', '');
|
|
|
|
- $update->addDocument($document);
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
- $output->writeln('<error>'.get_class($e).': '.$e->getMessage().', skipping package '.$package->getName().':provide:'.$provided.'</error>');
|
|
|
|
- }
|
|
|
|
|
|
+ $records[] = $this->createSearchableProvider($provided['packageName']);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
try {
|
|
try {
|
|
- $update->addCommit();
|
|
|
|
- $solarium->update($update);
|
|
|
|
|
|
+ $index->addObjects($records);
|
|
} catch (\Exception $e) {
|
|
} catch (\Exception $e) {
|
|
$output->writeln('<error>'.get_class($e).': '.$e->getMessage().', occurred while processing packages: '.implode(',', $idsSlice).'</error>');
|
|
$output->writeln('<error>'.get_class($e).': '.$e->getMessage().', occurred while processing packages: '.implode(',', $idsSlice).'</error>');
|
|
continue;
|
|
continue;
|
|
@@ -190,62 +148,141 @@ class IndexPackagesCommand extends ContainerAwareCommand
|
|
unset($packages);
|
|
unset($packages);
|
|
|
|
|
|
if ($verbose) {
|
|
if ($verbose) {
|
|
- $output->writeln('Updating package index times');
|
|
|
|
|
|
+ $output->writeln('Updating package indexedAt column');
|
|
}
|
|
}
|
|
|
|
|
|
- $retries = 5;
|
|
|
|
- // retry loop in case of a lock timeout
|
|
|
|
- while ($retries--) {
|
|
|
|
- try {
|
|
|
|
- $doctrine->getManager()->getConnection()->executeQuery(
|
|
|
|
- 'UPDATE package SET indexedAt=:indexed WHERE id IN (:ids)',
|
|
|
|
- [
|
|
|
|
- 'ids' => $idsToUpdate,
|
|
|
|
- 'indexed' => $indexTime->format('Y-m-d H:i:s'),
|
|
|
|
- ],
|
|
|
|
- ['ids' => Connection::PARAM_INT_ARRAY]
|
|
|
|
- );
|
|
|
|
- } catch (\Exception $e) {
|
|
|
|
- if (!$retries) {
|
|
|
|
- throw $e;
|
|
|
|
- }
|
|
|
|
- sleep(2);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ $this->updateIndexedAt($idsToUpdate, $doctrine, $indexTime->format('Y-m-d H:i:s'));
|
|
}
|
|
}
|
|
|
|
|
|
$lock->release();
|
|
$lock->release();
|
|
}
|
|
}
|
|
|
|
|
|
- private function updateDocumentFromPackage(
|
|
|
|
- Solarium_Document_ReadWrite $document,
|
|
|
|
|
|
+ private function packageToSearchableArray(
|
|
Package $package,
|
|
Package $package,
|
|
array $tags,
|
|
array $tags,
|
|
$redis,
|
|
$redis,
|
|
DownloadManager $downloadManager,
|
|
DownloadManager $downloadManager,
|
|
FavoriteManager $favoriteManager
|
|
FavoriteManager $favoriteManager
|
|
) {
|
|
) {
|
|
- $document->setField('id', $package->getId());
|
|
|
|
- $document->setField('name', $package->getName());
|
|
|
|
- $document->setField('package_name', $package->getPackageName());
|
|
|
|
- $document->setField('description', preg_replace('{[\x00-\x1f]+}u', '', $package->getDescription()));
|
|
|
|
- $document->setField('type', $package->getType());
|
|
|
|
- $document->setField('trendiness', $redis->zscore('downloads:trending', $package->getId()));
|
|
|
|
- $document->setField('downloads', $downloadManager->getTotalDownloads($package));
|
|
|
|
- $document->setField('favers', $favoriteManager->getFaverCount($package));
|
|
|
|
- $document->setField('repository', $package->getRepository());
|
|
|
|
- $document->setField('language', $package->getLanguage());
|
|
|
|
|
|
+ $faversCount = $favoriteManager->getFaverCount($package);
|
|
|
|
+ $downloads = $downloadManager->getDownloads($package);
|
|
|
|
+ $downloadsLog = $downloads['monthly'] > 0 ? log($downloads['monthly'], 10) : 0;
|
|
|
|
+ $starsLog = $package->getGitHubStars() > 0 ? log($package->getGitHubStars(), 10) : 0;
|
|
|
|
+ $popularity = round($downloadsLog + $starsLog);
|
|
|
|
+ $trendiness = $redis->zscore('downloads:trending', $package->getId());
|
|
|
|
+
|
|
|
|
+ $record = [
|
|
|
|
+ 'id' => $package->getId(),
|
|
|
|
+ 'objectID' => $package->getName(),
|
|
|
|
+ 'name' => $package->getName(),
|
|
|
|
+ 'package_organisation' => $package->getVendor(),
|
|
|
|
+ 'package_name' => $package->getPackageName(),
|
|
|
|
+ 'description' => preg_replace('{[\x00-\x1f]+}u', '', strip_tags($package->getDescription())),
|
|
|
|
+ 'type' => $package->getType(),
|
|
|
|
+ 'repository' => $package->getRepository(),
|
|
|
|
+ 'language' => $package->getLanguage(),
|
|
|
|
+ # log10 of downloads over the last 7days
|
|
|
|
+ 'trendiness' => $trendiness > 0 ? log($trendiness, 10) : 0,
|
|
|
|
+ # log10 of downloads + gh stars
|
|
|
|
+ 'popularity' => $popularity,
|
|
|
|
+ 'meta' => [
|
|
|
|
+ 'downloads' => $downloads['total'],
|
|
|
|
+ 'downloads_formatted' => number_format($downloads['total'], 0, ',', ' '),
|
|
|
|
+ 'favers' => $faversCount,
|
|
|
|
+ 'favers_formatted' => number_format($faversCount, 0, ',', ' '),
|
|
|
|
+ ],
|
|
|
|
+ ];
|
|
|
|
+
|
|
if ($package->isAbandoned()) {
|
|
if ($package->isAbandoned()) {
|
|
- $document->setField('abandoned', 1);
|
|
|
|
- $document->setField('replacementPackage', $package->getReplacementPackage() ?: '');
|
|
|
|
|
|
+ $record['abandoned'] = 1;
|
|
|
|
+ $record['replacementPackage'] = $package->getReplacementPackage() ?: '';
|
|
} else {
|
|
} else {
|
|
- $document->setField('abandoned', 0);
|
|
|
|
- $document->setField('replacementPackage', '');
|
|
|
|
|
|
+ $record['abandoned'] = 0;
|
|
|
|
+ $record['replacementPackage'] = '';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ $record['tags'] = $tags;
|
|
|
|
+
|
|
|
|
+ return $record;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private function createSearchableProvider(string $provided)
|
|
|
|
+ {
|
|
|
|
+ $record = [
|
|
|
|
+ 'id' => $provided,
|
|
|
|
+ 'objectID' => $provided,
|
|
|
|
+ 'name' => $provided,
|
|
|
|
+ 'package_organisation' => preg_replace('{/.*$}', '', $provided),
|
|
|
|
+ 'package_name' => preg_replace('{^[^/]*/}', '', $provided),
|
|
|
|
+ 'description' => '',
|
|
|
|
+ 'type' => 'virtual-package',
|
|
|
|
+ 'repository' => '',
|
|
|
|
+ 'language' => '',
|
|
|
|
+ 'trendiness' => 100,
|
|
|
|
+ 'popularity' => 4,
|
|
|
|
+ 'abandoned' => 0,
|
|
|
|
+ 'replacementPackage' => '',
|
|
|
|
+ 'tags' => [],
|
|
|
|
+ ];
|
|
|
|
+
|
|
|
|
+ return $record;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private function getProviders($doctrine, Package $package)
|
|
|
|
+ {
|
|
|
|
+ return $doctrine->getManager()->getConnection()->fetchAll(
|
|
|
|
+ 'SELECT lp.packageName
|
|
|
|
+ FROM package p
|
|
|
|
+ JOIN package_version pv ON p.id = pv.package_id
|
|
|
|
+ JOIN link_provide lp ON lp.version_id = pv.id
|
|
|
|
+ WHERE p.id = :id
|
|
|
|
+ AND pv.development = true
|
|
|
|
+ GROUP BY lp.packageName',
|
|
|
|
+ ['id' => $package->getId()]
|
|
|
|
+ );
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private function getTags($doctrine, Package $package)
|
|
|
|
+ {
|
|
|
|
+ $tags = $doctrine->getManager()->getConnection()->fetchAll(
|
|
|
|
+ 'SELECT t.name FROM package p
|
|
|
|
+ JOIN package_version pv ON p.id = pv.package_id
|
|
|
|
+ JOIN version_tag vt ON vt.version_id = pv.id
|
|
|
|
+ JOIN tag t ON t.id = vt.tag_id
|
|
|
|
+ WHERE p.id = :id
|
|
|
|
+ GROUP BY t.id, t.name',
|
|
|
|
+ ['id' => $package->getId()]
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ foreach ($tags as $idx => $tag) {
|
|
|
|
+ $tags[$idx] = $tag['name'];
|
|
}
|
|
}
|
|
|
|
|
|
- $tags = array_map(function ($tag) {
|
|
|
|
|
|
+ return array_map(function ($tag) {
|
|
return mb_strtolower(preg_replace('{[\x00-\x1f]+}u', '', $tag), 'UTF-8');
|
|
return mb_strtolower(preg_replace('{[\x00-\x1f]+}u', '', $tag), 'UTF-8');
|
|
}, $tags);
|
|
}, $tags);
|
|
- $document->setField('tags', $tags);
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private function updateIndexedAt(array $idsToUpdate, $doctrine, string $time)
|
|
|
|
+ {
|
|
|
|
+ $retries = 5;
|
|
|
|
+ // retry loop in case of a lock timeout
|
|
|
|
+ while ($retries--) {
|
|
|
|
+ try {
|
|
|
|
+ $doctrine->getManager()->getConnection()->executeQuery(
|
|
|
|
+ 'UPDATE package SET indexedAt=:indexed WHERE id IN (:ids)',
|
|
|
|
+ [
|
|
|
|
+ 'ids' => $idsToUpdate,
|
|
|
|
+ 'indexed' => $time,
|
|
|
|
+ ],
|
|
|
|
+ ['ids' => Connection::PARAM_INT_ARRAY]
|
|
|
|
+ );
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
+ if (!$retries) {
|
|
|
|
+ throw $e;
|
|
|
|
+ }
|
|
|
|
+ sleep(2);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|