summaryrefslogtreecommitdiff
path: root/includes/filerepo
diff options
context:
space:
mode:
Diffstat (limited to 'includes/filerepo')
-rw-r--r--includes/filerepo/ArchivedFile.php68
-rw-r--r--includes/filerepo/FSRepo.php103
-rw-r--r--includes/filerepo/File.php76
-rw-r--r--includes/filerepo/FileCache.php156
-rw-r--r--includes/filerepo/FileRepo.php175
-rw-r--r--includes/filerepo/ForeignAPIFile.php11
-rw-r--r--includes/filerepo/ForeignAPIRepo.php114
-rw-r--r--includes/filerepo/ForeignDBFile.php10
-rw-r--r--includes/filerepo/ForeignDBRepo.php15
-rw-r--r--includes/filerepo/ForeignDBViaLBRepo.php15
-rw-r--r--includes/filerepo/Image.php4
-rw-r--r--includes/filerepo/LocalFile.php224
-rw-r--r--includes/filerepo/LocalRepo.php97
-rw-r--r--includes/filerepo/NullRepo.php8
-rw-r--r--includes/filerepo/OldLocalFile.php24
-rw-r--r--includes/filerepo/RepoGroup.php124
16 files changed, 778 insertions, 446 deletions
diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/ArchivedFile.php
index 68c93b8f..ffc06303 100644
--- a/includes/filerepo/ArchivedFile.php
+++ b/includes/filerepo/ArchivedFile.php
@@ -33,7 +33,7 @@ class ArchivedFile
$this->id = -1;
$this->title = false;
$this->name = false;
- $this->group = '';
+ $this->group = 'deleted'; // needed for direct use of constructor
$this->key = '';
$this->size = 0;
$this->bits = 0;
@@ -45,47 +45,48 @@ class ArchivedFile
$this->description = '';
$this->user = 0;
$this->user_text = '';
- $this->timestamp = NULL;
+ $this->timestamp = null;
$this->deleted = 0;
$this->dataLoaded = false;
-
+ $this->exists = false;
+
if( is_object($title) ) {
$this->title = $title;
$this->name = $title->getDBkey();
}
-
+
if ($id)
$this->id = $id;
-
+
if ($key)
$this->key = $key;
-
+
if (!$id && !$key && !is_object($title))
throw new MWException( "No specifications provided to ArchivedFile constructor." );
}
/**
* Loads a file object from the filearchive table
- * @return ResultWrapper
+ * @return true on success or null
*/
public function load() {
if ( $this->dataLoaded ) {
return true;
}
$conds = array();
-
+
if( $this->id > 0 )
$conds['fa_id'] = $this->id;
if( $this->key ) {
- $conds['fa_storage_group'] = $this->group;
+ $conds['fa_storage_group'] = $this->group;
$conds['fa_storage_key'] = $this->key;
}
if( $this->title )
$conds['fa_name'] = $this->title->getDBkey();
-
+
if( !count($conds))
throw new MWException( "No specific information for retrieving archived file" );
-
+
if( !$this->title || $this->title->getNamespace() == NS_FILE ) {
$dbr = wfGetDB( DB_SLAVE );
$res = $dbr->select( 'filearchive',
@@ -142,13 +143,14 @@ class ArchivedFile
return;
}
$this->dataLoaded = true;
+ $this->exists = true;
return true;
}
/**
* Loads a file object from the filearchive table
- * @return ResultWrapper
+ * @return ArchivedFile
*/
public static function newFromRow( $row ) {
$file = new ArchivedFile( Title::makeTitle( NS_FILE, $row->fa_name ) );
@@ -176,7 +178,6 @@ class ArchivedFile
/**
* Return the associated title object
- * @public
*/
public function getTitle() {
return $this->title;
@@ -194,6 +195,11 @@ class ArchivedFile
return $this->id;
}
+ public function exists() {
+ $this->load();
+ return $this->exists;
+ }
+
/**
* Return the FileStore key
*/
@@ -203,6 +209,13 @@ class ArchivedFile
}
/**
+ * Return the FileStore key (overriding base File class)
+ */
+ public function getStorageKey() {
+ return $this->getKey();
+ }
+
+ /**
* Return the FileStore storage group
*/
public function getGroup() {
@@ -235,7 +248,6 @@ class ArchivedFile
/**
* Return the size of the image file, in bytes
- * @public
*/
public function getSize() {
$this->load();
@@ -244,7 +256,6 @@ class ArchivedFile
/**
* Return the bits of the image file, in bytes
- * @public
*/
public function getBits() {
$this->load();
@@ -337,30 +348,33 @@ class ArchivedFile
}
/**
- * int $field one of DELETED_* bitfield constants
+ * Returns the deletion bitfield
+ * @return int
+ */
+ public function getVisibility() {
+ $this->load();
+ return $this->deleted;
+ }
+
+ /**
* for file or revision rows
+ *
+ * @param $field Integer: one of DELETED_* bitfield constants
* @return bool
*/
public function isDeleted( $field ) {
+ $this->load();
return ($this->deleted & $field) == $field;
}
/**
* Determine if the current user is allowed to view a particular
* field of this FileStore image file, if it's marked as deleted.
- * @param int $field
+ * @param $field Integer
* @return bool
*/
public function userCan( $field ) {
- if( ($this->deleted & $field) == $field ) {
- global $wgUser;
- $permission = ( $this->deleted & File::DELETED_RESTRICTED ) == File::DELETED_RESTRICTED
- ? 'suppressrevision'
- : 'deleterevision';
- wfDebug( "Checking for $permission due to $field match on $this->deleted\n" );
- return $wgUser->isAllowed( $permission );
- } else {
- return true;
- }
+ $this->load();
+ return Revision::userCanBitfield( $this->deleted, $field );
}
}
diff --git a/includes/filerepo/FSRepo.php b/includes/filerepo/FSRepo.php
index d561e61b..0dd9d0f7 100644
--- a/includes/filerepo/FSRepo.php
+++ b/includes/filerepo/FSRepo.php
@@ -6,7 +6,7 @@
* @ingroup FileRepo
*/
class FSRepo extends FileRepo {
- var $directory, $deletedDir, $url, $deletedHashLevels;
+ var $directory, $deletedDir, $deletedHashLevels, $fileMode;
var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
var $oldFileFactory = false;
var $pathDisclosureProtection = 'simple';
@@ -23,6 +23,17 @@ class FSRepo extends FileRepo {
$this->deletedHashLevels = isset( $info['deletedHashLevels'] ) ?
$info['deletedHashLevels'] : $this->hashLevels;
$this->deletedDir = isset( $info['deletedDir'] ) ? $info['deletedDir'] : false;
+ $this->fileMode = isset( $info['fileMode'] ) ? $info['fileMode'] : 0644;
+ if ( isset( $info['thumbDir'] ) ) {
+ $this->thumbDir = $info['thumbDir'];
+ } else {
+ $this->thumbDir = "{$this->directory}/thumb";
+ }
+ if ( isset( $info['thumbUrl'] ) ) {
+ $this->thumbUrl = $info['thumbUrl'];
+ } else {
+ $this->thumbUrl = "{$this->url}/thumb";
+ }
}
/**
@@ -57,13 +68,15 @@ class FSRepo extends FileRepo {
return "{$this->directory}/temp";
case 'deleted':
return $this->deletedDir;
+ case 'thumb':
+ return $this->thumbDir;
default:
return false;
}
}
/**
- * Get the URL corresponding to one of the three basic zones
+ * @see FileRepo::getZoneUrl()
*/
function getZoneUrl( $zone ) {
switch ( $zone ) {
@@ -72,9 +85,11 @@ class FSRepo extends FileRepo {
case 'temp':
return "{$this->url}/temp";
case 'deleted':
- return false; // no public URL
+ return parent::getZoneUrl( $zone ); // no public URL
+ case 'thumb':
+ return $this->thumbUrl;
default:
- return false;
+ return parent::getZoneUrl( $zone );
}
}
@@ -203,7 +218,7 @@ class FSRepo extends FileRepo {
}
}
if ( $good ) {
- chmod( $dstPath, 0644 );
+ $this->chmod( $dstPath );
$status->successCount++;
} else {
$status->failCount++;
@@ -212,6 +227,70 @@ class FSRepo extends FileRepo {
return $status;
}
+ function append( $srcPath, $toAppendPath, $flags = 0 ) {
+ $status = $this->newGood();
+
+ // Resolve the virtual URL
+ if ( self::isVirtualUrl( $srcPath ) ) {
+ $srcPath = $this->resolveVirtualUrl( $srcPath );
+ }
+ // Make sure the files are there
+ if ( !is_file( $srcPath ) )
+ $status->fatal( 'filenotfound', $srcPath );
+
+ if ( !is_file( $toAppendPath ) )
+ $status->fatal( 'filenotfound', $toAppendPath );
+
+ if ( !$status->isOk() ) return $status;
+
+ // Do the append
+ $chunk = file_get_contents( $toAppendPath );
+ if( $chunk === false ) {
+ $status->fatal( 'fileappenderrorread', $toAppendPath );
+ }
+
+ if( $status->isOk() ) {
+ if ( file_put_contents( $srcPath, $chunk, FILE_APPEND ) ) {
+ $status->value = $srcPath;
+ } else {
+ $status->fatal( 'fileappenderror', $toAppendPath, $srcPath);
+ }
+ }
+
+ if ( $flags & self::DELETE_SOURCE ) {
+ unlink( $toAppendPath );
+ }
+
+ return $status;
+ }
+
+ /**
+ * Checks existence of specified array of files.
+ *
+ * @param array $files URLs of files to check
+ * @param integer $flags Bitwise combination of the following flags:
+ * self::FILES_ONLY Mark file as existing only if it is a file (not directory)
+ * @return Either array of files and existence flags, or false
+ */
+ function fileExistsBatch( $files, $flags = 0 ) {
+ if ( !file_exists( $this->directory ) || !is_readable( $this->directory ) ) {
+ return false;
+ }
+ $result = array();
+ foreach ( $files as $key => $file ) {
+ if ( self::isVirtualUrl( $file ) ) {
+ $file = $this->resolveVirtualUrl( $file );
+ }
+ if( $flags & self::FILES_ONLY ) {
+ $result[$key] = is_file( $file );
+ } else {
+ $result[$key] = file_exists( $file );
+ }
+ }
+
+ return $result;
+ }
+
/**
* Take all available measures to prevent web accessibility of new deleted
* directories, in case the user has not configured offline storage
@@ -362,7 +441,7 @@ class FSRepo extends FileRepo {
$status->successCount++;
wfDebug(__METHOD__.": wrote tempfile $srcPath to $dstPath\n");
// Thread-safe override for umask
- chmod( $dstPath, 0644 );
+ $this->chmod( $dstPath );
} else {
$status->failCount++;
}
@@ -439,7 +518,7 @@ class FSRepo extends FileRepo {
$status->error( 'filerenameerror', $srcPath, $archivePath );
$good = false;
} else {
- @chmod( $archivePath, 0644 );
+ $this->chmod( $archivePath );
}
}
if ( $good ) {
@@ -534,4 +613,14 @@ class FSRepo extends FileRepo {
return strtr( $param, $this->simpleCleanPairs );
}
+ /**
+ * Chmod a file, supressing the warnings.
+ * @param String $path The path to change
+ */
+ protected function chmod( $path ) {
+ wfSuppressWarnings();
+ chmod( $path, $this->fileMode );
+ wfRestoreWarnings();
+ }
+
}
diff --git a/includes/filerepo/File.php b/includes/filerepo/File.php
index 523a1c09..d79a1661 100644
--- a/includes/filerepo/File.php
+++ b/includes/filerepo/File.php
@@ -529,7 +529,7 @@ abstract class File {
* @return MediaTransformOutput
*/
function transform( $params, $flags = 0 ) {
- global $wgUseSquid, $wgIgnoreImageErrors;
+ global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch, $wgServer;
wfProfileIn( __METHOD__ );
do {
@@ -539,6 +539,12 @@ abstract class File {
break;
}
+ // Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
+ $descriptionUrl = $this->getDescriptionUrl();
+ if ( $descriptionUrl ) {
+ $params['descriptionUrl'] = $wgServer . $descriptionUrl;
+ }
+
$script = $this->getTransformScript();
if ( $script && !($flags & self::RENDER_NOW) ) {
// Use a script to transform on client request, if possible
@@ -561,9 +567,14 @@ abstract class File {
wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
$this->migrateThumbFile( $thumbName );
- if ( file_exists( $thumbPath ) ) {
- $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
- break;
+ if ( file_exists( $thumbPath )) {
+ $thumbTime = filemtime( $thumbPath );
+ if ( $thumbTime !== FALSE &&
+ gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) {
+
+ $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+ break;
+ }
}
$thumb = $this->handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
@@ -746,15 +757,6 @@ abstract class File {
return $path;
}
- /** Get relative path for a thumbnail file */
- function getThumbRel( $suffix = false ) {
- $path = 'thumb/' . $this->getRel();
- if ( $suffix !== false ) {
- $path .= '/' . $suffix;
- }
- return $path;
- }
-
/** Get the path of the archive directory, or a particular file if $suffix is specified */
function getArchivePath( $suffix = false ) {
return $this->repo->getZonePath('public') . '/' . $this->getArchiveRel( $suffix );
@@ -762,7 +764,11 @@ abstract class File {
/** Get the path of the thumbnail directory, or a particular file if $suffix is specified */
function getThumbPath( $suffix = false ) {
- return $this->repo->getZonePath('public') . '/' . $this->getThumbRel( $suffix );
+ $path = $this->repo->getZonePath('thumb') . '/' . $this->getRel();
+ if ( $suffix !== false ) {
+ $path .= '/' . $suffix;
+ }
+ return $path;
}
/** Get the URL of the archive directory, or a particular file if $suffix is specified */
@@ -778,7 +784,7 @@ abstract class File {
/** Get the URL of the thumbnail directory, or a particular file if $suffix is specified */
function getThumbUrl( $suffix = false ) {
- $path = $this->repo->getZoneUrl('public') . '/thumb/' . $this->getUrlRel();
+ $path = $this->repo->getZoneUrl('thumb') . '/' . $this->getUrlRel();
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
@@ -798,7 +804,7 @@ abstract class File {
/** Get the virtual URL for a thumbnail file or directory */
function getThumbVirtualUrl( $suffix = false ) {
- $path = $this->repo->getVirtualUrl() . '/public/thumb/' . $this->getUrlRel();
+ $path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
if ( $suffix !== false ) {
$path .= '/' . rawurlencode( $suffix );
}
@@ -943,6 +949,14 @@ abstract class File {
function isDeleted( $field ) {
return false;
}
+
+ /**
+ * Return the deletion bitfield
+ * STUB
+ */
+ function getVisibility() {
+ return 0;
+ }
/**
* Was this file ever deleted from the wiki?
@@ -1007,8 +1021,9 @@ abstract class File {
}
/**
- * Returns 'true' if this image is a multipage document, e.g. a DJVU
- * document.
+ * Returns 'true' if this file is a type which supports multiple pages,
+ * e.g. DJVU or PDF. Note that this may be true even if the file in
+ * question only has a single page.
*
* @return Bool
*/
@@ -1069,15 +1084,15 @@ abstract class File {
* Get the HTML text of the description page, if available
*/
function getDescriptionText() {
- global $wgMemc, $wgContLang;
+ global $wgMemc, $wgLang;
if ( !$this->repo->fetchDescription ) {
return false;
}
- $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() );
+ $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
if ( $renderUrl ) {
if ( $this->repo->descriptionCacheExpiry > 0 ) {
wfDebug("Attempting to get the description from cache...");
- $key = wfMemcKey( 'RemoteFileDescription', 'url', $wgContLang->getCode(),
+ $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', $wgLang->getCode(),
$this->getName() );
$obj = $wgMemc->get($key);
if ($obj) {
@@ -1125,6 +1140,19 @@ abstract class File {
}
/**
+ * Get the deletion archive key, <sha1>.<ext>
+ */
+ function getStorageKey() {
+ $hash = $this->getSha1();
+ if ( !$hash ) {
+ return false;
+ }
+ $ext = $this->getExtension();
+ $dotExt = $ext === '' ? '' : ".$ext";
+ return $hash . $dotExt;
+ }
+
+ /**
* Determine if the current user is allowed to view a particular
* field of this file, if it's marked as deleted.
* STUB
@@ -1173,7 +1201,7 @@ abstract class File {
wfDebug(__METHOD__.": $path loaded, {$info['size']} bytes, {$info['mime']}.\n");
} else {
- $info['mime'] = NULL;
+ $info['mime'] = null;
$info['media_type'] = MEDIATYPE_UNKNOWN;
$info['metadata'] = '';
$info['sha1'] = '';
@@ -1259,6 +1287,10 @@ abstract class File {
function redirectedFrom( $from ) {
$this->redirected = $from;
}
+
+ function isMissing() {
+ return false;
+ }
}
/**
* Aliases for backwards compatibility with 1.6
diff --git a/includes/filerepo/FileCache.php b/includes/filerepo/FileCache.php
deleted file mode 100644
index 7840d1a3..00000000
--- a/includes/filerepo/FileCache.php
+++ /dev/null
@@ -1,156 +0,0 @@
-<?php
-/**
- * Cache of file objects, wrapping some RepoGroup functions to avoid redundant
- * queries. Loosely inspired by the LinkCache / LinkBatch classes for titles.
- *
- * ISSUE: Merge with RepoGroup?
- *
- * @ingroup FileRepo
- */
-class FileCache {
- var $repoGroup;
- var $cache = array(), $notFound = array();
-
- protected static $instance;
-
- /**
- * Get a FileCache instance. Typically, only one instance of FileCache
- * is needed in a MediaWiki invocation.
- */
- static function singleton() {
- if ( self::$instance ) {
- return self::$instance;
- }
- self::$instance = new FileCache( RepoGroup::singleton() );
- return self::$instance;
- }
-
- /**
- * Destroy the singleton instance, so that a new one will be created next
- * time singleton() is called.
- */
- static function destroySingleton() {
- self::$instance = null;
- }
-
- /**
- * Set the singleton instance to a given object
- */
- static function setSingleton( $instance ) {
- self::$instance = $instance;
- }
-
- /**
- * Construct a group of file repositories.
- * @param RepoGroup $repoGroup
- */
- function __construct( $repoGroup ) {
- $this->repoGroup = $repoGroup;
- }
-
-
- /**
- * Add some files to the cache. This is a fairly low-level function,
- * which most users should not need to call. Note that any existing
- * entries for the same keys will not be replaced. Call clearFiles()
- * first if you need that.
- * @param array $files array of File objects, indexed by DB key
- */
- function addFiles( $files ) {
- wfDebug( "FileCache adding ".count( $files )." files\n" );
- $this->cache += $files;
- }
-
- /**
- * Remove some files from the cache, so that their existence will be
- * rechecked. This is a fairly low-level function, which most users
- * should not need to call.
- * @param array $remove array indexed by DB keys to remove (the values are ignored)
- */
- function clearFiles( $remove ) {
- wfDebug( "FileCache clearing data for ".count( $remove )." files\n" );
- $this->cache = array_diff_keys( $this->cache, $remove );
- $this->notFound = array_diff_keys( $this->notFound, $remove );
- }
-
- /**
- * Mark some DB keys as nonexistent. This is a fairly low-level
- * function, which most users should not need to call.
- * @param array $dbkeys array of DB keys
- */
- function markNotFound( $dbkeys ) {
- wfDebug( "FileCache marking ".count( $dbkeys )." files as not found\n" );
- $this->notFound += array_fill_keys( $dbkeys, true );
- }
-
-
- /**
- * Search the cache for a file.
- * @param mixed $title Title object or string
- * @return File object or false if it is not found
- * @todo Implement searching for old file versions(?)
- */
- function findFile( $title ) {
- if( !( $title instanceof Title ) ) {
- $title = Title::makeTitleSafe( NS_FILE, $title );
- }
- if( !$title ) {
- return false; // invalid title?
- }
-
- $dbkey = $title->getDBkey();
- if( array_key_exists( $dbkey, $this->cache ) ) {
- wfDebug( "FileCache HIT for $dbkey\n" );
- return $this->cache[$dbkey];
- }
- if( array_key_exists( $dbkey, $this->notFound ) ) {
- wfDebug( "FileCache negative HIT for $dbkey\n" );
- return false;
- }
-
- // Not in cache, fall back to a direct query
- $file = $this->repoGroup->findFile( $title );
- if( $file ) {
- wfDebug( "FileCache MISS for $dbkey\n" );
- $this->cache[$dbkey] = $file;
- } else {
- wfDebug( "FileCache negative MISS for $dbkey\n" );
- $this->notFound[$dbkey] = true;
- }
- return $file;
- }
-
- /**
- * Search the cache for multiple files.
- * @param array $titles Title objects or strings to search for
- * @return array of File objects, indexed by DB key
- */
- function findFiles( $titles ) {
- $titleObjs = array();
- foreach ( $titles as $title ) {
- if ( !( $title instanceof Title ) ) {
- $title = Title::makeTitleSafe( NS_FILE, $title );
- }
- if ( $title ) {
- $titleObjs[$title->getDBkey()] = $title;
- }
- }
-
- $result = array_intersect_key( $this->cache, $titleObjs );
-
- $unsure = array_diff_key( $titleObjs, $result, $this->notFound );
- if( $unsure ) {
- wfDebug( "FileCache MISS for ".count( $unsure )." files out of ".count( $titleObjs )."...\n" );
- // XXX: We assume the array returned by findFiles() is
- // indexed by DBkey; this appears to be true, but should
- // be explicitly documented.
- $found = $this->repoGroup->findFiles( $unsure );
- $result += $found;
- $this->addFiles( $found );
- $this->markNotFound( array_keys( array_diff_key( $unsure, $found ) ) );
- }
-
- wfDebug( "FileCache found ".count( $result )." files out of ".count( $titleObjs )."\n" );
- return $result;
- }
-}
diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php
index c9d34377..f94709b3 100644
--- a/includes/filerepo/FileRepo.php
+++ b/includes/filerepo/FileRepo.php
@@ -6,16 +6,15 @@
* @ingroup FileRepo
*/
abstract class FileRepo {
+ const FILES_ONLY = 1;
const DELETE_SOURCE = 1;
- const FIND_PRIVATE = 1;
- const FIND_IGNORE_REDIRECT = 2;
const OVERWRITE = 2;
const OVERWRITE_SAME = 4;
var $thumbScriptUrl, $transformVia404;
var $descBaseUrl, $scriptDirUrl, $articleUrl, $fetchDescription, $initialCapital;
var $pathDisclosureProtection = 'paranoid';
- var $descriptionCacheExpiry, $apiThumbCacheExpiry, $hashLevels;
+ var $descriptionCacheExpiry, $hashLevels, $url, $thumbUrl;
/**
* Factory functions for creating new files
@@ -29,10 +28,10 @@ abstract class FileRepo {
$this->name = $info['name'];
// Optional settings
- $this->initialCapital = true; // by default
+ $this->initialCapital = MWNamespace::isCapitalized( NS_FILE );
foreach ( array( 'descBaseUrl', 'scriptDirUrl', 'articleUrl', 'fetchDescription',
- 'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
- 'descriptionCacheExpiry', 'apiThumbCacheExpiry', 'hashLevels' ) as $var )
+ 'thumbScriptUrl', 'initialCapital', 'pathDisclosureProtection',
+ 'descriptionCacheExpiry', 'hashLevels', 'url', 'thumbUrl' ) as $var )
{
if ( isset( $info[$var] ) ) {
$this->$var = $info[$var];
@@ -81,9 +80,24 @@ abstract class FileRepo {
* version control should return false if the time is specified.
*
* @param mixed $title Title object or string
- * @param mixed $time 14-character timestamp, or false for the current version
- */
- function findFile( $title, $time = false, $flags = 0 ) {
+ * @param $options Associative array of options:
+ * time: requested time for an archived image, or false for the
+ * current version. An image object will be returned which was
+ * created at the specified time.
+ *
+ * ignoreRedirect: If true, do not follow file redirects
+ *
+ * private: If true, return restricted (deleted) files if the current
+ * user is allowed to view them. Otherwise, such files will not
+ * be found.
+ */
+ function findFile( $title, $options = array() ) {
+ if ( !is_array( $options ) ) {
+ // MW 1.15 compat
+ $time = $options;
+ } else {
+ $time = isset( $options['time'] ) ? $options['time'] : false;
+ }
if ( !($title instanceof Title) ) {
$title = Title::makeTitleSafe( NS_FILE, $title );
if ( !is_object( $title ) ) {
@@ -104,17 +118,17 @@ abstract class FileRepo {
if ( $img && $img->exists() ) {
if ( !$img->isDeleted(File::DELETED_FILE) ) {
return $img;
- } else if ( ($flags & FileRepo::FIND_PRIVATE) && $img->userCan(File::DELETED_FILE) ) {
+ } else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
return $img;
}
}
}
-
+
# Now try redirects
- if ( $flags & FileRepo::FIND_IGNORE_REDIRECT ) {
+ if ( !empty( $options['ignoreRedirect'] ) ) {
return false;
}
- $redir = $this->checkRedirect( $title );
+ $redir = $this->checkRedirect( $title );
if( $redir && $redir->getNamespace() == NS_FILE) {
$img = $this->newFile( $redir );
if( !$img ) {
@@ -127,22 +141,34 @@ abstract class FileRepo {
}
return false;
}
-
+
/*
- * Find many files at once.
- * @param array $titles, an array of titles
- * @todo Think of a good way to optionally pass timestamps to this function.
+ * Find many files at once.
+ * @param array $items, an array of titles, or an array of findFile() options with
+ * the "title" option giving the title. Example:
+ *
+ * $findItem = array( 'title' => $title, 'private' => true );
+ * $findBatch = array( $findItem );
+ * $repo->findFiles( $findBatch );
*/
- function findFiles( $titles ) {
+ function findFiles( $items ) {
$result = array();
- foreach ( $titles as $index => $title ) {
- $file = $this->findFile( $title );
+ foreach ( $items as $index => $item ) {
+ if ( is_array( $item ) ) {
+ $title = $item['title'];
+ $options = $item;
+ unset( $options['title'] );
+ } else {
+ $title = $item;
+ $options = array();
+ }
+ $file = $this->findFile( $title, $options );
if ( $file )
$result[$file->getTitle()->getDBkey()] = $file;
}
return $result;
}
-
+
/**
* Create a new File object from the local repository
* @param mixed $sha1 SHA-1 key
@@ -163,16 +189,23 @@ abstract class FileRepo {
return call_user_func( $this->fileFactoryKey, $sha1, $this );
}
}
-
+
/**
* Find an instance of the file with this key, created at the specified time
* Returns false if the file does not exist. Repositories not supporting
* version control should return false if the time is specified.
*
* @param string $sha1 string
- * @param mixed $time 14-character timestamp, or false for the current version
+ * @param array $options Option array, same as findFile().
*/
- function findFileFromKey( $sha1, $time = false, $flags = 0 ) {
+ function findFileFromKey( $sha1, $options = array() ) {
+ if ( !is_array( $options ) ) {
+ # MW 1.15 compat
+ $time = $options;
+ } else {
+ $time = isset( $options['time'] ) ? $options['time'] : false;
+ }
+
# First try the current version of the file to see if it precedes the timestamp
$img = $this->newFileFromKey( $sha1 );
if ( !$img ) {
@@ -187,7 +220,7 @@ abstract class FileRepo {
if ( $img->exists() ) {
if ( !$img->isDeleted(File::DELETED_FILE) ) {
return $img;
- } else if ( ($flags & FileRepo::FIND_PRIVATE) && $img->userCan(File::DELETED_FILE) ) {
+ } else if ( !empty( $options['private'] ) && $img->userCan(File::DELETED_FILE) ) {
return $img;
}
}
@@ -203,6 +236,15 @@ abstract class FileRepo {
}
/**
+ * Get the URL corresponding to one of the four basic zones
+ * @param String $zone One of: public, deleted, temp, thumb
+ * @return String or false
+ */
+ function getZoneUrl( $zone ) {
+ return false;
+ }
+
+ /**
* Returns true if the repository can transform files via a 404 handler
*/
function canTransformVia404() {
@@ -214,7 +256,7 @@ abstract class FileRepo {
*/
function getNameFromTitle( $title ) {
global $wgCapitalLinks;
- if ( $this->initialCapital != $wgCapitalLinks ) {
+ if ( $this->initialCapital != MWNamespace::isCapitalized( NS_FILE ) ) {
global $wgContLang;
$name = $title->getUserCaseDBKey();
if ( $this->initialCapital ) {
@@ -238,7 +280,7 @@ abstract class FileRepo {
return $path;
}
}
-
+
/**
* Get a relative path including trailing slash, e.g. f/fa/
* If the repo is not hashed, returns an empty string
@@ -355,6 +397,17 @@ abstract class FileRepo {
*/
abstract function storeTemp( $originalName, $srcPath );
+
+ /**
+ * Append the contents of the source path to the given file.
+ * @param $srcPath string location of the source file
+ * @param $toAppendPath string path to append to.
+ * @param $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
+ * that the source file should be deleted if possible
+ * @return mixed Status or false
+ */
+ abstract function append( $srcPath, $toAppendPath, $flags = 0 );
+
/**
* Remove a temporary file or mark it for garbage collection
* @param string $virtualUrl The virtual URL returned by storeTemp
@@ -400,6 +453,21 @@ abstract class FileRepo {
*/
abstract function publishBatch( $triplets, $flags = 0 );
+ function fileExists( $file, $flags = 0 ) {
+ $result = $this->fileExistsBatch( array( $file ), $flags );
+ return $result[0];
+ }
+
+ /**
+ * Checks existence of an array of files.
+ *
+ * @param array $files URLs (or paths) of files to check
+ * @param integer $flags Bitwise combination of the following flags:
+ * self::FILES_ONLY Mark file as existing only if it is a file (not directory)
+ * @return Either array of files and existence flags, or false
+ */
+ abstract function fileExistsBatch( $files, $flags = 0 );
+
/**
* Move a group of files to the deletion archive.
*
@@ -529,21 +597,25 @@ abstract class FileRepo {
/**
* Invalidates image redirect cache related to that image
+ * Doesn't do anything for repositories that don't support image redirects.
*
+ * STUB
* @param Title $title Title of image
- */
- function invalidateImageRedirect( $title ) {
- global $wgMemc;
- $memcKey = $this->getMemcKey( "image_redirect:" . md5( $title->getPrefixedDBkey() ) );
- $wgMemc->delete( $memcKey );
- }
-
+ */
+ function invalidateImageRedirect( $title ) {}
+
+ /**
+ * Get an array or iterator of file objects for files that have a given
+ * SHA-1 content hash.
+ *
+ * STUB
+ */
function findBySha1( $hash ) {
return array();
}
-
+
/**
- * Get the human-readable name of the repo.
+ * Get the human-readable name of the repo.
* @return string
*/
public function getDisplayName() {
@@ -551,22 +623,33 @@ abstract class FileRepo {
if ( $this->name == 'local' ) {
return null;
}
+ // 'shared-repo-name-wikimediacommons' is used when $wgUseInstantCommons = true
$repoName = wfMsg( 'shared-repo-name-' . $this->name );
if ( !wfEmptyMsg( 'shared-repo-name-' . $this->name, $repoName ) ) {
return $repoName;
}
- return wfMsg( 'shared-repo' );
- }
-
- function getSlaveDB() {
- return wfGetDB( DB_SLAVE );
+ return wfMsg( 'shared-repo' );
}
- function getMasterDB() {
- return wfGetDB( DB_MASTER );
+ /**
+ * Get a key on the primary cache for this repository.
+ * Returns false if the repository's cache is not accessible at this site.
+ * The parameters are the parts of the key, as for wfMemcKey().
+ *
+ * STUB
+ */
+ function getSharedCacheKey( /*...*/ ) {
+ return false;
}
-
- function getMemcKey( $key ) {
- return wfWikiID( $this->getSlaveDB() ) . ":{$key}";
+
+ /**
+ * Get a key for this repo in the local cache domain. These cache keys are
+ * not shared with remote instances of the repo.
+ * The parameters are the parts of the key, as for wfMemcKey().
+ */
+ function getLocalCacheKey( /*...*/ ) {
+ $args = func_get_args();
+ array_unshift( $args, 'filerepo', $this->getName() );
+ return call_user_func_array( 'wfMemcKey', $args );
}
}
diff --git a/includes/filerepo/ForeignAPIFile.php b/includes/filerepo/ForeignAPIFile.php
index 03498fb1..c46b1f8f 100644
--- a/includes/filerepo/ForeignAPIFile.php
+++ b/includes/filerepo/ForeignAPIFile.php
@@ -43,10 +43,7 @@ class ForeignAPIFile extends File {
$this->getName(),
isset( $params['width'] ) ? $params['width'] : -1,
isset( $params['height'] ) ? $params['height'] : -1 );
- if( $thumbUrl ) {
- return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );;
- }
- return false;
+ return $this->handler->getTransform( $this, 'bogus', $thumbUrl, $params );;
}
// Info we can get from API...
@@ -108,7 +105,7 @@ class ForeignAPIFile extends File {
return $this->mInfo['mime'];
}
- /// @fixme May guess wrong on file types that can be eg audio or video
+ /// @todo Fixme: may guess wrong on file types that can be eg audio or video
function getMediaType() {
$magic = MimeMagic::singleton();
return $magic->getMediaType( null, $this->getMimeType() );
@@ -162,13 +159,13 @@ class ForeignAPIFile extends File {
function purgeDescriptionPage() {
global $wgMemc, $wgContLang;
$url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() );
- $key = wfMemcKey( 'RemoteFileDescription', 'url', md5($url) );
+ $key = $this->repo->getLocalCacheKey( 'RemoteFileDescription', 'url', md5($url) );
$wgMemc->delete( $key );
}
function purgeThumbnails() {
global $wgMemc;
- $key = wfMemcKey( 'ForeignAPIRepo', 'ThumbUrl', $this->getName() );
+ $key = $this->repo->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $this->getName() );
$wgMemc->delete( $key );
$files = $this->getThumbnails();
$dir = $this->getThumbPath( $this->getName() );
diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php
index e63e4a6b..264cb920 100644
--- a/includes/filerepo/ForeignAPIRepo.php
+++ b/includes/filerepo/ForeignAPIRepo.php
@@ -19,18 +19,30 @@
*/
class ForeignAPIRepo extends FileRepo {
var $fileFactory = array( 'ForeignAPIFile', 'newFromTitle' );
- var $apiThumbCacheExpiry = 0;
+ var $apiThumbCacheExpiry = 86400;
protected $mQueryCache = array();
-
+ protected $mFileExists = array();
+
function __construct( $info ) {
parent::__construct( $info );
$this->mApiBase = $info['apibase']; // http://commons.wikimedia.org/w/api.php
+ if( isset( $info['apiThumbCacheExpiry'] ) ) {
+ $this->apiThumbCacheExpiry = $info['apiThumbCacheExpiry'];
+ }
if( !$this->scriptDirUrl ) {
// hack for description fetches
$this->scriptDirUrl = dirname( $this->mApiBase );
}
+ // If we can cache thumbs we can guess sane defaults for these
+ if( $this->canCacheThumbs() && !$this->url ) {
+ global $wgLocalFileRepo;
+ $this->url = $wgLocalFileRepo['url'];
+ }
+ if( $this->canCacheThumbs() && !$this->thumbUrl ) {
+ $this->thumbUrl = $this->url . '/thumb';
+ }
}
-
+
/**
* Per docs in FileRepo, this needs to return false if we don't support versioned
* files. Well, we don't.
@@ -51,19 +63,49 @@ class ForeignAPIRepo extends FileRepo {
function storeTemp( $originalName, $srcPath ) {
return false;
}
+ function append( $srcPath, $toAppendPath, $flags = 0 ){
+ return false;
+ }
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
function deleteBatch( $sourceDestPairs ) {
return false;
}
+
+
+ function fileExistsBatch( $files, $flags = 0 ) {
+ $results = array();
+ foreach ( $files as $k => $f ) {
+ if ( isset( $this->mFileExists[$k] ) ) {
+ $results[$k] = true;
+ unset( $files[$k] );
+ } elseif( self::isVirtualUrl( $f ) ) {
+ # TODO! FIXME! We need to be able to handle virtual
+ # URLs better, at least when we know they refer to the
+ # same repo.
+ $results[$k] = false;
+ unset( $files[$k] );
+ }
+ }
+
+ $results = $this->fetchImageQuery( array( 'titles' => implode( $files, '|' ),
+ 'prop' => 'imageinfo' ) );
+ if( isset( $data['query']['pages'] ) ) {
+ $i = 0;
+ foreach( $files as $key => $file ) {
+ $results[$key] = $this->mFileExists[$key] = !isset( $data['query']['pages'][$i]['missing'] );
+ $i++;
+ }
+ }
+ }
function getFileProps( $virtualUrl ) {
return false;
}
-
+
protected function queryImage( $query ) {
$data = $this->fetchImageQuery( $query );
-
+
if( isset( $data['query']['pages'] ) ) {
foreach( $data['query']['pages'] as $pageid => $info ) {
if( isset( $info['imageinfo'][0] ) ) {
@@ -73,10 +115,10 @@ class ForeignAPIRepo extends FileRepo {
}
return false;
}
-
+
protected function fetchImageQuery( $query ) {
global $wgMemc;
-
+
$url = $this->mApiBase .
'?' .
wfArrayToCgi(
@@ -84,9 +126,9 @@ class ForeignAPIRepo extends FileRepo {
array(
'format' => 'json',
'action' => 'query' ) ) );
-
+
if( !isset( $this->mQueryCache[$url] ) ) {
- $key = wfMemcKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) );
+ $key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'Metadata', md5( $url ) );
$data = $wgMemc->get( $key );
if( !$data ) {
$data = Http::get( $url );
@@ -102,16 +144,16 @@ class ForeignAPIRepo extends FileRepo {
}
$this->mQueryCache[$url] = $data;
}
- return json_decode( $this->mQueryCache[$url], true );
+ return FormatJson::decode( $this->mQueryCache[$url], true );
}
-
+
function getImageInfo( $title, $time = false ) {
return $this->queryImage( array(
'titles' => 'Image:' . $title->getText(),
'iiprop' => 'timestamp|user|comment|url|size|sha1|metadata|mime',
'prop' => 'imageinfo' ) );
}
-
+
function findBySha1( $hash ) {
$results = $this->fetchImageQuery( array(
'aisha1base36' => $hash,
@@ -125,7 +167,7 @@ class ForeignAPIRepo extends FileRepo {
}
return $ret;
}
-
+
function getThumbUrl( $name, $width=-1, $height=-1 ) {
$info = $this->queryImage( array(
'titles' => 'Image:' . $name,
@@ -133,49 +175,69 @@ class ForeignAPIRepo extends FileRepo {
'iiurlwidth' => $width,
'iiurlheight' => $height,
'prop' => 'imageinfo' ) );
- if( $info ) {
+ if( $info && $info['thumburl'] ) {
wfDebug( __METHOD__ . " got remote thumb " . $info['thumburl'] . "\n" );
return $info['thumburl'];
} else {
return false;
}
}
-
+
function getThumbUrlFromCache( $name, $width, $height ) {
global $wgMemc, $wgUploadPath, $wgServer, $wgUploadDirectory;
-
+
if ( !$this->canCacheThumbs() ) {
return $this->getThumbUrl( $name, $width, $height );
}
-
- $key = wfMemcKey( 'ForeignAPIRepo', 'ThumbUrl', $name );
+
+ $key = $this->getLocalCacheKey( 'ForeignAPIRepo', 'ThumbUrl', $name );
if ( $thumbUrl = $wgMemc->get($key) ) {
wfDebug("Got thumb from local cache. $thumbUrl \n");
return $thumbUrl;
}
else {
$foreignUrl = $this->getThumbUrl( $name, $width, $height );
-
+ if( !$foreignUrl ) {
+ wfDebug( __METHOD__ . " Could not find thumburl\n" );
+ return false;
+ }
+ $thumb = Http::get( $foreignUrl );
+ if( !$thumb ) {
+ wfDebug( __METHOD__ . " Could not download thumb\n" );
+ return false;
+ }
// We need the same filename as the remote one :)
- $fileName = ltrim( substr( $foreignUrl, strrpos( $foreignUrl, '/' ) ), '/' );
+ $fileName = rawurldecode( pathinfo( $foreignUrl, PATHINFO_BASENAME ) );
$path = 'thumb/' . $this->getHashPath( $name ) . $name . "/";
if ( !is_dir($wgUploadDirectory . '/' . $path) ) {
wfMkdirParents($wgUploadDirectory . '/' . $path);
}
- if ( !is_writable( $wgUploadDirectory . '/' . $path . $fileName ) ) {
+ $localUrl = $wgServer . $wgUploadPath . '/' . $path . $fileName;
+ # FIXME: Delete old thumbs that aren't being used. Maintenance script?
+ if( !file_put_contents($wgUploadDirectory . '/' . $path . $fileName, $thumb ) ) {
wfDebug( __METHOD__ . " could not write to thumb path\n" );
return $foreignUrl;
}
- $localUrl = $wgServer . $wgUploadPath . '/' . $path . $fileName;
- $thumb = Http::get( $foreignUrl );
- # FIXME: Delete old thumbs that aren't being used. Maintenance script?
- file_put_contents($wgUploadDirectory . '/' . $path . $fileName, $thumb );
$wgMemc->set( $key, $localUrl, $this->apiThumbCacheExpiry );
wfDebug( __METHOD__ . " got local thumb $localUrl, saving to cache \n" );
return $localUrl;
}
}
-
+
+ /**
+ * @see FileRepo::getZoneUrl()
+ */
+ function getZoneUrl( $zone ) {
+ switch ( $zone ) {
+ case 'public':
+ return $this->url;
+ case 'thumb':
+ return $this->thumbUrl;
+ default:
+ return parent::getZoneUrl( $zone );
+ }
+ }
+
/**
* Are we locally caching the thumbnails?
* @return bool
diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/ForeignDBFile.php
index 8fe6f921..a24ff72b 100644
--- a/includes/filerepo/ForeignDBFile.php
+++ b/includes/filerepo/ForeignDBFile.php
@@ -19,16 +19,6 @@ class ForeignDBFile extends LocalFile {
return $file;
}
- function getCacheKey() {
- if ( $this->repo->hasSharedCache() ) {
- $hashedName = md5($this->name);
- return wfForeignMemcKey( $this->repo->dbName, $this->repo->tablePrefix,
- 'file', $hashedName );
- } else {
- return false;
- }
- }
-
function publish( $srcPath, $flags = 0 ) {
$this->readOnlyError();
}
diff --git a/includes/filerepo/ForeignDBRepo.php b/includes/filerepo/ForeignDBRepo.php
index e078dd25..35c2c4bf 100644
--- a/includes/filerepo/ForeignDBRepo.php
+++ b/includes/filerepo/ForeignDBRepo.php
@@ -44,6 +44,21 @@ class ForeignDBRepo extends LocalRepo {
return $this->hasSharedCache;
}
+ /**
+ * Get a key on the primary cache for this repository.
+ * Returns false if the repository's cache is not accessible at this site.
+ * The parameters are the parts of the key, as for wfMemcKey().
+ */
+ function getSharedCacheKey( /*...*/ ) {
+ if ( $this->hasSharedCache() ) {
+ $args = func_get_args();
+ array_unshift( $args, $this->dbName, $this->tablePrefix );
+ return call_user_func_array( 'wfForeignMemcKey', $args );
+ } else {
+ return false;
+ }
+ }
+
function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
throw new MWException( get_class($this) . ': write operations are not supported' );
}
diff --git a/includes/filerepo/ForeignDBViaLBRepo.php b/includes/filerepo/ForeignDBViaLBRepo.php
index 13c9f434..80325752 100644
--- a/includes/filerepo/ForeignDBViaLBRepo.php
+++ b/includes/filerepo/ForeignDBViaLBRepo.php
@@ -27,6 +27,21 @@ class ForeignDBViaLBRepo extends LocalRepo {
return $this->hasSharedCache;
}
+ /**
+ * Get a key on the primary cache for this repository.
+ * Returns false if the repository's cache is not accessible at this site.
+ * The parameters are the parts of the key, as for wfMemcKey().
+ */
+ function getSharedCacheKey( /*...*/ ) {
+ if ( $this->hasSharedCache() ) {
+ $args = func_get_args();
+ array_unshift( $args, $this->wiki );
+ return implode( ':', $args );
+ } else {
+ return false;
+ }
+ }
+
function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
throw new MWException( get_class($this) . ': write operations are not supported' );
}
diff --git a/includes/filerepo/Image.php b/includes/filerepo/Image.php
index 5207bb4b..08ce219a 100644
--- a/includes/filerepo/Image.php
+++ b/includes/filerepo/Image.php
@@ -19,7 +19,7 @@ class Image extends LocalFile {
*/
static function newFromTitle( $title, $time = false ) {
wfDeprecated( __METHOD__ );
- $img = wfFindFile( $title, $time );
+ $img = wfFindFile( $title, array( 'time' => $time ) );
if ( !$img ) {
$img = wfLocalFile( $title );
}
@@ -44,7 +44,7 @@ class Image extends LocalFile {
}
return $img;
} else {
- return NULL;
+ return null;
}
}
diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/LocalFile.php
index b997d75f..b6b4bfed 100644
--- a/includes/filerepo/LocalFile.php
+++ b/includes/filerepo/LocalFile.php
@@ -24,8 +24,7 @@ define( 'MW_FILE_VERSION', 8 );
*
* @ingroup FileRepo
*/
-class LocalFile extends File
-{
+class LocalFile extends File {
/**#@+
* @private
*/
@@ -49,6 +48,7 @@ class LocalFile extends File
$dataLoaded, # Whether or not all this has been loaded from the database (loadFromXxx)
$upgraded, # Whether the row was upgraded on load
$locked, # True if the image row is locked
+ $missing, # True if file is not present in file system. Not to be cached in memcached
$deleted; # Bitfield akin to rev_deleted
/**#@-*/
@@ -122,7 +122,7 @@ class LocalFile extends File
*/
function __construct( $title, $repo ) {
if( !is_object( $title ) ) {
- throw new MWException( __CLASS__.' constructor given bogus title.' );
+ throw new MWException( __CLASS__ . ' constructor given bogus title.' );
}
parent::__construct( $title, $repo );
$this->metadata = '';
@@ -132,11 +132,12 @@ class LocalFile extends File
}
/**
- * Get the memcached key
+ * Get the memcached key for the main data for this file, or false if
+ * there is no access to the shared cache.
*/
function getCacheKey() {
- $hashedName = md5($this->getName());
- return wfMemcKey( 'file', $hashedName );
+ $hashedName = md5( $this->getName() );
+ return $this->repo->getSharedCacheKey( 'file', $hashedName );
}
/**
@@ -148,12 +149,13 @@ class LocalFile extends File
$this->dataLoaded = false;
$key = $this->getCacheKey();
if ( !$key ) {
+ wfProfileOut( __METHOD__ );
return false;
}
$cachedValues = $wgMemc->get( $key );
// Check if the key existed and belongs to this version of MediaWiki
- if ( isset($cachedValues['version']) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) {
+ if ( isset( $cachedValues['version'] ) && ( $cachedValues['version'] == MW_FILE_VERSION ) ) {
wfDebug( "Pulling file metadata from cache key $key\n" );
$this->fileExists = $cachedValues['fileExists'];
if ( $this->fileExists ) {
@@ -250,7 +252,7 @@ class LocalFile extends File
$prefixLength = strlen( $prefix );
// Sanity check prefix once
if ( substr( key( $array ), 0, $prefixLength ) !== $prefix ) {
- throw new MWException( __METHOD__. ': incorrect $prefix parameter' );
+ throw new MWException( __METHOD__ . ': incorrect $prefix parameter' );
}
$decoded = array();
foreach ( $array as $name => $value ) {
@@ -258,19 +260,19 @@ class LocalFile extends File
}
$decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
if ( empty( $decoded['major_mime'] ) ) {
- $decoded['mime'] = "unknown/unknown";
+ $decoded['mime'] = 'unknown/unknown';
} else {
- if (!$decoded['minor_mime']) {
- $decoded['minor_mime'] = "unknown";
+ if ( !$decoded['minor_mime'] ) {
+ $decoded['minor_mime'] = 'unknown';
}
- $decoded['mime'] = $decoded['major_mime'].'/'.$decoded['minor_mime'];
+ $decoded['mime'] = $decoded['major_mime'] . '/' . $decoded['minor_mime'];
}
# Trim zero padding from char/binary field
$decoded['sha1'] = rtrim( $decoded['sha1'], "\0" );
return $decoded;
}
- /*
+ /**
* Load file metadata from a DB result row
*/
function loadFromRow( $row, $prefix = 'img_' ) {
@@ -303,7 +305,7 @@ class LocalFile extends File
if ( wfReadOnly() ) {
return;
}
- if ( is_null($this->media_type) ||
+ if ( is_null( $this->media_type ) ||
$this->mime == 'image/svg'
) {
$this->upgradeRow();
@@ -331,16 +333,18 @@ class LocalFile extends File
# Don't destroy file info of missing files
if ( !$this->fileExists ) {
- wfDebug( __METHOD__.": file does not exist, aborting\n" );
+ wfDebug( __METHOD__ . ": file does not exist, aborting\n" );
+ wfProfileOut( __METHOD__ );
return;
}
$dbw = $this->repo->getMasterDB();
list( $major, $minor ) = self::splitMime( $this->mime );
if ( wfReadOnly() ) {
+ wfProfileOut( __METHOD__ );
return;
}
- wfDebug(__METHOD__.': upgrading '.$this->getName()." to the current schema\n");
+ wfDebug( __METHOD__ . ': upgrading ' . $this->getName() . " to the current schema\n" );
$dbw->update( 'image',
array(
@@ -391,13 +395,20 @@ class LocalFile extends File
/** getPath inherited */
/** isVisible inhereted */
+ function isMissing() {
+ if( $this->missing === null ) {
+ list( $fileExists ) = $this->repo->fileExistsBatch( array( $this->getVirtualUrl() ), FileRepo::FILES_ONLY );
+ $this->missing = !$fileExists;
+ }
+ return $this->missing;
+ }
+
/**
* Return the width of the image
*
* Returns false on error
- * @public
*/
- function getWidth( $page = 1 ) {
+ public function getWidth( $page = 1 ) {
$this->load();
if ( $this->isMultipage() ) {
$dim = $this->getHandler()->getPageDimensions( $this, $page );
@@ -415,9 +426,8 @@ class LocalFile extends File
* Return the height of the image
*
* Returns false on error
- * @public
*/
- function getHeight( $page = 1 ) {
+ public function getHeight( $page = 1 ) {
$this->load();
if ( $this->isMultipage() ) {
$dim = $this->getHandler()->getPageDimensions( $this, $page );
@@ -436,7 +446,7 @@ class LocalFile extends File
*
* @param $type string 'text' or 'id'
*/
- function getUser($type='text') {
+ function getUser( $type = 'text' ) {
$this->load();
if( $type == 'text' ) {
return $this->user_text;
@@ -460,9 +470,8 @@ class LocalFile extends File
/**
* Return the size of the image file, in bytes
- * @public
*/
- function getSize() {
+ public function getSize() {
$this->load();
return $this->size;
}
@@ -493,9 +502,8 @@ class LocalFile extends File
/**
* Returns true if the file file exists on disk.
* @return boolean Whether file file exist on disk.
- * @public
*/
- function exists() {
+ public function exists() {
$this->load();
return $this->fileExists;
}
@@ -518,7 +526,7 @@ class LocalFile extends File
// This happened occasionally due to broken migration code in 1.5
// Rename to broken-*
for ( $i = 0; $i < 100 ; $i++ ) {
- $broken = $this->repo->getZonePath('public') . "/broken-$i-$thumbName";
+ $broken = $this->repo->getZonePath( 'public' ) . "/broken-$i-$thumbName";
if ( !file_exists( $broken ) ) {
rename( $thumbPath, $broken );
break;
@@ -551,7 +559,7 @@ class LocalFile extends File
$handle = opendir( $dir );
if ( $handle ) {
- while ( false !== ( $file = readdir($handle) ) ) {
+ while ( false !== ( $file = readdir( $handle ) ) ) {
if ( $file{0} != '.' ) {
$files[] = $file;
}
@@ -577,9 +585,11 @@ class LocalFile extends File
*/
function purgeHistory() {
global $wgMemc;
- $hashedName = md5($this->getName());
- $oldKey = wfMemcKey( 'oldfile', $hashedName );
- $wgMemc->delete( $oldKey );
+ $hashedName = md5( $this->getName() );
+ $oldKey = $this->repo->getSharedCacheKey( 'oldfile', $hashedName );
+ if ( $oldKey ) {
+ $wgMemc->delete( $oldKey );
+ }
}
/**
@@ -624,13 +634,13 @@ class LocalFile extends File
/** purgeDescription inherited */
/** purgeEverything inherited */
- function getHistory($limit = null, $start = null, $end = null, $inc = true) {
+ function getHistory( $limit = null, $start = null, $end = null, $inc = true ) {
$dbr = $this->repo->getSlaveDB();
- $tables = array('oldimage');
+ $tables = array( 'oldimage' );
$fields = OldLocalFile::selectFields();
$conds = $opts = $join_conds = array();
- $eq = $inc ? "=" : "";
- $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBKey() );
+ $eq = $inc ? '=' : '';
+ $conds[] = "oi_name = " . $dbr->addQuotes( $this->title->getDBkey() );
if( $start ) {
$conds[] = "oi_timestamp <$eq " . $dbr->addQuotes( $dbr->timestamp( $start ) );
}
@@ -641,19 +651,19 @@ class LocalFile extends File
$opts['LIMIT'] = $limit;
}
// Search backwards for time > x queries
- $order = (!$start && $end !== null) ? "ASC" : "DESC";
+ $order = ( !$start && $end !== null ) ? 'ASC' : 'DESC';
$opts['ORDER BY'] = "oi_timestamp $order";
- $opts['USE INDEX'] = array('oldimage' => 'oi_name_timestamp');
-
+ $opts['USE INDEX'] = array( 'oldimage' => 'oi_name_timestamp' );
+
wfRunHooks( 'LocalFile::getHistory', array( &$this, &$tables, &$fields,
&$conds, &$opts, &$join_conds ) );
-
+
$res = $dbr->select( $tables, $fields, $conds, __METHOD__, $opts, $join_conds );
$r = array();
- while( $row = $dbr->fetchObject($res) ) {
- $r[] = OldLocalFile::newFromRow($row, $this->repo);
+ while( $row = $dbr->fetchObject( $res ) ) {
+ $r[] = OldLocalFile::newFromRow( $row, $this->repo );
}
- if( $order == "ASC" ) {
+ if( $order == 'ASC' ) {
$r = array_reverse( $r ); // make sure it ends up descending
}
return $r;
@@ -666,10 +676,8 @@ class LocalFile extends File
* 0 return line for current version
* 1 query for old versions, return first one
* 2, ... return next old version from above query
- *
- * @public
*/
- function nextHistoryLine() {
+ public function nextHistoryLine() {
# Polymorphic function name to distinguish foreign and local fetches
$fname = get_class( $this ) . '::' . __FUNCTION__;
@@ -687,12 +695,12 @@ class LocalFile extends File
$fname
);
if ( 0 == $dbr->numRows( $this->historyRes ) ) {
- $dbr->freeResult($this->historyRes);
+ $dbr->freeResult( $this->historyRes );
$this->historyRes = null;
- return FALSE;
+ return false;
}
- } else if ( $this->historyLine == 1 ) {
- $dbr->freeResult($this->historyRes);
+ } elseif ( $this->historyLine == 1 ) {
+ $dbr->freeResult( $this->historyRes );
$this->historyRes = $dbr->select( 'oldimage', '*',
array( 'oi_name' => $this->title->getDBkey() ),
$fname,
@@ -706,12 +714,11 @@ class LocalFile extends File
/**
* Reset the history pointer to the first element of the history
- * @public
*/
- function resetHistory() {
+ public function resetHistory() {
$this->historyLine = 0;
- if (!is_null($this->historyRes)) {
- $this->repo->getSlaveDB()->freeResult($this->historyRes);
+ if ( !is_null( $this->historyRes ) ) {
+ $this->repo->getSlaveDB()->freeResult( $this->historyRes );
$this->historyRes = null;
}
}
@@ -763,7 +770,7 @@ class LocalFile extends File
function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '',
$watch = false, $timestamp = false )
{
- $pageText = UploadForm::getInitialPageText( $desc, $license, $copyStatus, $source );
+ $pageText = SpecialUpload::getInitialPageText( $desc, $license, $copyStatus, $source );
if ( !$this->recordUpload2( $oldver, $desc, $pageText ) ) {
return false;
}
@@ -804,7 +811,7 @@ class LocalFile extends File
// Fail now if the file isn't there
if ( !$this->fileExists ) {
- wfDebug( __METHOD__.": File ".$this->getPath()." went missing!\n" );
+ wfDebug( __METHOD__ . ": File " . $this->getPath() . " went missing!\n" );
return false;
}
@@ -905,7 +912,7 @@ class LocalFile extends File
$log->getRcComment(), false );
$nullRevision->insertOn( $dbw );
- wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $user) );
+ wfRunHooks( 'NewRevisionFromEditComplete', array( $article, $nullRevision, $latest, $user ) );
$article->updateRevisionOn( $dbw, $nullRevision );
# Invalidate the cache for the description page
@@ -922,7 +929,7 @@ class LocalFile extends File
# Commit the transaction now, in case something goes wrong later
# The most important thing is that files don't get lost, especially archives
- $dbw->immediateCommit();
+ $dbw->commit();
# Invalidate cache for all pages using this file
$update = new HTMLCacheUpdate( $this->getTitle(), 'imagelinks' );
@@ -1059,7 +1066,7 @@ class LocalFile extends File
*
* @param $reason
* @param $suppress
- * @throws MWException or FSException on database or filestore failure
+ * @throws MWException or FSException on database or file store failure
* @return FileRepoStatus object.
*/
function deleteOld( $archiveName, $reason, $suppress=false ) {
@@ -1386,7 +1393,7 @@ class LocalFileDeleteBatch {
* Run the transaction
*/
function execute() {
- global $wgUser, $wgUseSquid;
+ global $wgUseSquid;
wfProfileIn( __METHOD__ );
$this->file->lock();
@@ -1399,7 +1406,7 @@ class LocalFileDeleteBatch {
array( 'oi_archive_name' ),
array( 'oi_name' => $this->file->getName(),
'oi_archive_name IN (' . $dbw->makeList( array_keys($oldRels) ) . ')',
- 'oi_deleted & ' . File::DELETED_FILE => File::DELETED_FILE ),
+ $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ),
__METHOD__ );
while( $row = $dbw->fetchObject( $res ) ) {
$privateFiles[$row->oi_archive_name] = 1;
@@ -1413,7 +1420,7 @@ class LocalFileDeleteBatch {
foreach ( $this->srcRels as $name => $srcRel ) {
// Skip files that have no hash (missing source).
// Keep private files where they are.
- if ( isset($hashes[$name]) && !array_key_exists($name,$privateFiles) ) {
+ if ( isset( $hashes[$name] ) && !array_key_exists( $name, $privateFiles ) ) {
$hash = $hashes[$name];
$key = $hash . $dotExt;
$dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
@@ -1429,6 +1436,9 @@ class LocalFileDeleteBatch {
// them in a separate transaction, then run the file ops, then update the fa_name fields.
$this->doDBInserts();
+ // Removes non-existent file from the batch, so we don't get errors.
+ $this->deletionBatch = $this->removeNonexistentFiles( $this->deletionBatch );
+
// Execute the file deletion batch
$status = $this->file->repo->deleteBatch( $this->deletionBatch );
if ( !$status->isGood() ) {
@@ -1440,6 +1450,7 @@ class LocalFileDeleteBatch {
// Roll back inserts, release lock and abort
// TODO: delete the defunct filearchive rows if we are using a non-transactional DB
$this->file->unlockAndRollback();
+ wfProfileOut( __METHOD__ );
return $this->status;
}
@@ -1461,6 +1472,22 @@ class LocalFileDeleteBatch {
wfProfileOut( __METHOD__ );
return $this->status;
}
+
+ /**
+ * Removes non-existent files from a deletion batch.
+ */
+ function removeNonexistentFiles( $batch ) {
+ $files = $newBatch = array();
+ foreach( $batch as $batchItem ) {
+ list( $src, $dest ) = $batchItem;
+ $files[$src] = $this->file->repo->getVirtualUrl( 'public' ) . '/' . rawurlencode( $src );
+ }
+ $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
+ foreach( $batch as $batchItem )
+ if( $result[$batchItem[0]] )
+ $newBatch[] = $batchItem;
+ return $newBatch;
+ }
}
#------------------------------------------------------------------------------
@@ -1508,7 +1535,7 @@ class LocalFileRestoreBatch {
* So we save the batch and let the caller call cleanup()
*/
function execute() {
- global $wgUser, $wgLang;
+ global $wgLang;
if ( !$this->all && !$this->ids ) {
// Do nothing
return $this->file->repo->newGood();
@@ -1653,6 +1680,9 @@ class LocalFileRestoreBatch {
$status->error( 'undelete-missing-filearchive', $id );
}
+ // Remove missing files from batch, so we don't get errors when undeleting them
+ $storeBatch = $this->removeNonexistentFiles( $storeBatch );
+
// Run the store batch
// Use the OVERWRITE_SAME flag to smooth over a common error
$storeStatus = $this->file->repo->storeBatch( $storeBatch, FileRepo::OVERWRITE_SAME );
@@ -1683,9 +1713,10 @@ class LocalFileRestoreBatch {
__METHOD__ );
}
- if( $status->successCount > 0 ) {
+ // If store batch is empty (all files are missing), deletion is to be considered successful
+ if( $status->successCount > 0 || !$storeBatch ) {
if( !$exists ) {
- wfDebug( __METHOD__." restored {$status->successCount} items, creating a new current\n" );
+ wfDebug( __METHOD__ . " restored {$status->successCount} items, creating a new current\n" );
// Update site_stats
$site_stats = $dbw->tableName( 'site_stats' );
@@ -1693,7 +1724,7 @@ class LocalFileRestoreBatch {
$this->file->purgeEverything();
} else {
- wfDebug( __METHOD__." restored {$status->successCount} as archived versions\n" );
+ wfDebug( __METHOD__ . " restored {$status->successCount} as archived versions\n" );
$this->file->purgeDescription();
$this->file->purgeHistory();
}
@@ -1703,6 +1734,38 @@ class LocalFileRestoreBatch {
}
/**
+ * Removes non-existent files from a store batch.
+ */
+ function removeNonexistentFiles( $triplets ) {
+ $files = $filteredTriplets = array();
+ foreach( $triplets as $file )
+ $files[$file[0]] = $file[0];
+ $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
+ foreach( $triplets as $file )
+ if( $result[$file[0]] )
+ $filteredTriplets[] = $file;
+ return $filteredTriplets;
+ }
+
+ /**
+ * Removes non-existent files from a cleanup batch.
+ */
+ function removeNonexistentFromCleanup( $batch ) {
+ $files = $newBatch = array();
+ $repo = $this->file->repo;
+ foreach( $batch as $file ) {
+ $files[$file] = $repo->getVirtualUrl( 'deleted' ) . '/' .
+ rawurlencode( $repo->getDeletedHashPath( $file ) . $file );
+ }
+
+ $result = $repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
+ foreach( $batch as $file )
+ if( $result[$file] )
+ $newBatch[] = $file;
+ return $newBatch;
+ }
+
+ /**
* Delete unused files in the deleted zone.
* This should be called from outside the transaction in which execute() was called.
*/
@@ -1710,6 +1773,7 @@ class LocalFileRestoreBatch {
if ( !$this->cleanupBatch ) {
return $this->file->repo->newGood();
}
+ $this->cleanupBatch = $this->removeNonexistentFromCleanup( $this->cleanupBatch );
$status = $this->file->repo->cleanupDeletedBatch( $this->cleanupBatch );
return $status;
}
@@ -1728,7 +1792,7 @@ class LocalFileMoveBatch {
$this->file = $file;
$this->target = $target;
$this->oldHash = $this->file->repo->getHashPath( $this->file->getName() );
- $this->newHash = $this->file->repo->getHashPath( $this->target->getDBKey() );
+ $this->newHash = $this->file->repo->getHashPath( $this->target->getDBkey() );
$this->oldName = $this->file->getName();
$this->newName = $this->file->repo->getNameFromTitle( $this->target );
$this->oldRel = $this->oldHash . $this->oldName;
@@ -1736,14 +1800,14 @@ class LocalFileMoveBatch {
$this->db = $file->repo->getMasterDb();
}
- /*
+ /**
* Add the current image to the batch
*/
function addCurrent() {
$this->cur = array( $this->oldRel, $this->newRel );
}
- /*
+ /**
* Add the old versions of the image to the batch
*/
function addOlds() {
@@ -1781,7 +1845,7 @@ class LocalFileMoveBatch {
$this->db->freeResult( $result );
}
- /*
+ /**
* Perform the move.
*/
function execute() {
@@ -1789,6 +1853,7 @@ class LocalFileMoveBatch {
$status = $repo->newGood();
$triplets = $this->getMoveTriplets();
+ $triplets = $this->removeNonexistentFiles( $triplets );
$statusDb = $this->doDBUpdates();
wfDebugLog( 'imagemove', "Renamed {$this->file->name} in database: {$statusDb->successCount} successes, {$statusDb->failCount} failures" );
$statusMove = $repo->storeBatch( $triplets, FSRepo::DELETE_SOURCE );
@@ -1797,12 +1862,13 @@ class LocalFileMoveBatch {
wfDebugLog( 'imagemove', "Error in moving files: " . $statusMove->getWikiText() );
$this->db->rollback();
}
+
$status->merge( $statusDb );
$status->merge( $statusMove );
return $status;
}
- /*
+ /**
* Do the database updates and return a new WikiError indicating how many
* rows where updated.
*/
@@ -1842,7 +1908,7 @@ class LocalFileMoveBatch {
return $status;
}
- /*
+ /**
* Generate triplets for FSRepo::storeBatch().
*/
function getMoveTriplets() {
@@ -1856,4 +1922,22 @@ class LocalFileMoveBatch {
}
return $triplets;
}
+
+ /**
+ * Removes non-existent files from move batch.
+ */
+ function removeNonexistentFiles( $triplets ) {
+ $files = array();
+ foreach( $triplets as $file )
+ $files[$file[0]] = $file[0];
+ $result = $this->file->repo->fileExistsBatch( $files, FSRepo::FILES_ONLY );
+ $filteredTriplets = array();
+ foreach( $triplets as $file )
+ if( $result[$file[0]] ) {
+ $filteredTriplets[] = $file;
+ } else {
+ wfDebugLog( 'imagemove', "File {$file[0]} does not exist" );
+ }
+ return $filteredTriplets;
+ }
}
diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php
index c679dd98..6c4d21a2 100644
--- a/includes/filerepo/LocalRepo.php
+++ b/includes/filerepo/LocalRepo.php
@@ -49,8 +49,8 @@ class LocalRepo extends FSRepo {
$ext = File::normalizeExtension($ext);
$inuse = $dbw->selectField( 'oldimage', '1',
array( 'oi_sha1' => $sha1,
- "oi_archive_name LIKE '%.{$ext}'",
- 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ),
+ 'oi_archive_name ' . $dbw->buildLike( $dbw->anyString(), ".$ext" ),
+ $dbw->bitAnd('oi_deleted', File::DELETED_FILE) => File::DELETED_FILE ),
__METHOD__, array( 'FOR UPDATE' ) );
}
if ( !$inuse ) {
@@ -83,17 +83,24 @@ class LocalRepo extends FSRepo {
$title = Title::makeTitle( NS_FILE, $title->getText() );
}
- $memcKey = $this->getMemcKey( "image_redirect:" . md5( $title->getPrefixedDBkey() ) );
+ $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
+ if ( $memcKey === false ) {
+ $memcKey = $this->getLocalCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
+ $expiry = 300; // no invalidation, 5 minutes
+ } else {
+ $expiry = 86400; // has invalidation, 1 day
+ }
$cachedValue = $wgMemc->get( $memcKey );
- if( $cachedValue ) {
- return Title::newFromDbKey( $cachedValue );
- } elseif( $cachedValue == ' ' ) { # FIXME: ugly hack, but BagOStuff caching seems to be weird and return false if !cachedValue, not only if it doesn't exist
+ if ( $cachedValue === ' ' || $cachedValue === '' ) {
+ // Does not exist
return false;
- }
+ } elseif ( strval( $cachedValue ) !== '' ) {
+ return Title::newFromText( $cachedValue, NS_FILE );
+ } // else $cachedValue is false or null: cache miss
$id = $this->getArticleID( $title );
if( !$id ) {
- $wgMemc->set( $memcKey, " ", 9000 );
+ $wgMemc->set( $memcKey, " ", $expiry );
return false;
}
$dbr = $this->getSlaveDB();
@@ -104,12 +111,14 @@ class LocalRepo extends FSRepo {
__METHOD__
);
- if( $row ) $targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title );
- $wgMemc->set( $memcKey, ($row ? $targetTitle->getPrefixedDBkey() : " "), 9000 );
- if( !$row ) {
+ if( $row && $row->rd_namespace == NS_FILE ) {
+ $targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title );
+ $wgMemc->set( $memcKey, $targetTitle->getDBkey(), $expiry );
+ return $targetTitle;
+ } else {
+ $wgMemc->set( $memcKey, '', $expiry );
return false;
}
- return $targetTitle;
}
@@ -127,15 +136,17 @@ class LocalRepo extends FSRepo {
'page_id', //Field
array( //Conditions
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBKey(),
+ 'page_title' => $title->getDBkey(),
),
__METHOD__ //Function name
);
return $id;
}
-
-
+ /**
+ * Get an array or iterator of file objects for files that have a given
+ * SHA-1 content hash.
+ */
function findBySha1( $hash ) {
$dbr = $this->getSlaveDB();
$res = $dbr->select(
@@ -150,28 +161,42 @@ class LocalRepo extends FSRepo {
$res->free();
return $result;
}
-
- /*
- * Find many files using one query
+
+ /**
+ * Get a connection to the slave DB
*/
- function findFiles( $titles ) {
- // FIXME: Only accepts a $titles array where the keys are the sanitized
- // file names.
-
- if ( count( $titles ) == 0 ) return array();
-
- $dbr = $this->getSlaveDB();
- $res = $dbr->select(
- 'image',
- LocalFile::selectFields(),
- array( 'img_name' => array_keys( $titles ) )
- );
-
- $result = array();
- while ( $row = $res->fetchObject() ) {
- $result[$row->img_name] = $this->newFileFromRow( $row );
+ function getSlaveDB() {
+ return wfGetDB( DB_SLAVE );
+ }
+
+ /**
+ * Get a connection to the master DB
+ */
+ function getMasterDB() {
+ return wfGetDB( DB_MASTER );
+ }
+
+ /**
+ * Get a key on the primary cache for this repository.
+ * Returns false if the repository's cache is not accessible at this site.
+ * The parameters are the parts of the key, as for wfMemcKey().
+ */
+ function getSharedCacheKey( /*...*/ ) {
+ $args = func_get_args();
+ return call_user_func_array( 'wfMemcKey', $args );
+ }
+
+ /**
+ * Invalidates image redirect cache related to that image
+ *
+ * @param Title $title Title of image
+ */
+ function invalidateImageRedirect( $title ) {
+ global $wgMemc;
+ $memcKey = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
+ if ( $memcKey ) {
+ $wgMemc->delete( $memcKey );
}
- $res->free();
- return $result;
}
}
+
diff --git a/includes/filerepo/NullRepo.php b/includes/filerepo/NullRepo.php
index fb89cebb..2bc61bde 100644
--- a/includes/filerepo/NullRepo.php
+++ b/includes/filerepo/NullRepo.php
@@ -14,19 +14,25 @@ class NullRepo extends FileRepo {
function storeTemp( $originalName, $srcPath ) {
return false;
}
+ function append( $srcPath, $toAppendPath, $flags = 0 ){
+ return false;
+ }
function publishBatch( $triplets, $flags = 0 ) {
return false;
}
function deleteBatch( $sourceDestPairs ) {
return false;
}
+ function fileExistsBatch( $files, $flags = 0 ) {
+ return false;
+ }
function getFileProps( $virtualUrl ) {
return false;
}
function newFile( $title, $time = false ) {
return false;
}
- function findFile( $title, $time = false ) {
+ function findFile( $title, $options = array() ) {
return false;
}
}
diff --git a/includes/filerepo/OldLocalFile.php b/includes/filerepo/OldLocalFile.php
index 46c35bd9..35f3f9f2 100644
--- a/includes/filerepo/OldLocalFile.php
+++ b/includes/filerepo/OldLocalFile.php
@@ -177,25 +177,27 @@ class OldLocalFile extends LocalFile {
* @return bool
*/
function isDeleted( $field ) {
+ $this->load();
return ($this->deleted & $field) == $field;
}
/**
+ * Returns bitfield value
+ * @return int
+ */
+ function getVisibility() {
+ $this->load();
+ return (int)$this->deleted;
+ }
+
+ /**
* Determine if the current user is allowed to view a particular
- * field of this FileStore image file, if it's marked as deleted.
+ * field of this image file, if it's marked as deleted.
* @param int $field
* @return bool
*/
function userCan( $field ) {
- if( isset($this->deleted) && ($this->deleted & $field) == $field ) {
- global $wgUser;
- $permission = ( $this->deleted & File::DELETED_RESTRICTED ) == File::DELETED_RESTRICTED
- ? 'suppressrevision'
- : 'deleterevision';
- wfDebug( "Checking for $permission due to $field match on $this->mDeleted\n" );
- return $wgUser->isAllowed( $permission );
- } else {
- return true;
- }
+ $this->load();
+ return Revision::userCanBitfield( $this->deleted, $field );
}
}
diff --git a/includes/filerepo/RepoGroup.php b/includes/filerepo/RepoGroup.php
index 2303f581..1465400c 100644
--- a/includes/filerepo/RepoGroup.php
+++ b/includes/filerepo/RepoGroup.php
@@ -13,8 +13,10 @@
class RepoGroup {
var $localRepo, $foreignRepos, $reposInitialised = false;
var $localInfo, $foreignInfo;
+ var $cache;
protected static $instance;
+ const MAX_CACHE_SIZE = 1000;
/**
* Get a RepoGroup instance. At present only one instance of RepoGroup is
@@ -54,56 +56,116 @@ class RepoGroup {
function __construct( $localInfo, $foreignInfo ) {
$this->localInfo = $localInfo;
$this->foreignInfo = $foreignInfo;
+ $this->cache = array();
}
/**
* Search repositories for an image.
- * You can also use wfGetFile() to do this.
+ * You can also use wfFindFile() to do this.
* @param mixed $title Title object or string
- * @param mixed $time The 14-char timestamp the file should have
- * been uploaded, or false for the current version
- * @param mixed $flags FileRepo::FIND_ flags
+ * @param $options Associative array of options:
+ * time: requested time for an archived image, or false for the
+ * current version. An image object will be returned which was
+ * created at the specified time.
+ *
+ * ignoreRedirect: If true, do not follow file redirects
+ *
+ * private: If true, return restricted (deleted) files if the current
+ * user is allowed to view them. Otherwise, such files will not
+ * be found.
+ *
+ * bypassCache: If true, do not use the process-local cache of File objects
* @return File object or false if it is not found
*/
- function findFile( $title, $time = false, $flags = 0 ) {
+ function findFile( $title, $options = array() ) {
+ if ( !is_array( $options ) ) {
+ // MW 1.15 compat
+ $options = array( 'time' => $options );
+ }
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
+ if ( !($title instanceof Title) ) {
+ $title = Title::makeTitleSafe( NS_FILE, $title );
+ if ( !is_object( $title ) ) {
+ return false;
+ }
+ }
- $image = $this->localRepo->findFile( $title, $time, $flags );
+ # Check the cache
+ if ( empty( $options['ignoreRedirect'] )
+ && empty( $options['private'] )
+ && empty( $options['bypassCache'] ) )
+ {
+ $useCache = true;
+ $time = isset( $options['time'] ) ? $options['time'] : '';
+ $dbkey = $title->getDBkey();
+ if ( isset( $this->cache[$dbkey][$time] ) ) {
+ wfDebug( __METHOD__.": got File:$dbkey from process cache\n" );
+ # Move it to the end of the list so that we can delete the LRU entry later
+ $tmp = $this->cache[$dbkey];
+ unset( $this->cache[$dbkey] );
+ $this->cache[$dbkey] = $tmp;
+ # Return the entry
+ return $this->cache[$dbkey][$time];
+ } else {
+ # Add a negative cache entry, may be overridden
+ $this->trimCache();
+ $this->cache[$dbkey][$time] = false;
+ $cacheEntry =& $this->cache[$dbkey][$time];
+ }
+ } else {
+ $useCache = false;
+ }
+
+ # Check the local repo
+ $image = $this->localRepo->findFile( $title, $options );
if ( $image ) {
+ if ( $useCache ) {
+ $cacheEntry = $image;
+ }
return $image;
}
+
+ # Check the foreign repos
foreach ( $this->foreignRepos as $repo ) {
- $image = $repo->findFile( $title, $time, $flags );
+ $image = $repo->findFile( $title, $options );
if ( $image ) {
+ if ( $useCache ) {
+ $cacheEntry = $image;
+ }
return $image;
}
}
+ # Not found, do not override negative cache
return false;
}
- function findFiles( $titles ) {
+
+ function findFiles( $inputItems ) {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
- $titleObjs = array();
- foreach ( $titles as $title ) {
- if ( !( $title instanceof Title ) )
- $title = Title::makeTitleSafe( NS_FILE, $title );
- if ( $title )
- $titleObjs[$title->getDBkey()] = $title;
+ $items = array();
+ foreach ( $inputItems as $item ) {
+ if ( !is_array( $item ) ) {
+ $item = array( 'title' => $item );
+ }
+ if ( !( $item['title'] instanceof Title ) )
+ $item['title'] = Title::makeTitleSafe( NS_FILE, $item['title'] );
+ if ( $item['title'] )
+ $items[$item['title']->getDBkey()] = $item;
}
- $images = $this->localRepo->findFiles( $titleObjs );
+ $images = $this->localRepo->findFiles( $items );
foreach ( $this->foreignRepos as $repo ) {
- // Remove found files from $titleObjs
- foreach ( $images as $name => $image )
- if ( isset( $titleObjs[$name] ) )
- unset( $titleObjs[$name] );
-
- $images = array_merge( $images, $repo->findFiles( $titleObjs ) );
+ // Remove found files from $items
+ foreach ( $images as $name => $image ) {
+ unset( $items[$name] );
+ }
+
+ $images = array_merge( $images, $repo->findFiles( $items ) );
}
return $images;
}
@@ -128,16 +190,16 @@ class RepoGroup {
}
return false;
}
-
+
function findBySha1( $hash ) {
if ( !$this->reposInitialised ) {
$this->initialiseRepos();
}
-
+
$result = $this->localRepo->findBySha1( $hash );
foreach ( $this->foreignRepos as $repo )
$result = array_merge( $result, $repo->findBySha1( $hash ) );
- return $result;
+ return $result;
}
/**
@@ -178,7 +240,7 @@ class RepoGroup {
}
/**
- * Call a function for each foreign repo, with the repo object as the
+ * Call a function for each foreign repo, with the repo object as the
* first parameter.
*
* @param $callback callback The function to call
@@ -254,4 +316,16 @@ class RepoGroup {
return File::getPropsFromPath( $fileName );
}
}
+
+ /**
+ * Limit cache memory
+ */
+ function trimCache() {
+ while ( count( $this->cache ) >= self::MAX_CACHE_SIZE ) {
+ reset( $this->cache );
+ $key = key( $this->cache );
+ wfDebug( __METHOD__.": evicting $key\n" );
+ unset( $this->cache[$key] );
+ }
+ }
}