the browser-facing portion of osu!
at master 5.1 kB view raw
1<?php 2 3// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0. 4// See the LICENCE file in the repository root for full licence text. 5 6namespace App\Console\Commands; 7 8use App\Libraries\Elasticsearch\Es; 9use App\Libraries\Elasticsearch\Indexing; 10use App\Libraries\Elasticsearch\Search; 11use App\Libraries\Elasticsearch\Sort; 12use App\Libraries\OsuWiki; 13use App\Libraries\Search\BasicSearch; 14use App\Libraries\Wiki\WikiSitemap; 15use App\Models\Wiki\Page; 16use Illuminate\Console\Command; 17 18class EsIndexWiki extends Command 19{ 20 /** 21 * The name and signature of the console command. 22 * 23 * @var string 24 */ 25 protected $signature = 'es:index-wiki {--create-only} {--inplace} {--cleanup}'; 26 27 /** 28 * The console command description. 29 * 30 * @var string 31 */ 32 protected $description = 'Re-indexes wiki pages'; 33 34 private $cleanup; 35 private $createOnly; 36 private $indexName; 37 private $indicesToRemove; 38 private $inplace; 39 40 public function handle() 41 { 42 $this->readOptions(); 43 44 $alias = Page::esIndexName(); 45 $oldIndices = Indexing::getOldIndices($alias); 46 47 if (!$this->inplace || empty($oldIndices)) { 48 $this->indexName = Page::esTimestampedIndexName(); 49 } else { 50 $this->indexName = $oldIndices[0]; 51 } 52 53 $this->indicesToRemove = array_filter($oldIndices, function ($index) { 54 // because removing the index we just wrote to would be silly. 55 return $this->indexName !== $index; 56 }); 57 58 $continue = $this->starterMessage(); 59 if (!$continue) { 60 $this->error('User aborted!'); 61 return; 62 } 63 64 if (!Es::getClient()->indices()->exists(['index' => [$this->indexName]])) { 65 $this->info("Creating '{$this->indexName}'..."); 66 Page::esCreateIndex($this->indexName); 67 } 68 69 $this->reindex(); 70 71 Indexing::updateAlias($alias, $this->indexName); 72 73 $this->updateSitemap(); 74 75 $this->finish(); 76 } 77 78 private function finish() 79 { 80 if (!$this->cleanup) { 81 return; 82 } 83 84 foreach ($this->indicesToRemove as $index) { 85 $this->warn("Removing '{$index}'..."); 86 Indexing::deleteIndex($index); 87 } 88 } 89 90 private function newBaseSearch(): Search 91 { 92 return (new BasicSearch($this->indexName)) 93 ->query(['match_all' => new \stdClass()]) 94 ->sort([new Sort('path.keyword', 'asc'), new Sort('locale.keyword', 'asc')]) 95 ->source(false); 96 } 97 98 private function readOptions() 99 { 100 $this->createOnly = $this->option('create-only'); 101 $this->inplace = $this->option('inplace'); 102 $this->cleanup = $this->option('cleanup'); 103 } 104 105 private function reindex() 106 { 107 if ($this->createOnly) { 108 return; 109 } 110 // for storing the paths as keys; the values don't matter in practise. 111 $paths = []; 112 113 $this->line('Fetching page list from Github...'); 114 OsuWiki::getPageList()->each(function ($path) use (&$paths) { 115 $path = str_replace_first('wiki/', '', $path); 116 $paths[$path] = false; 117 }); 118 119 $this->line(count($paths).' pages found'); 120 121 if ($this->inplace) { 122 $this->line('Fetching existing list...'); 123 $cursor = ['', '']; // number of params for initial cursor must match number of sorts used. 124 while ($cursor !== null) { 125 $search = $this->newBaseSearch()->searchAfter(array_values($cursor)); 126 $response = $search->response(); 127 128 foreach ($response as $hit) { 129 $paths[$hit['_id']] = true; 130 } 131 132 $cursor = $search->getSortCursor(); 133 } 134 } 135 136 $total = count($paths); 137 $this->line("Total: {$total} documents"); 138 $bar = $this->output->createProgressBar($total); 139 140 foreach ($paths as $path => $_inEs) { 141 $pagePath = Page::parsePagePath($path); 142 $page = new Page($pagePath['path'], $pagePath['locale']); 143 $page->sync(true, $this->indexName); 144 145 if (!$page->isVisible()) { 146 $this->warn("delete {$pagePath['locale']}: {$pagePath['path']}"); 147 $page->esDeleteDocument(['index' => $this->indexName]); 148 } 149 150 $bar->advance(); 151 } 152 153 $bar->finish(); 154 $this->line(''); 155 } 156 157 private function starterMessage() 158 { 159 if ($this->cleanup) { 160 $this->warn( 161 "The following indices will be deleted on completion!\n" 162 .implode("\n", $this->indicesToRemove) 163 ); 164 } 165 166 return $this->option('no-interaction') || $this->confirm("This index to {$this->indexName}, begin indexing?", true); 167 } 168 169 private function updateSitemap() 170 { 171 $this->line('Updating wiki sitemap...'); 172 WikiSitemap::expire(); 173 WikiSitemap::get(); 174 } 175}