MediaWiki  master
FileBackend.php
Go to the documentation of this file.
1 <?php
32 use Psr\Log\LoggerAwareInterface;
33 use Psr\Log\LoggerInterface;
34 use Psr\Log\NullLogger;
35 use Wikimedia\ScopedCallback;
36 
99 abstract class FileBackend implements LoggerAwareInterface {
101  protected $name;
102 
104  protected $domainId;
105 
107  protected $readOnly;
108 
110  protected $parallelize;
111 
113  protected $concurrency;
114 
116  protected $tmpFileFactory;
117 
119  protected $lockManager;
121  protected $logger;
123  protected $profiler;
124 
126  protected $obResetFunc;
128  protected $streamMimeFunc;
130  protected $statusWrapper;
131 
133  public const ATTR_HEADERS = 1; // files can be tagged with standard HTTP headers
134  public const ATTR_METADATA = 2; // files can be stored with metadata key/values
135  public const ATTR_UNICODE_PATHS = 4; // files can have Unicode paths (not just ASCII)
136 
138  protected const STAT_ABSENT = false;
139 
141  public const STAT_ERROR = null;
143  public const LIST_ERROR = null;
145  public const TEMPURL_ERROR = null;
147  public const EXISTENCE_ERROR = null;
148 
150  public const TIMESTAMP_FAIL = false;
152  public const CONTENT_FAIL = false;
154  public const XATTRS_FAIL = false;
156  public const SIZE_FAIL = false;
158  public const SHA1_FAIL = false;
159 
193  public function __construct( array $config ) {
194  if ( !array_key_exists( 'name', $config ) ) {
195  throw new InvalidArgumentException( 'Backend name not specified.' );
196  }
197  $this->name = $config['name'];
198  $this->domainId = $config['domainId'] // e.g. "my_wiki-en_"
199  ?? $config['wikiId'] // b/c alias
200  ?? null;
201  if ( !is_string( $this->name ) || !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
202  throw new InvalidArgumentException( "Backend name '{$this->name}' is invalid." );
203  }
204  if ( !is_string( $this->domainId ) ) {
205  throw new InvalidArgumentException(
206  "Backend domain ID not provided for '{$this->name}'." );
207  }
208  $this->lockManager = $config['lockManager'] ?? new NullLockManager( [] );
209  $this->readOnly = isset( $config['readOnly'] )
210  ? (string)$config['readOnly']
211  : '';
212  $this->parallelize = isset( $config['parallelize'] )
213  ? (string)$config['parallelize']
214  : 'off';
215  $this->concurrency = isset( $config['concurrency'] )
216  ? (int)$config['concurrency']
217  : 50;
218  $this->obResetFunc = $config['obResetFunc'] ?? [ $this, 'resetOutputBuffer' ];
219  $this->streamMimeFunc = $config['streamMimeFunc'] ?? null;
220 
221  $this->profiler = $config['profiler'] ?? null;
222  if ( !is_callable( $this->profiler ) ) {
223  $this->profiler = null;
224  }
225  $this->logger = $config['logger'] ?? new NullLogger();
226  $this->statusWrapper = $config['statusWrapper'] ?? null;
227  // tmpDirectory gets precedence for backward compatibility
228  if ( isset( $config['tmpDirectory'] ) ) {
229  $this->tmpFileFactory = new TempFSFileFactory( $config['tmpDirectory'] );
230  } else {
231  $this->tmpFileFactory = $config['tmpFileFactory'] ?? new TempFSFileFactory();
232  }
233  }
234 
235  public function setLogger( LoggerInterface $logger ) {
236  $this->logger = $logger;
237  }
238 
247  final public function getName() {
248  return $this->name;
249  }
250 
257  final public function getDomainId() {
258  return $this->domainId;
259  }
260 
268  final public function getWikiId() {
269  return $this->getDomainId();
270  }
271 
277  final public function isReadOnly() {
278  return ( $this->readOnly != '' );
279  }
280 
286  final public function getReadOnlyReason() {
287  return ( $this->readOnly != '' ) ? $this->readOnly : false;
288  }
289 
297  public function getFeatures() {
299  }
300 
308  final public function hasFeatures( $bitfield ) {
309  return ( $this->getFeatures() & $bitfield ) === $bitfield;
310  }
311 
461  final public function doOperations( array $ops, array $opts = [] ) {
462  if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
463  return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
464  }
465  if ( $ops === [] ) {
466  return $this->newStatus(); // nothing to do
467  }
468 
469  $ops = $this->resolveFSFileObjects( $ops );
470  if ( empty( $opts['force'] ) ) {
471  unset( $opts['nonLocking'] );
472  }
473 
475  $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
476 
477  return $this->doOperationsInternal( $ops, $opts );
478  }
479 
486  abstract protected function doOperationsInternal( array $ops, array $opts );
487 
499  final public function doOperation( array $op, array $opts = [] ) {
500  return $this->doOperations( [ $op ], $opts );
501  }
502 
513  final public function create( array $params, array $opts = [] ) {
514  return $this->doOperation( [ 'op' => 'create' ] + $params, $opts );
515  }
516 
527  final public function store( array $params, array $opts = [] ) {
528  return $this->doOperation( [ 'op' => 'store' ] + $params, $opts );
529  }
530 
541  final public function copy( array $params, array $opts = [] ) {
542  return $this->doOperation( [ 'op' => 'copy' ] + $params, $opts );
543  }
544 
555  final public function move( array $params, array $opts = [] ) {
556  return $this->doOperation( [ 'op' => 'move' ] + $params, $opts );
557  }
558 
569  final public function delete( array $params, array $opts = [] ) {
570  return $this->doOperation( [ 'op' => 'delete' ] + $params, $opts );
571  }
572 
584  final public function describe( array $params, array $opts = [] ) {
585  return $this->doOperation( [ 'op' => 'describe' ] + $params, $opts );
586  }
587 
702  final public function doQuickOperations( array $ops, array $opts = [] ) {
703  if ( empty( $opts['bypassReadOnly'] ) && $this->isReadOnly() ) {
704  return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
705  }
706  if ( $ops === [] ) {
707  return $this->newStatus(); // nothing to do
708  }
709 
710  $ops = $this->resolveFSFileObjects( $ops );
711  foreach ( $ops as &$op ) {
712  $op['overwrite'] = true; // avoids RTTs in key/value stores
713  }
714 
716  $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
717 
718  return $this->doQuickOperationsInternal( $ops, $opts );
719  }
720 
728  abstract protected function doQuickOperationsInternal( array $ops, array $opts );
729 
741  final public function doQuickOperation( array $op, array $opts = [] ) {
742  return $this->doQuickOperations( [ $op ], $opts );
743  }
744 
756  final public function quickCreate( array $params, array $opts = [] ) {
757  return $this->doQuickOperation( [ 'op' => 'create' ] + $params, $opts );
758  }
759 
771  final public function quickStore( array $params, array $opts = [] ) {
772  return $this->doQuickOperation( [ 'op' => 'store' ] + $params, $opts );
773  }
774 
786  final public function quickCopy( array $params, array $opts = [] ) {
787  return $this->doQuickOperation( [ 'op' => 'copy' ] + $params, $opts );
788  }
789 
801  final public function quickMove( array $params, array $opts = [] ) {
802  return $this->doQuickOperation( [ 'op' => 'move' ] + $params, $opts );
803  }
804 
816  final public function quickDelete( array $params, array $opts = [] ) {
817  return $this->doQuickOperation( [ 'op' => 'delete' ] + $params, $opts );
818  }
819 
831  final public function quickDescribe( array $params, array $opts = [] ) {
832  return $this->doQuickOperation( [ 'op' => 'describe' ] + $params, $opts );
833  }
834 
847  abstract public function concatenate( array $params );
848 
867  final public function prepare( array $params ) {
868  if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
869  return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
870  }
872  $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
873  return $this->doPrepare( $params );
874  }
875 
881  abstract protected function doPrepare( array $params );
882 
899  final public function secure( array $params ) {
900  if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
901  return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
902  }
904  $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
905  return $this->doSecure( $params );
906  }
907 
913  abstract protected function doSecure( array $params );
914 
933  final public function publish( array $params ) {
934  if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
935  return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
936  }
938  $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
939  return $this->doPublish( $params );
940  }
941 
947  abstract protected function doPublish( array $params );
948 
960  final public function clean( array $params ) {
961  if ( empty( $params['bypassReadOnly'] ) && $this->isReadOnly() ) {
962  return $this->newStatus( 'backend-fail-readonly', $this->name, $this->readOnly );
963  }
965  $scope = ScopedCallback::newScopedIgnoreUserAbort(); // try to ignore client aborts
966  return $this->doClean( $params );
967  }
968 
974  abstract protected function doClean( array $params );
975 
992  abstract public function fileExists( array $params );
993 
1004  abstract public function getFileTimestamp( array $params );
1005 
1017  final public function getFileContents( array $params ) {
1018  $contents = $this->getFileContentsMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
1019 
1020  return $contents[$params['src']];
1021  }
1022 
1036  abstract public function getFileContentsMulti( array $params );
1037 
1058  abstract public function getFileXAttributes( array $params );
1059 
1070  abstract public function getFileSize( array $params );
1071 
1088  abstract public function getFileStat( array $params );
1089 
1100  abstract public function getFileSha1Base36( array $params );
1101 
1111  abstract public function getFileProps( array $params );
1112 
1132  abstract public function streamFile( array $params );
1133 
1152  final public function getLocalReference( array $params ) {
1153  $fsFiles = $this->getLocalReferenceMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
1154 
1155  return $fsFiles[$params['src']];
1156  }
1157 
1175  abstract public function getLocalReferenceMulti( array $params );
1176 
1189  final public function getLocalCopy( array $params ) {
1190  $tmpFiles = $this->getLocalCopyMulti( [ 'srcs' => [ $params['src'] ] ] + $params );
1191 
1192  return $tmpFiles[$params['src']];
1193  }
1194 
1210  abstract public function getLocalCopyMulti( array $params );
1211 
1230  abstract public function getFileHttpUrl( array $params );
1231 
1257  abstract public function directoryExists( array $params );
1258 
1281  abstract public function getDirectoryList( array $params );
1282 
1299  final public function getTopDirectoryList( array $params ) {
1300  return $this->getDirectoryList( [ 'topOnly' => true ] + $params );
1301  }
1302 
1324  abstract public function getFileList( array $params );
1325 
1342  final public function getTopFileList( array $params ) {
1343  return $this->getFileList( [ 'topOnly' => true ] + $params );
1344  }
1345 
1354  abstract public function preloadCache( array $paths );
1355 
1364  abstract public function clearCache( array $paths = null );
1365 
1380  abstract public function preloadFileStat( array $params );
1381 
1393  final public function lockFiles( array $paths, $type, $timeout = 0 ) {
1394  $paths = array_map( [ __CLASS__, 'normalizeStoragePath' ], $paths );
1395 
1396  return $this->wrapStatus( $this->lockManager->lock( $paths, $type, $timeout ) );
1397  }
1398 
1406  final public function unlockFiles( array $paths, $type ) {
1407  $paths = array_map( [ __CLASS__, 'normalizeStoragePath' ], $paths );
1408 
1409  return $this->wrapStatus( $this->lockManager->unlock( $paths, $type ) );
1410  }
1411 
1428  final public function getScopedFileLocks(
1429  array $paths, $type, StatusValue $status, $timeout = 0
1430  ) {
1431  if ( $type === 'mixed' ) {
1432  foreach ( $paths as &$typePaths ) {
1433  $typePaths = array_map( [ __CLASS__, 'normalizeStoragePath' ], $typePaths );
1434  }
1435  } else {
1436  $paths = array_map( [ __CLASS__, 'normalizeStoragePath' ], $paths );
1437  }
1438 
1439  return ScopedLock::factory( $this->lockManager, $paths, $type, $status, $timeout );
1440  }
1441 
1458  abstract public function getScopedLocksForOps( array $ops, StatusValue $status );
1459 
1467  final public function getRootStoragePath() {
1468  return "mwstore://{$this->name}";
1469  }
1470 
1478  final public function getContainerStoragePath( $container ) {
1479  return $this->getRootStoragePath() . "/{$container}";
1480  }
1481 
1491  protected function resolveFSFileObjects( array $ops ) {
1492  foreach ( $ops as &$op ) {
1493  $src = $op['src'] ?? null;
1494  if ( $src instanceof FSFile ) {
1495  $op['srcRef'] = $src;
1496  $op['src'] = $src->getPath();
1497  }
1498  }
1499  unset( $op );
1500 
1501  return $ops;
1502  }
1503 
1511  final public static function isStoragePath( $path ) {
1512  return ( strpos( $path ?? '', 'mwstore://' ) === 0 );
1513  }
1514 
1523  final public static function splitStoragePath( $storagePath ) {
1524  if ( self::isStoragePath( $storagePath ) ) {
1525  // Remove the "mwstore://" prefix and split the path
1526  $parts = explode( '/', substr( $storagePath, 10 ), 3 );
1527  if ( count( $parts ) >= 2 && $parts[0] != '' && $parts[1] != '' ) {
1528  if ( count( $parts ) == 3 ) {
1529  return $parts; // e.g. "backend/container/path"
1530  } else {
1531  return [ $parts[0], $parts[1], '' ]; // e.g. "backend/container"
1532  }
1533  }
1534  }
1535 
1536  return [ null, null, null ];
1537  }
1538 
1546  final public static function normalizeStoragePath( $storagePath ) {
1547  [ $backend, $container, $relPath ] = self::splitStoragePath( $storagePath );
1548  if ( $relPath !== null ) { // must be for this backend
1549  $relPath = self::normalizeContainerPath( $relPath );
1550  if ( $relPath !== null ) {
1551  return ( $relPath != '' )
1552  ? "mwstore://{$backend}/{$container}/{$relPath}"
1553  : "mwstore://{$backend}/{$container}";
1554  }
1555  }
1556 
1557  return null;
1558  }
1559 
1568  final public static function parentStoragePath( $storagePath ) {
1569  // XXX dirname() depends on platform and locale! If nothing enforces that the storage path
1570  // doesn't contain characters like '\', behavior can vary by platform. We should use
1571  // explode() instead.
1572  $storagePath = dirname( $storagePath );
1573  [ , , $rel ] = self::splitStoragePath( $storagePath );
1574 
1575  return ( $rel === null ) ? null : $storagePath;
1576  }
1577 
1585  final public static function extensionFromPath( $path, $case = 'lowercase' ) {
1586  // This will treat a string starting with . as not having an extension, but store paths have
1587  // to start with 'mwstore://', so "garbage in, garbage out".
1588  $i = strrpos( $path, '.' );
1589  $ext = $i ? substr( $path, $i + 1 ) : '';
1590 
1591  if ( $case === 'lowercase' ) {
1592  $ext = strtolower( $ext );
1593  } elseif ( $case === 'uppercase' ) {
1594  $ext = strtoupper( $ext );
1595  }
1596 
1597  return $ext;
1598  }
1599 
1607  final public static function isPathTraversalFree( $path ) {
1608  return ( self::normalizeContainerPath( $path ) !== null );
1609  }
1610 
1620  final public static function makeContentDisposition( $type, $filename = '' ) {
1621  $parts = [];
1622 
1623  $type = strtolower( $type );
1624  if ( !in_array( $type, [ 'inline', 'attachment' ] ) ) {
1625  throw new InvalidArgumentException( "Invalid Content-Disposition type '$type'." );
1626  }
1627  $parts[] = $type;
1628 
1629  if ( strlen( $filename ) ) {
1630  $parts[] = "filename*=UTF-8''" . rawurlencode( basename( $filename ) );
1631  }
1632 
1633  return implode( ';', $parts );
1634  }
1635 
1646  final protected static function normalizeContainerPath( $path ) {
1647  // Normalize directory separators
1648  $path = strtr( $path, '\\', '/' );
1649  // Collapse any consecutive directory separators
1650  $path = preg_replace( '![/]{2,}!', '/', $path );
1651  // Remove any leading directory separator
1652  $path = ltrim( $path, '/' );
1653  // Use the same traversal protection as Title::secureAndSplit()
1654  if ( strpos( $path, '.' ) !== false ) {
1655  if (
1656  $path === '.' ||
1657  $path === '..' ||
1658  strpos( $path, './' ) === 0 ||
1659  strpos( $path, '../' ) === 0 ||
1660  strpos( $path, '/./' ) !== false ||
1661  strpos( $path, '/../' ) !== false
1662  ) {
1663  return null;
1664  }
1665  }
1666 
1667  return $path;
1668  }
1669 
1678  final protected function newStatus( ...$args ) {
1679  if ( count( $args ) ) {
1680  $sv = StatusValue::newFatal( ...$args );
1681  } else {
1682  $sv = StatusValue::newGood();
1683  }
1684 
1685  return $this->wrapStatus( $sv );
1686  }
1687 
1692  final protected function wrapStatus( StatusValue $sv ) {
1693  return $this->statusWrapper ? call_user_func( $this->statusWrapper, $sv ) : $sv;
1694  }
1695 
1700  protected function scopedProfileSection( $section ) {
1701  return $this->profiler ? ( $this->profiler )( $section ) : null;
1702  }
1703 
1707  protected function resetOutputBuffer() {
1708  // XXX According to documentation, ob_get_status() always returns a non-empty array and this
1709  // condition will always be true
1710  while ( ob_get_status() ) {
1711  if ( !ob_end_clean() ) {
1712  // Could not remove output buffer handler; abort now
1713  // to avoid getting in some kind of infinite loop.
1714  break;
1715  }
1716  }
1717  }
1718 }
Class representing a non-directory file on the file system.
Definition: FSFile.php:32
Base class for all file backend classes (including multi-write backends).
Definition: FileBackend.php:99
getScopedLocksForOps(array $ops, StatusValue $status)
Get an array of scoped locks needed for a batch of file operations.
concatenate(array $params)
Concatenate a list of storage files into a single file system file.
static parentStoragePath( $storagePath)
Get the parent storage directory of a storage path.
doOperation(array $op, array $opts=[])
Same as doOperations() except it takes a single operation.
create(array $params, array $opts=[])
Performs a single create operation.
quickDescribe(array $params, array $opts=[])
Performs a single quick describe operation.
wrapStatus(StatusValue $sv)
static isStoragePath( $path)
Check if a given path is a "mwstore://" path.
hasFeatures( $bitfield)
Check if the backend medium supports a field of extra features.
getFileXAttributes(array $params)
Get metadata about a file at a storage path in the backend.
getLocalCopy(array $params)
Get a local copy on disk of the file at a storage path in the backend.
clean(array $params)
Delete a storage directory if it is empty.
quickCreate(array $params, array $opts=[])
Performs a single quick create operation.
string $name
Unique backend name.
move(array $params, array $opts=[])
Performs a single move operation.
getDirectoryList(array $params)
Get an iterator to list all directories under a storage directory.
getFileList(array $params)
Get an iterator to list all stored files under a storage directory.
callable null $profiler
lockFiles(array $paths, $type, $timeout=0)
Lock the files at the given storage paths in the backend.
preloadFileStat(array $params)
Preload file stat information (concurrently if possible) into in-process cache.
describe(array $params, array $opts=[])
Performs a single describe operation.
getDomainId()
Get the domain identifier used for this backend (possibly empty).
getFeatures()
Get the a bitfield of extra features supported by the backend medium.
streamFile(array $params)
Stream the content of the file at a storage path in the backend.
getFileSha1Base36(array $params)
Get a SHA-1 hash of the content of the file at a storage path in the backend.
getReadOnlyReason()
Get an explanatory message if this backend is read-only.
doClean(array $params)
publish(array $params)
Remove measures to block web access to a storage directory and the container it belongs to.
quickDelete(array $params, array $opts=[])
Performs a single quick delete operation.
const ATTR_UNICODE_PATHS
callable $obResetFunc
clearCache(array $paths=null)
Invalidate any in-process file stat and property cache.
getFileStat(array $params)
Get quick information about a file at a storage path in the backend.
fileExists(array $params)
Check if a file exists at a storage path in the backend.
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
getFileTimestamp(array $params)
Get the last-modified timestamp of the file at a storage path.
LoggerInterface $logger
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
static makeContentDisposition( $type, $filename='')
Build a Content-Disposition header value per RFC 6266.
static normalizeContainerPath( $path)
Validate and normalize a relative storage path.
string $readOnly
Read-only explanation message.
preloadCache(array $paths)
Preload persistent file stat cache and property cache into in-process cache.
string $domainId
Unique domain name.
getScopedFileLocks(array $paths, $type, StatusValue $status, $timeout=0)
Lock the files at the given storage paths in the backend.
store(array $params, array $opts=[])
Performs a single store operation.
isReadOnly()
Check if this backend is read-only.
getRootStoragePath()
Get the root storage path of this backend.
getTopDirectoryList(array $params)
Same as FileBackend::getDirectoryList() except only lists directories that are immediately under the ...
getFileProps(array $params)
Get the properties of the content of the file at a storage path in the backend.
getFileSize(array $params)
Get the size (bytes) of a file at a storage path in the backend.
getWikiId()
Alias to getDomainId()
prepare(array $params)
Prepare a storage directory for usage.
getFileContentsMulti(array $params)
Like getFileContents() except it takes an array of storage paths and returns an order preserved map o...
doQuickOperations(array $ops, array $opts=[])
Perform a set of independent file operations on some files.
unlockFiles(array $paths, $type)
Unlock the files at the given storage paths in the backend.
getContainerStoragePath( $container)
Get the storage path for the given container for this backend.
scopedProfileSection( $section)
const ATTR_METADATA
doOperations(array $ops, array $opts=[])
This is the main entry point into the backend for write operations.
newStatus(... $args)
Yields the result of the status wrapper callback on either:
static normalizeStoragePath( $storagePath)
Normalize a storage path by cleaning up directory separators.
directoryExists(array $params)
Check if a directory exists at a given storage path.
callable $streamMimeFunc
resolveFSFileObjects(array $ops)
Convert FSFile 'src' paths to string paths (with an 'srcRef' field set to the FSFile)
quickMove(array $params, array $opts=[])
Performs a single quick move operation.
string $parallelize
When to do operations in parallel.
doPublish(array $params)
LockManager $lockManager
getLocalCopyMulti(array $params)
Like getLocalCopy() except it takes an array of storage paths and yields an order preserved-map of st...
int $concurrency
How many operations can be done in parallel.
getTopFileList(array $params)
Same as FileBackend::getFileList() except only lists files that are immediately under the given direc...
const ATTR_HEADERS
Bitfield flags for supported features.
doPrepare(array $params)
__construct(array $config)
Create a new backend instance from configuration.
quickCopy(array $params, array $opts=[])
Performs a single quick copy operation.
static isPathTraversalFree( $path)
Check if a relative path has no directory traversals.
quickStore(array $params, array $opts=[])
Performs a single quick store operation.
doQuickOperation(array $op, array $opts=[])
Same as doQuickOperations() except it takes a single operation.
callable $statusWrapper
doSecure(array $params)
setLogger(LoggerInterface $logger)
doQuickOperationsInternal(array $ops, array $opts)
getLocalReferenceMulti(array $params)
Like getLocalReference() except it takes an array of storage paths and yields an order-preserved map ...
copy(array $params, array $opts=[])
Performs a single copy operation.
getFileContents(array $params)
Get the contents of a file at a storage path in the backend.
TempFSFileFactory $tmpFileFactory
getLocalReference(array $params)
Returns a file system file, identical in content to the file at a storage path.
getName()
Get the unique backend name.
getFileHttpUrl(array $params)
Return an HTTP URL to a given file that requires no authentication to use.
doOperationsInternal(array $ops, array $opts)
secure(array $params)
Take measures to block web access to a storage directory and the container it belongs to.
Simple lock management based on in-process reference counting.
static factory(LockManager $manager, array $paths, $type, StatusValue $status, $timeout=0)
Get a ScopedLock object representing a lock on resource paths.
Definition: ScopedLock.php:65
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:46
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:73
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:85
if(!is_readable( $file)) $ext
Definition: router.php:48