private $registryFile;
private $libDir;
private $tmpParentDir;
+ private $cacheDir;
private $infoPrinter;
private $errorPrinter;
private $verbosePrinter;
private $action;
+ private $registry;
/**
* @param string $registryFile Path to YAML file
// Use a temporary directory under the destination directory instead
// of wfTempDir() because PHP's rename() does not work across file
- // systems, as the user's /tmp and $IP may be on different filesystems.
- $this->tmpParentDir = "{$this->libDir}/.tmp";
+ // systems, and the user's /tmp and $IP may be on different filesystems.
+ $this->tmpParentDir = "{$this->libDir}/.foreign/tmp";
+
+ $cacheHome = getenv( 'XDG_CACHE_HOME' ) ? realpath( getenv( 'XDG_CACHE_HOME' ) ) : false;
+ $this->cacheDir = $cacheHome ? "$cacheHome/mw-foreign" : "{$this->libDir}/.foreign/cache";
}
/**
* @throws Exception
*/
public function run( $action, $module ) {
- if ( !in_array( $action, [ 'update', 'verify', 'make-sri' ] ) ) {
- throw new Exception( 'Invalid action parameter.' );
+ $actions = [ 'update', 'verify', 'make-sri' ];
+ if ( !in_array( $action, $actions ) ) {
+ $this->error( "Invalid action.\n\nMust be one of " . implode( ', ', $actions ) . '.' );
+ return false;
}
$this->action = $action;
- $registry = $this->parseBasicYaml( file_get_contents( $this->registryFile ) );
+ $this->registry = $this->parseBasicYaml( file_get_contents( $this->registryFile ) );
if ( $module === 'all' ) {
- $modules = $registry;
- } elseif ( isset( $registry[ $module ] ) ) {
- $modules = [ $module => $registry[ $module ] ];
+ $modules = $this->registry;
+ } elseif ( isset( $this->registry[ $module ] ) ) {
+ $modules = [ $module => $this->registry[ $module ] ];
} else {
- throw new Exception( 'Unknown module name.' );
+ $this->error( "Unknown module name.\n\nMust be one of:\n" .
+ wordwrap( implode( ', ', array_keys( $this->registry ) ), 80 ) .
+ '.'
+ );
+ return false;
}
foreach ( $modules as $moduleName => $info ) {
}
}
- $this->cleanUp();
$this->output( "\nDone!\n" );
+ $this->cleanUp();
if ( $this->hasErrors ) {
// The verify mode should check all modules/files and fail after, not during.
return false;
return true;
}
+ private function cacheKey( $src, $integrity ) {
+ $key = basename( $src ) . '_' . substr( $integrity, -12 );
+ $key = preg_replace( '/[.\/+?=_-]+/', '_', $key );
+ return rtrim( $key, '_' );
+ }
+
+ /** @return string|false */
+ private function cacheGet( $key ) {
+ return Wikimedia\quietCall( 'file_get_contents', "{$this->cacheDir}/$key.data" );
+ }
+
+ private function cacheSet( $key, $data ) {
+ wfMkdirParents( $this->cacheDir );
+ file_put_contents( "{$this->cacheDir}/$key.data", $data, LOCK_EX );
+ }
+
private function fetch( $src, $integrity ) {
+ $key = $this->cacheKey( $src, $integrity );
+ $data = $this->cacheGet( $key );
+ if ( $data ) {
+ return $data;
+ }
+
$req = MWHttpRequest::factory( $src, [ 'method' => 'GET', 'followRedirects' => false ] );
if ( !$req->execute()->isOK() ) {
throw new Exception( "Failed to download resource at {$src}" );
$actualIntegrity = $algo . '-' . base64_encode( hash( $algo, $data, true ) );
if ( $integrity === $actualIntegrity ) {
$this->verbose( "... passed integrity check for {$src}\n" );
+ $this->cacheSet( $key, $data );
+ } elseif ( $this->action === 'make-sri' ) {
+ $this->output( "Integrity for {$src}\n\tintegrity: ${actualIntegrity}\n" );
} else {
- if ( $this->action === 'make-sri' ) {
- $this->output( "Integrity for {$src}\n\tintegrity: ${actualIntegrity}\n" );
- } else {
- throw new Exception( "Integrity check failed for {$src}\n" .
- "\tExpected: {$integrity}\n" .
- "\tActual: {$actualIntegrity}"
- );
- }
+ throw new Exception( "Integrity check failed for {$src}\n" .
+ "\tExpected: {$integrity}\n" .
+ "\tActual: {$actualIntegrity}"
+ );
}
return $data;
}
$from,
RecursiveDirectoryIterator::SKIP_DOTS
) );
+ /** @var SplFileInfo $file */
foreach ( $rii as $file ) {
$remote = $file->getPathname();
$local = strtr( $remote, [ $from => $to ] );
private function cleanUp() {
wfRecursiveRemoveDir( $this->tmpParentDir );
+
+ // Prune the cache of files we don't recognise.
+ $knownKeys = [];
+ foreach ( $this->registry as $info ) {
+ if ( $info['type'] === 'file' || $info['type'] === 'tar' ) {
+ $knownKeys[] = $this->cacheKey( $info['src'], $info['integrity'] );
+ } elseif ( $info['type'] === 'multi-file' ) {
+ foreach ( $info['files'] as $file ) {
+ $knownKeys[] = $this->cacheKey( $file['src'], $file['integrity'] );
+ }
+ }
+ }
+ foreach ( glob( "{$this->cacheDir}/*" ) as $cacheFile ) {
+ if ( !in_array( basename( $cacheFile, '.data' ), $knownKeys ) ) {
+ unlink( $cacheFile );
+ }
+ }
}
/**