MediaWiki  master
rebuildImages.php
Go to the documentation of this file.
1 <?php
33 require_once __DIR__ . '/Maintenance.php';
34 
37 
43 class ImageBuilder extends Maintenance {
47  protected $dbw;
48 
50  private $dryrun;
51 
53  private $repo;
54 
56  private $updated;
57 
59  private $processed;
60 
62  private $count;
63 
65  private $startTime;
66 
68  private $table;
69 
70  public function __construct() {
71  parent::__construct();
72 
74  // make sure to update old, but compatible img_metadata fields.
75  $wgUpdateCompatibleMetadata = true;
76 
77  $this->addDescription( 'Script to update image metadata records' );
78 
79  $this->addOption( 'missing', 'Check for files without associated database record' );
80  $this->addOption( 'dry-run', 'Only report, don\'t update the database' );
81  }
82 
83  public function execute() {
84  $this->dbw = $this->getDB( DB_MASTER );
85  $this->dryrun = $this->hasOption( 'dry-run' );
86  if ( $this->dryrun ) {
87  MediaWiki\MediaWikiServices::getInstance()->getReadOnlyMode()
88  ->setReason( 'Dry run mode, image upgrades are suppressed' );
89  }
90 
91  if ( $this->hasOption( 'missing' ) ) {
92  $this->crawlMissing();
93  } else {
94  $this->build();
95  }
96  }
97 
101  function getRepo() {
102  if ( $this->repo === null ) {
103  $this->repo = RepoGroup::singleton()->getLocalRepo();
104  }
105 
106  return $this->repo;
107  }
108 
109  function build() {
110  $this->buildImage();
111  $this->buildOldImage();
112  }
113 
118  function init( $count, $table ) {
119  $this->processed = 0;
120  $this->updated = 0;
121  $this->count = $count;
122  $this->startTime = microtime( true );
123  $this->table = $table;
124  }
125 
126  function progress( $updated ) {
127  $this->updated += $updated;
128  $this->processed++;
129  if ( $this->processed % 100 != 0 ) {
130  return;
131  }
132  $portion = $this->processed / $this->count;
133  $updateRate = $this->updated / $this->processed;
134 
135  $now = microtime( true );
136  $delta = $now - $this->startTime;
137  $estimatedTotalTime = $delta / $portion;
138  $eta = $this->startTime + $estimatedTotalTime;
139  $rate = $this->processed / $delta;
140 
141  $this->output( sprintf( "%s: %6.2f%% done on %s; ETA %s [%d/%d] %.2f/sec <%.2f%% updated>\n",
142  wfTimestamp( TS_DB, intval( $now ) ),
143  $portion * 100.0,
144  $this->table,
145  wfTimestamp( TS_DB, intval( $eta ) ),
146  $this->processed,
147  $this->count,
148  $rate,
149  $updateRate * 100.0 ) );
150  flush();
151  }
152 
153  function buildTable( $table, $key, $queryInfo, $callback ) {
154  $count = $this->dbw->selectField( $table, 'count(*)', '', __METHOD__ );
155  $this->init( $count, $table );
156  $this->output( "Processing $table...\n" );
157 
158  $result = $this->getDB( DB_REPLICA )->select(
159  $queryInfo['tables'], $queryInfo['fields'], [], __METHOD__, [], $queryInfo['joins']
160  );
161 
162  foreach ( $result as $row ) {
163  $update = call_user_func( $callback, $row, null );
164  if ( $update ) {
165  $this->progress( 1 );
166  } else {
167  $this->progress( 0 );
168  }
169  }
170  $this->output( "Finished $table... $this->updated of $this->processed rows updated\n" );
171  }
172 
173  function buildImage() {
174  $callback = [ $this, 'imageCallback' ];
175  $this->buildTable( 'image', 'img_name', LocalFile::getQueryInfo(), $callback );
176  }
177 
178  function imageCallback( $row, $copy ) {
179  // Create a File object from the row
180  // This will also upgrade it
181  $file = $this->getRepo()->newFileFromRow( $row );
182 
183  return $file->getUpgraded();
184  }
185 
186  function buildOldImage() {
187  $this->buildTable( 'oldimage', 'oi_archive_name', OldLocalFile::getQueryInfo(),
188  [ $this, 'oldimageCallback' ] );
189  }
190 
191  function oldimageCallback( $row, $copy ) {
192  // Create a File object from the row
193  // This will also upgrade it
194  if ( $row->oi_archive_name == '' ) {
195  $this->output( "Empty oi_archive_name for oi_name={$row->oi_name}\n" );
196 
197  return false;
198  }
199  $file = $this->getRepo()->newFileFromRow( $row );
200 
201  return $file->getUpgraded();
202  }
203 
204  function crawlMissing() {
205  $this->getRepo()->enumFiles( [ $this, 'checkMissingImage' ] );
206  }
207 
208  function checkMissingImage( $fullpath ) {
209  $filename = wfBaseName( $fullpath );
210  $row = $this->dbw->selectRow( 'image',
211  [ 'img_name' ],
212  [ 'img_name' => $filename ],
213  __METHOD__ );
214 
215  if ( !$row ) { // file not registered
216  $this->addMissingImage( $filename, $fullpath );
217  }
218  }
219 
220  function addMissingImage( $filename, $fullpath ) {
221  $timestamp = $this->dbw->timestamp( $this->getRepo()->getFileTimestamp( $fullpath ) );
222  $services = MediaWikiServices::getInstance();
223 
224  $altname = $services->getContentLanguage()->checkTitleEncoding( $filename );
225  if ( $altname != $filename ) {
226  if ( $this->dryrun ) {
227  $filename = $altname;
228  $this->output( "Estimating transcoding... $altname\n" );
229  } else {
230  // @fixme create renameFile()
231  // @phan-suppress-next-line PhanUndeclaredMethod See comment above...
232  $filename = $this->renameFile( $filename );
233  }
234  }
235 
236  if ( $filename == '' ) {
237  $this->output( "Empty filename for $fullpath\n" );
238 
239  return;
240  }
241  if ( !$this->dryrun ) {
242  $file = $services->getRepoGroup()->getLocalRepo()->newFile( $filename );
243  if ( !$file->recordUpload(
244  '',
245  '(recovered file, missing upload log entry)',
246  '',
247  '',
248  '',
249  false,
250  $timestamp
251  ) ) {
252  $this->output( "Error uploading file $fullpath\n" );
253 
254  return;
255  }
256  }
257  $this->output( $fullpath . "\n" );
258  }
259 }
260 
261 $maintClass = ImageBuilder::class;
262 require_once RUN_MAINTENANCE_IF_MAIN;
LocalRepo null $repo
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
oldimageCallback( $row, $copy)
wfBaseName( $path, $suffix='')
Return the final portion of a pathname.
const RUN_MAINTENANCE_IF_MAIN
Definition: Maintenance.php:39
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Definition: Maintenance.php:86
static getInstance()
Returns the global default instance of the top level service locator.
hasOption( $name)
Checks to see if a particular option exists.
const DB_MASTER
Definition: defines.php:26
addMissingImage( $filename, $fullpath)
$maintClass
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
init( $count, $table)
static singleton()
Definition: RepoGroup.php:60
addDescription( $text)
Set the description text.
IMaintainableDatabase $dbw
output( $out, $channel=null)
Throw some output to the user.
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new oldlocalfile object...
buildTable( $table, $key, $queryInfo, $callback)
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
const DB_REPLICA
Definition: defines.php:25
static getQueryInfo(array $options=[])
Return the tables, fields, and join conditions to be selected to create a new localfile object...
Definition: LocalFile.php:216
Maintenance script to update image metadata records.
getDB( $db, $groups=[], $dbDomain=false)
Returns a database to be used by current maintenance script.
checkMissingImage( $fullpath)
$wgUpdateCompatibleMetadata
If to automatically update the img_metadata field if the metadata field is outdated but compatible wi...
imageCallback( $row, $copy)
progress( $updated)