MediaWiki master
rebuildImages.php
Go to the documentation of this file.
1<?php
19// @codeCoverageIgnoreStart
20require_once __DIR__ . '/Maintenance.php';
21// @codeCoverageIgnoreEnd
22
31use Wikimedia\Timestamp\TimestampFormat as TS;
32
42 protected $dbw;
43
45 private $dryrun;
46
48 private $repo;
49
51 private $updated;
52
54 private $processed;
55
57 private $count;
58
60 private $startTime;
61
63 private $table;
64
65 public function __construct() {
66 parent::__construct();
67 $this->addDescription( 'Script to update image metadata records' );
68
69 $this->addOption( 'missing', 'Check for files without associated database record' );
70 $this->addOption( 'dry-run', 'Only report, don\'t update the database' );
71 }
72
73 public function execute() {
74 $this->dbw = $this->getPrimaryDB();
75 $this->dryrun = $this->hasOption( 'dry-run' );
76 if ( $this->dryrun ) {
77 $this->getServiceContainer()->getReadOnlyMode()
78 ->setReason( 'Dry run mode, image upgrades are suppressed' );
79 }
80
81 if ( $this->hasOption( 'missing' ) ) {
82 $this->crawlMissing();
83 } else {
84 $this->build();
85 }
86 }
87
91 private function getRepo() {
92 if ( $this->repo === null ) {
93 $this->repo = $this->getServiceContainer()->getRepoGroup()
94 ->newCustomLocalRepo( [
95 // make sure to update old, but compatible img_metadata fields.
96 'updateCompatibleMetadata' => true
97 ] );
98 }
99
100 return $this->repo;
101 }
102
103 private function build() {
104 $migrationStage = $this->getServiceContainer()->getMainConfig()->get(
105 MainConfigNames::FileSchemaMigrationStage
106 );
107
108 if ( $migrationStage & SCHEMA_COMPAT_READ_OLD ) {
109 $this->buildImage();
110 $this->buildOldImage();
111 } else {
112 $this->buildFile();
113 }
114 }
115
120 private function init( $count, $table ) {
121 $this->processed = 0;
122 $this->updated = 0;
123 $this->count = $count;
124 $this->startTime = microtime( true );
125 $this->table = $table;
126 }
127
128 private function progress( int $updated ) {
129 $this->updated += $updated;
130 $this->processed++;
131 if ( $this->processed % 100 != 0 ) {
132 return;
133 }
134 $portion = $this->processed / $this->count;
135 $updateRate = $this->updated / $this->processed;
136
137 $now = microtime( true );
138 $delta = $now - $this->startTime;
139 $estimatedTotalTime = $delta / $portion;
140 $eta = $this->startTime + $estimatedTotalTime;
141 $rate = $this->processed / $delta;
142
143 $this->output( sprintf( "%s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec <%.2f%% updated>\n",
144 wfTimestamp( TS::DB, intval( $now ) ),
145 $portion * 100.0,
146 $this->table,
147 wfTimestamp( TS::DB, intval( $eta ) ),
148 $this->processed,
149 $this->count,
150 $rate,
151 $updateRate * 100.0 ) );
152 flush();
153 }
154
155 private function buildTable( string $table, SelectQueryBuilder $queryBuilder, callable $callback ) {
156 $count = $this->dbw->newSelectQueryBuilder()
157 ->select( 'count(*)' )
158 ->from( $table )
159 ->caller( __METHOD__ )->fetchField();
160 $this->init( $count, $table );
161 $this->output( "Processing $table...\n" );
162
163 $result = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
164
165 foreach ( $result as $row ) {
166 $update = $callback( $row );
167 if ( $update ) {
168 $this->progress( 1 );
169 } else {
170 $this->progress( 0 );
171 }
172 }
173 $this->output( "Finished $table... $this->updated of $this->processed rows updated\n" );
174 }
175
176 private function buildImage() {
177 $this->buildTable(
178 'image',
179 FileSelectQueryBuilder::newForFile( $this->getReplicaDB() ),
180 $this->imageCallback( ... )
181 );
182 }
183
184 private function imageCallback( \stdClass $row ): bool {
185 // Create a File object from the row
186 // This will also upgrade it
187 $file = $this->getRepo()->newFileFromRow( $row );
188
189 return $file->getUpgraded();
190 }
191
192 private function buildOldImage() {
193 $this->buildTable(
194 'oldimage',
195 FileSelectQueryBuilder::newForOldFile( $this->getReplicaDB() ),
196 $this->oldimageCallback( ... )
197 );
198 }
199
200 private function oldimageCallback( \stdClass $row ): bool {
201 // Create a File object from the row
202 // This will also upgrade it
203 if ( $row->oi_archive_name == '' ) {
204 $this->output( "Empty oi_archive_name for oi_name={$row->oi_name}\n" );
205
206 return false;
207 }
208 $file = $this->getRepo()->newFileFromRow( $row );
209
210 return $file->getUpgraded();
211 }
212
213 private function buildFile() {
214 $this->buildTable(
215 'file',
216 FileSelectQueryBuilder::newForFile( $this->getReplicaDB() ),
217 $this->fileCallback( ... )
218 );
219 }
220
221 private function fileCallback( \stdClass $row ): bool {
222 // Create a File object from the row
223 // This will also upgrade it
224 $file = $this->getRepo()->newFile( $row->file_name );
225
226 return $file->getUpgraded();
227 }
228
229 private function crawlMissing() {
230 $this->getRepo()->enumFiles( $this->checkMissingImage( ... ) );
231 }
232
233 private function checkMissingImage( string $fullpath ) {
234 $filename = wfBaseName( $fullpath );
235
236 $row = FileSelectQueryBuilder::newForFile( $this->getReplicaDB() )
237 ->where( [ 'img_name' => $filename ] )
238 ->caller( __METHOD__ )->fetchRow();
239
240 if ( !$row ) {
241 // file not registered
242 $this->addMissingImage( $filename, $fullpath );
243 }
244 }
245
246 private function addMissingImage( string $filename, string $fullpath ) {
247 $timestamp = $this->dbw->timestamp( $this->getRepo()->getFileTimestamp( $fullpath ) );
248 $services = $this->getServiceContainer();
249
250 $altname = $services->getContentLanguage()->checkTitleEncoding( $filename );
251 if ( $altname != $filename ) {
252 if ( $this->dryrun ) {
253 $filename = $altname;
254 $this->output( "Estimating transcoding... $altname\n" );
255 } else {
256 // @fixme create renameFile()
257 // @phan-suppress-next-line PhanUndeclaredMethod See comment above...
258 $filename = $this->renameFile( $filename );
259 }
260 }
261
262 if ( $filename == '' ) {
263 $this->output( "Empty filename for $fullpath\n" );
264
265 return;
266 }
267 if ( !$this->dryrun ) {
268 $file = $services->getRepoGroup()->getLocalRepo()->newFile( $filename );
269 $pageText = SpecialUpload::getInitialPageText(
270 '(recovered file, missing upload log entry)'
271 );
272 $user = User::newSystemUser( User::MAINTENANCE_SCRIPT_USER, [ 'steal' => true ] );
273 $status = $file->recordUpload3(
274 '',
275 '(recovered file, missing upload log entry)',
276 $pageText,
277 $user,
278 false,
279 $timestamp
280 );
281 if ( !$status->isOK() ) {
282 $this->output( "Error uploading file $fullpath\n" );
283
284 return;
285 }
286 }
287 $this->output( $fullpath . "\n" );
288 }
289}
290
291// @codeCoverageIgnoreStart
292$maintClass = ImageBuilder::class;
293require_once RUN_MAINTENANCE_IF_MAIN;
294// @codeCoverageIgnoreEnd
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:294
wfBaseName( $path, $suffix='')
Return the final portion of a pathname.
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:68
Maintenance script to update image metadata records.
__construct()
Default constructor.
execute()
Do the actual work.
IDatabase $dbw
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
Definition LocalRepo.php:45
A class containing constants representing the names of configuration variables.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
output( $out, $channel=null)
Throw some output to the user.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
hasOption( $name)
Checks to see if a particular option was set.
getReplicaDB(string|false $virtualDomain=false)
getServiceContainer()
Returns the main service container.
getPrimaryDB(string|false $virtualDomain=false)
addDescription( $text)
Set the description text.
Form for uploading media files.
User class for the MediaWiki software.
Definition User.php:130
Build SELECT queries with a fluent interface.
caller( $fname)
Set the method name to be included in an SQL comment.
Interface to a relational database.
Definition IDatabase.php:31
$maintClass