Go to the documentation of this file.
24 require_once __DIR__ .
'/Maintenance.php';
34 parent::__construct();
35 $this->
addDescription(
'Sync one file backend with another using the journal' );
36 $this->
addOption(
'src',
'Name of backend to sync from',
true,
true );
37 $this->
addOption(
'dst',
'Name of destination backend to sync',
false,
true );
38 $this->
addOption(
'start',
'Starting journal ID',
false,
true );
39 $this->
addOption(
'end',
'Ending journal ID',
false,
true );
40 $this->
addOption(
'posdir',
'Directory to read/record journal positions',
false,
true );
41 $this->
addOption(
'posdump',
'Just dump current journal position into the position dir.' );
42 $this->
addOption(
'postime',
'For position dumps, get the ID at this time',
false,
true );
43 $this->
addOption(
'backoff',
'Stop at entries younger than this age (sec).',
false,
true );
44 $this->
addOption(
'verbose',
'Verbose mode',
false,
false,
'v' );
52 if ( $posDir !=
'' ) {
53 $posFile =
"$posDir/" . rawurlencode( $src->getDomainId() );
64 $id = (int)$src->getJournal()->getPositionAtTime( $this->
getOption(
'postime' ) );
65 $this->
output(
"Requested journal position is $id.\n" );
67 $id = (int)$src->getJournal()->getCurrentPosition();
68 $this->
output(
"Current journal position is $id.\n" );
70 if ( file_put_contents( $posFile, $id, LOCK_EX ) !==
false ) {
71 $this->
output(
"Saved journal position file.\n" );
73 $this->
output(
"Could not save journal position file.\n" );
88 if ( !$start && $posFile && is_dir( $posDir ) ) {
89 $start = is_file( $posFile )
90 ? (int)trim( file_get_contents( $posFile ) )
93 $startFromPosFile =
true;
95 $startFromPosFile =
false;
99 $time = time() - $this->
getOption(
'backoff', 0 );
100 $end = (int)$src->getJournal()->getPositionAtTime( $time );
105 $this->
output(
"Synchronizing backend '{$dst->getName()}' to '{$src->getName()}'...\n" );
106 $this->
output(
"Starting journal position is $start.\n" );
107 if ( is_finite( $end ) ) {
108 $this->
output(
"Ending journal position is $end.\n" );
112 $callback =
function ( $pos ) use ( $startFromPosFile, $posFile, $start ) {
113 if ( $startFromPosFile && $pos >= $start ) {
114 file_put_contents( $posFile, $pos, LOCK_EX );
119 $lastOKPos = $this->
syncBackends( $src, $dst, $start, $end, $callback );
122 if ( $startFromPosFile && $lastOKPos >= $start ) {
123 if ( file_put_contents( $posFile, $lastOKPos, LOCK_EX ) !==
false ) {
124 $this->
output(
"Updated journal position file.\n" );
126 $this->
output(
"Could not update journal position file.\n" );
130 if ( $lastOKPos ===
false ) {
132 $this->
output(
"No journal entries found.\n" );
134 $this->
output(
"No new journal entries found.\n" );
137 $this->
output(
"Stopped synchronization at journal position $lastOKPos.\n" );
162 if ( $start > $end ) {
163 $this->
fatalError(
"Error: given starting ID greater than ending ID." );
168 $limit = min( $this->
getBatchSize(), $end - $start + 1 );
169 $this->
output(
"Doing id $start to " . ( $start + $limit - 1 ) .
"...\n" );
171 $entries = $src->
getJournal()->getChangeEntries( $start, $limit, $next );
173 if ( $first && !count( $entries ) ) {
180 foreach ( $entries as $entry ) {
181 if ( $entry[
'op'] !==
'null' ) {
182 $pathsInBatch[$entry[
'path']] = 1;
184 $lastPosInBatch = $entry[
'id'];
189 $lastOKPos = max( $lastOKPos, $lastPosInBatch );
190 $callback( $lastOKPos );
197 $this->
output(
"End of journal entries.\n" );
199 }
while ( $start && $start <= $end );
214 if ( !count( $paths ) ) {
235 foreach ( $sPaths as $i => $sPath ) {
236 $dPath = $dPaths[$i];
237 $sExists = $src->
fileExists( [
'src' => $sPath,
'latest' => 1 ] );
238 if ( $sExists ===
true ) {
239 if ( $this->
filesAreSame( $src, $dst, $sPath, $dPath ) ) {
245 $this->
error(
"Unable to sync '$dPath': could not get local copy." );
250 $fsFiles[] = $fsFile;
253 'dir' => dirname( $dPath ),
'bypassReadOnly' => 1 ] ) );
257 $ops[] = [
'op' =>
'store',
258 'src' => $fsFile->getPath(),
'dst' => $dPath,
'overwrite' => 1 ];
259 } elseif ( $sExists ===
false ) {
260 $ops[] = [
'op' =>
'delete',
'src' => $dPath,
'ignoreMissingSource' => 1 ];
262 $this->
error(
"Unable to sync '$dPath': could not stat file." );
269 $t_start = microtime(
true );
275 $elapsed_ms = floor( ( microtime(
true ) - $t_start ) * 1000 );
277 $this->
output(
"Synchronized these file(s) [{$elapsed_ms}ms]:\n" .
278 implode(
"\n", $dPaths ) .
"\n" );
293 '!^mwstore://([^/]+)!',
const RUN_MAINTENANCE_IF_MAIN
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.
Base class for all file backend classes (including multi-write backends).
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
addDescription( $text)
Set the description text.
getName()
Get the unique backend name.
execute()
Do the actual work.
getScopedFileLocks(array $paths, $type, StatusValue $status, $timeout=0)
Lock the files at the given storage paths in the backend.
static escapeRegexReplacement( $string)
Escape a string to make it suitable for inclusion in a preg_replace() replacement parameter.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
Maintenance script that syncs one file backend to another based on the journal of later.
getFileSize(array $params)
Get the size (bytes) of a file at a storage path in the backend.
replaceNamePaths( $paths, FileBackend $backend)
Substitute the backend name of storage paths with that of a given one.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getFileSha1Base36(array $params)
Get a SHA-1 hash of the content of the file at a storage path in the backend.
filesAreSame(FileBackend $src, FileBackend $dst, $sPath, $dPath)
syncFileBatch(array $paths, FileBackend $src, FileBackend $dst)
Sync particular files of backend $src to the corresponding $dst backend files.
doQuickOperations(array $ops, array $opts=[])
Perform a set of independent file operations on some files.
__construct()
Default constructor.
static newGood( $value=null)
Factory function for good results.
fileExists(array $params)
Check if a file exists at a storage path in the backend.
prepare(array $params)
Prepare a storage directory for usage.
syncBackends(FileBackend $src, FileBackend $dst, $start, $end, Closure $callback)
Sync $dst backend to $src backend based on the $src logs given after $start.
getOption( $name, $default=null)
Get an option, or return the default.
getBatchSize()
Returns batch size.
error( $err, $die=0)
Throw an error to the user.
output( $out, $channel=null)
Throw some output to the user.
getJournal()
Get the file journal object for this backend.
getLocalReference(array $params)
Returns a file system file, identical in content to the file at a storage path.
hasOption( $name)
Checks to see if a particular option exists.
setBatchSize( $s=0)
Set the batch size.