* 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.
*
* @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).
if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
throw new MWException( "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 )
+ $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']