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";
}
/**
}
$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 {
$this->error( "Unknown module name.\n\nMust be one of:\n" .
- wordwrap( implode( ', ', array_keys( $registry ) ), 80 ) .
+ wordwrap( implode( ', ', array_keys( $this->registry ) ), 80 ) .
'.'
);
return false;
}
}
- $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 );
} else {
if ( $this->action === 'make-sri' ) {
$this->output( "Integrity for {$src}\n\tintegrity: ${actualIntegrity}\n" );
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 );
+ }
+ }
}
/**