29use Wikimedia\ObjectFactory;
42 protected $backends = [];
84 return MediaWikiServices::getInstance()->getFileBackendGroup();
93 MediaWikiServices::getInstance()->resetServiceForTesting(
'FileBackendGroup' );
111 MimeAnalyzer $mimeAnalyzer,
114 ObjectFactory $objectFactory
116 $this->options = $options;
117 $this->srvCache = $srvCache;
118 $this->wanCache = $wanCache;
119 $this->mimeAnalyzer = $mimeAnalyzer;
120 $this->lmgFactory = $lmgFactory;
121 $this->tmpFileFactory = $tmpFileFactory;
122 $this->objectFactory = $objectFactory;
125 $this->
register( $options->
get(
'FileBackends' ), $configuredReadOnlyMode->
getReason() );
129 $repos = array_merge(
130 $options->
get(
'ForeignFileRepos' ), [ $options->
get(
'LocalFileRepo' ) ] );
131 foreach ( $repos as $info ) {
132 $backendName = $info[
'backend'];
133 if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
136 $repoName = $info[
'name'];
138 $directory = $info[
'directory'];
139 $deletedDir = $info[
'deletedDir'] ??
false;
140 $thumbDir = $info[
'thumbDir'] ??
"{$directory}/thumb";
141 $transcodedDir = $info[
'transcodedDir'] ??
"{$directory}/transcoded";
144 'name' => $backendName,
145 'class' => FSFileBackend::class,
146 'lockManager' =>
'fsLockManager',
147 'containerPaths' => [
148 "{$repoName}-public" =>
"{$directory}",
149 "{$repoName}-thumb" => $thumbDir,
150 "{$repoName}-transcoded" => $transcodedDir,
151 "{$repoName}-deleted" => $deletedDir,
152 "{$repoName}-temp" =>
"{$directory}/temp"
154 'fileMode' => $info[
'fileMode'] ?? 0644,
155 'directoryMode' => $options->
get(
'DirectoryMode' ),
160 $this->
register( $autoBackends, $configuredReadOnlyMode->
getReason() );
170 protected function register( array $configs, $readOnlyReason = null ) {
171 foreach ( $configs as $config ) {
172 if ( !isset( $config[
'name'] ) ) {
173 throw new InvalidArgumentException(
"Cannot register a backend with no name." );
175 $name = $config[
'name'];
176 if ( isset( $this->backends[$name] ) ) {
177 throw new LogicException(
"Backend with name '$name' already registered." );
178 } elseif ( !isset( $config[
'class'] ) ) {
179 throw new InvalidArgumentException(
"Backend with name '$name' has no class." );
181 $class = $config[
'class'];
183 $config[
'domainId'] =
184 $config[
'domainId'] ?? $config[
'wikiId'] ?? $this->options->get(
'fallbackWikiId' );
185 $config[
'readOnly'] = $config[
'readOnly'] ?? $readOnlyReason;
187 unset( $config[
'class'] );
188 $this->backends[$name] = [
203 public function get( $name ) {
205 if ( !isset( $this->backends[$name][
'instance'] ) ) {
206 $config = $this->config( $name );
208 $class = $config[
'class'];
209 if ( $class === FileBackendMultiWrite::class ) {
211 foreach ( $config[
'backends'] as $index => $beConfig ) {
212 if ( isset( $beConfig[
'template'] ) ) {
215 $config[
'backends'][$index] += $this->config( $beConfig[
'template'] );
220 $this->backends[$name][
'instance'] =
new $class( $config );
223 return $this->backends[$name][
'instance'];
234 if ( !isset( $this->backends[$name] ) ) {
235 throw new InvalidArgumentException(
"No backend defined with the name '$name'." );
238 $config = $this->backends[$name][
'config'];
243 'mimeCallback' => [ $this,
'guessMimeInternal' ],
244 'obResetFunc' =>
'wfResetOutputBuffers',
245 'streamMimeFunc' => [ StreamFile::class,
'contentTypeFromPath' ],
246 'tmpFileFactory' => $this->tmpFileFactory,
247 'statusWrapper' => [ Status::class,
'wrap' ],
248 'wanCache' => $this->wanCache,
249 'srvCache' => $this->srvCache,
250 'logger' => LoggerFactory::getInstance(
'FileOperation' ),
251 'profiler' =>
static function ( $section ) {
252 return Profiler::instance()->scopedProfileIn( $section );
259 'class' => $this->backends[$name][
'class'],
261 $this->lmgFactory->getLockManagerGroup( $config[
'domainId'] )
262 ->get( $config[
'lockManager'] ),
263 'fileJournal' => isset( $config[
'fileJournal'] )
264 ? $this->objectFactory->createObject(
265 $config[
'fileJournal'] + [
'backend' => $name ],
266 [
'specIsArg' =>
true,
'assertClass' => FileJournal::class ] )
280 if ( $backend !==
null && isset( $this->backends[$backend] ) ) {
281 return $this->
get( $backend );
297 $type = $this->mimeAnalyzer->getMimeTypeFromExtensionOrNull(
$ext );
299 if ( !
$type && $fsPath ) {
300 $type = $this->mimeAnalyzer->guessMimeType( $fsPath,
false );
302 $tmpFile = $this->tmpFileFactory->newTempFSFile(
'mime_',
'' );
303 file_put_contents( $tmpFile->getPath(),
$content );
304 $type = $this->mimeAnalyzer->guessMimeType( $tmpFile->getPath(),
false );
306 return $type ?:
'unknown/unknown';
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Class representing a cache/ephemeral data store.
Class to handle file backend registration.
ObjectFactory $objectFactory
MimeAnalyzer $mimeAnalyzer
static destroySingleton()
config( $name)
Get the config array for a backend object with a given name.
TempFSFileFactory $tmpFileFactory
guessMimeInternal( $storagePath, $content, $fsPath)
__construct(ServiceOptions $options, ConfiguredReadOnlyMode $configuredReadOnlyMode, BagOStuff $srvCache, WANObjectCache $wanCache, MimeAnalyzer $mimeAnalyzer, LockManagerGroupFactory $lmgFactory, TempFSFileFactory $tmpFileFactory, ObjectFactory $objectFactory)
LockManagerGroupFactory $lmgFactory
const CONSTRUCTOR_OPTIONS
backendFromPath( $storagePath)
Get an appropriate backend object from a storage path.
array[] $backends
(name => ('class' => string, 'config' => array, 'instance' => object))
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
Simple version of FileJournal that does nothing.
Multi-datacenter aware caching interface.
if(!is_readable( $file)) $ext