Merge "filebackend: cleaned up the FileBackend constructor"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 10 Dec 2013 22:09:21 +0000 (22:09 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 10 Dec 2013 22:09:21 +0000 (22:09 +0000)
1  2 
includes/filebackend/FileBackend.php
includes/filebackend/FileBackendGroup.php
includes/filebackend/FileBackendMultiWrite.php
tests/phpunit/includes/media/SVGTest.php

   * For legacy reasons, the FSFileBackend class allows manually setting the paths of
   * containers to ones that do not respect the "wiki ID".
   *
 - * In key/value stores, the container is the only hierarchy (the rest is emulated).
 + * In key/value (object) stores, containers are the only hierarchy (the rest is emulated).
   * FS-based backends are somewhat more restrictive due to the existence of real
   * directory files; a regular file cannot have the same name as a directory. Other
   * backends with virtual directories may not have this limitation. Callers should
   * store files in such a way that no files and directories are under the same path.
   *
 + * In general, this class allows for callers to access storage through the same
 + * interface, without regard to the underlying storage system. However, calling code
 + * must follow certain patterns and be aware of certain things to ensure compatibility:
 + *   - a) Always call prepare() on the parent directory before trying to put a file there;
 + *        key/value stores only need the container to exist first, but filesystems need
 + *        all the parent directories to exist first (prepare() is aware of all this)
 + *   - b) Always call clean() on a directory when it might become empty to avoid empty
 + *        directory buildup on filesystems; key/value stores never have empty directories,
 + *        so doing this helps preserve consistency in both cases
 + *   - c) Likewise, do not rely on the existence of empty directories for anything;
 + *        calling directoryExists() on a path that prepare() was previously called on
 + *        will return false for key/value stores if there are no files under that path
 + *   - d) Never alter the resulting FSFile returned from getLocalReference(), as it could
 + *        either be a copy of the source file in /tmp or the original source file itself
 + *   - e) Use a file layout that results in never attempting to store files over directories
 + *        or directories over files; key/value stores allow this but filesystems do not
 + *   - f) Use ASCII file names (e.g. base32, IDs, hashes) to avoid Unicode issues in Windows
 + *   - g) Do not assume that move operations are atomic (difficult with key/value stores)
 + *   - h) Do not assume that file stat or read operations always have immediate consistency;
 + *        various methods have a "latest" flag that should always be used if up-to-date
 + *        information is required (this trades performance for correctness as needed)
 + *   - i) Do not assume that directory listings have immediate consistency
 + *
   * Methods of subclasses should avoid throwing exceptions at all costs.
   * As a corollary, external dependencies should be kept to a minimum.
   *
@@@ -83,7 -60,7 +83,7 @@@
   * @since 1.19
   */
  abstract class FileBackend {
 -      /** @var  string Unique backend name */
 +      /** @var string Unique backend name */
        protected $name;
  
        /** @var string Unique wiki name */
         *                   This name should not be changed after use (e.g. with journaling).
         *                   Note that the name is *not* used in actual container names.
         *   - wikiId      : Prefix to container names that is unique to this backend.
-        *                   If not provided, this defaults to the current wiki ID.
         *                   It should only consist of alphanumberic, '-', and '_' characters.
         *                   This ID is what avoids collisions if multiple logical backends
         *                   use the same storage system, so this should be set carefully.
-        *   - lockManager : Registered name of a file lock manager to use.
-        *   - fileJournal : File journal configuration; see FileJournal::factory().
-        *                   Journals simply log changes to files stored in the backend.
+        *   - lockManager : LockManager object to use for any file locking.
+        *                   If not provided, then no file locking will be enforced.
+        *   - fileJournal : FileJournal object to use for logging changes to files.
+        *                   If not provided, then change journaling will be disabled.
         *   - readOnly    : Write operations are disallowed if this is a non-empty string.
         *                   It should be an explanation for the backend being read-only.
         *   - parallelize : When to do file operations in parallel (when possible).
         *                   Allowed values are "implicit", "explicit" and "off".
         *   - concurrency : How many file operations can be done in parallel.
 -       * @throws MWException
 +       * @throws FileBackendException
         */
        public function __construct( array $config ) {
                $this->name = $config['name'];
                if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
 -                      throw new MWException( "Backend name `{$this->name}` is invalid." );
 +                      throw new FileBackendException( "Backend name `{$this->name}` is invalid." );
                }
-               $this->wikiId = isset( $config['wikiId'] )
-                       ? $config['wikiId']
-                       : wfWikiID(); // e.g. "my_wiki-en_"
-               $this->lockManager = ( $config['lockManager'] instanceof LockManager )
+               if ( !isset( $config['wikiId'] ) ) {
+                       $config['wikiId'] = wfWikiID();
+                       wfDeprecated( __METHOD__ . ' called without "wikiID".', '1.23' );
+               }
+               if ( isset( $config['lockManager'] ) && !is_object( $config['lockManager'] ) ) {
+                       $config['lockManager'] =
+                               LockManagerGroup::singleton( $config['wikiId'] )->get( $config['lockManager'] );
+                       wfDeprecated( __METHOD__ . ' called with non-object "lockManager".', '1.23' );
+               }
+               $this->wikiId = $config['wikiId']; // e.g. "my_wiki-en_"
+               $this->lockManager = isset( $config['lockManager'] )
                        ? $config['lockManager']
-                       : LockManagerGroup::singleton( $this->wikiId )->get( $config['lockManager'] );
+                       : new NullLockManager( array() );
                $this->fileJournal = isset( $config['fileJournal'] )
-                       ? ( ( $config['fileJournal'] instanceof FileJournal )
-                               ? $config['fileJournal']
-                               : FileJournal::factory( $config['fileJournal'], $this->name ) )
+                       ? $config['fileJournal']
                        : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $this->name );
                $this->readOnly = isset( $config['readOnly'] )
                        ? (string)$config['readOnly']
         *
         * @param string $type One of (attachment, inline)
         * @param string $filename Suggested file name (should not contain slashes)
 -       * @throws MWException
 +       * @throws FileBackendError
         * @return string
         * @since 1.20
         */
  
                $type = strtolower( $type );
                if ( !in_array( $type, array( 'inline', 'attachment' ) ) ) {
 -                      throw new MWException( "Invalid Content-Disposition type '$type'." );
 +                      throw new FileBackendError( "Invalid Content-Disposition type '$type'." );
                }
                $parts[] = $type;
  
  }
  
  /**
 + * Generic file backend exception for checked and unexpected (e.g. config) exceptions
 + *
 + * @ingroup FileBackend
 + * @since 1.23
 + */
 +class FileBackendException extends MWException {
 +}
 +
 +/**
 + * File backend exception for checked exceptions (e.g. I/O errors)
 + *
   * @ingroup FileBackend
   * @since 1.22
   */
 -class FileBackendError extends MWException {
 +class FileBackendError extends FileBackendException {
  }
@@@ -113,18 -113,18 +113,18 @@@ class FileBackendGroup 
         * Register an array of file backend configurations
         *
         * @param array $configs
 -       * @throws MWException
 +       * @throws FileBackendException
         */
        protected function register( array $configs ) {
                foreach ( $configs as $config ) {
                        if ( !isset( $config['name'] ) ) {
 -                              throw new MWException( "Cannot register a backend with no name." );
 +                              throw new FileBackendException( "Cannot register a backend with no name." );
                        }
                        $name = $config['name'];
                        if ( isset( $this->backends[$name] ) ) {
 -                              throw new MWException( "Backend with name `{$name}` already registered." );
 +                              throw new FileBackendException( "Backend with name `{$name}` already registered." );
                        } elseif ( !isset( $config['class'] ) ) {
 -                              throw new MWException( "Cannot register backend `{$name}` with no class." );
 +                              throw new FileBackendException( "Backend with name `{$name}` has no class." );
                        }
                        $class = $config['class'];
  
         *
         * @param string $name
         * @return FileBackend
 -       * @throws MWException
 +       * @throws FileBackendException
         */
        public function get( $name ) {
                if ( !isset( $this->backends[$name] ) ) {
 -                      throw new MWException( "No backend defined with the name `$name`." );
 +                      throw new FileBackendException( "No backend defined with the name `$name`." );
                }
                // Lazy-load the actual backend instance
                if ( !isset( $this->backends[$name]['instance'] ) ) {
                        $class = $this->backends[$name]['class'];
                        $config = $this->backends[$name]['config'];
+                       $config['wikiId'] = isset( $config['wikiId'] )
+                               ? $config['wikiId']
+                               : wfWikiID(); // e.g. "my_wiki-en_"
+                       $config['lockManager'] =
+                               LockManagerGroup::singleton( $config['wikiId'] )->get( $config['lockManager'] );
+                       $config['fileJournal'] = isset( $config['fileJournal'] )
+                               ? FileJournal::factory( $config['fileJournal'], $name )
+                               : FileJournal::factory( array( 'class' => 'NullFileJournal' ), $name );
                        $this->backends[$name]['instance'] = new $class( $config );
                }
  
         *
         * @param string $name
         * @return array
 -       * @throws MWException
 +       * @throws FileBackendException
         */
        public function config( $name ) {
                if ( !isset( $this->backends[$name] ) ) {
 -                      throw new MWException( "No backend defined with the name `$name`." );
 +                      throw new FileBackendException( "No backend defined with the name `$name`." );
                }
                $class = $this->backends[$name]['class'];
  
@@@ -92,7 -92,7 +92,7 @@@ class FileBackendMultiWrite extends Fil
         *   - noPushDirConts : (hack) Only apply directory functions to the master backend.
         *
         * @param array $config
 -       * @throws MWException
 +       * @throws FileBackendError
         */
        public function __construct( array $config ) {
                parent::__construct( $config );
                        }
                        $name = $config['name'];
                        if ( isset( $namesUsed[$name] ) ) { // don't break FileOp predicates
 -                              throw new MWException( "Two or more backends defined with the name $name." );
 +                              throw new FileBackendError( "Two or more backends defined with the name $name." );
                        }
                        $namesUsed[$name] = 1;
                        // Alter certain sub-backend settings for sanity
                        unset( $config['readOnly'] ); // use proxy backend setting
                        unset( $config['fileJournal'] ); // use proxy backend journal
+                       unset( $config['lockManager'] ); // lock under proxy backend
                        $config['wikiId'] = $this->wikiId; // use the proxy backend wiki ID
-                       $config['lockManager'] = 'nullLockManager'; // lock under proxy backend
                        if ( !empty( $config['isMultiMaster'] ) ) {
                                if ( $this->masterIndex >= 0 ) {
 -                                      throw new MWException( 'More than one master backend defined.' );
 +                                      throw new FileBackendError( 'More than one master backend defined.' );
                                }
                                $this->masterIndex = $index; // this is the "master"
                                $config['fileJournal'] = $this->fileJournal; // log under proxy backend
                        }
                        // Create sub-backend object
                        if ( !isset( $config['class'] ) ) {
 -                              throw new MWException( 'No class given for a backend config.' );
 +                              throw new FileBackendError( 'No class given for a backend config.' );
                        }
                        $class = $config['class'];
                        $this->backends[$index] = new $class( $config );
                }
                if ( $this->masterIndex < 0 ) { // need backends and must have a master
 -                      throw new MWException( 'No master backend defined.' );
 +                      throw new FileBackendError( 'No master backend defined.' );
                }
        }
  
@@@ -1,6 -1,5 +1,6 @@@
  <?php
 -class SVGTest extends MediaWikiTestCase {
 +
 +class SvgTest extends MediaWikiTestCase {
  
        protected function setUp() {
                parent::setUp();
@@@ -11,7 -10,7 +11,7 @@@
  
                $this->backend = new FSFileBackend( array(
                        'name' => 'localtesting',
-                       'lockManager' => 'nullLockManager',
+                       'wikiId' => wfWikiId(),
                        'containerPaths' => array( 'data' => $this->filePath )
                ) );
                $this->repo = new FSRepo( array(
@@@ -20,7 -19,7 +20,7 @@@
                        'backend' => $this->backend
                ) );
  
 -              $this->handler = new SVGHandler;
 +              $this->handler = new SvgHandler;
        }
  
        /**