00001 <?php
00002
00006 class FileStore {
00007 const DELETE_ORIGINAL = 1;
00008
00012 static function get( $group ) {
00013 global $wgFileStore;
00014
00015 if( isset( $wgFileStore[$group] ) ) {
00016 $info = $wgFileStore[$group];
00017 return new FileStore( $group,
00018 $info['directory'],
00019 $info['url'],
00020 intval( $info['hash'] ) );
00021 } else {
00022 return null;
00023 }
00024 }
00025
00026 private function __construct( $group, $directory, $path, $hash ) {
00027 $this->mGroup = $group;
00028 $this->mDirectory = $directory;
00029 $this->mPath = $path;
00030 $this->mHashLevel = $hash;
00031 }
00032
00040 static function lock() {
00041 $dbw = wfGetDB( DB_MASTER );
00042 $lockname = $dbw->addQuotes( FileStore::lockName() );
00043 return $dbw->lock( $lockname, __METHOD__ );
00044 }
00045
00050 static function unlock() {
00051 $dbw = wfGetDB( DB_MASTER );
00052 $lockname = $dbw->addQuotes( FileStore::lockName() );
00053 return $dbw->unlock( $lockname, __METHOD__ );
00054 }
00055
00056 private static function lockName() {
00057 return 'MediaWiki.' . wfWikiID() . '.FileStore';
00058 }
00059
00071 function insert( $key, $sourcePath, $flags=0 ) {
00072 $destPath = $this->filePath( $key );
00073 return $this->copyFile( $sourcePath, $destPath, $flags );
00074 }
00075
00087 function export( $key, $destPath, $flags=0 ) {
00088 $sourcePath = $this->filePath( $key );
00089 return $this->copyFile( $sourcePath, $destPath, $flags );
00090 }
00091
00092 private function copyFile( $sourcePath, $destPath, $flags=0 ) {
00093 if( !file_exists( $sourcePath ) ) {
00094
00095 throw new FSException( "missing source file '$sourcePath'" );
00096 }
00097
00098 $transaction = new FSTransaction();
00099
00100 if( $flags & self::DELETE_ORIGINAL ) {
00101 $transaction->addCommit( FSTransaction::DELETE_FILE, $sourcePath );
00102 }
00103
00104 if( file_exists( $destPath ) ) {
00105
00106 } else {
00107 if( !file_exists( dirname( $destPath ) ) ) {
00108 wfSuppressWarnings();
00109 $ok = wfMkdirParents( dirname( $destPath ) );
00110 wfRestoreWarnings();
00111
00112 if( !$ok ) {
00113 throw new FSException(
00114 "failed to create directory for '$destPath'" );
00115 }
00116 }
00117
00118 wfSuppressWarnings();
00119 $ok = copy( $sourcePath, $destPath );
00120 wfRestoreWarnings();
00121
00122 if( $ok ) {
00123 wfDebug( __METHOD__." copied '$sourcePath' to '$destPath'\n" );
00124 $transaction->addRollback( FSTransaction::DELETE_FILE, $destPath );
00125 } else {
00126 throw new FSException(
00127 __METHOD__." failed to copy '$sourcePath' to '$destPath'" );
00128 }
00129 }
00130
00131 return $transaction;
00132 }
00133
00145 function delete( $key ) {
00146 $destPath = $this->filePath( $key );
00147 if( false === $destPath ) {
00148 throw new FSException( "file store does not contain file '$key'" );
00149 } else {
00150 return FileStore::deleteFile( $destPath );
00151 }
00152 }
00153
00166 static function deleteFile( $path ) {
00167 if( file_exists( $path ) ) {
00168 $transaction = new FSTransaction();
00169 $transaction->addCommit( FSTransaction::DELETE_FILE, $path );
00170 return $transaction;
00171 } else {
00172 throw new FSException( "cannot delete missing file '$path'" );
00173 }
00174 }
00175
00181 function stream( $key ) {
00182 $path = $this->filePath( $key );
00183 if( $path === false ) {
00184 wfHttpError( 400, "Bad request", "Invalid or badly-formed filename." );
00185 return false;
00186 }
00187
00188 if( file_exists( $path ) ) {
00189
00190
00191 header( 'Content-Disposition: inline; filename="' . $key . '"' );
00192
00193 require_once 'StreamFile.php';
00194 wfStreamFile( $path );
00195 } else {
00196 return wfHttpError( 404, "Not found",
00197 "The requested resource does not exist." );
00198 }
00199 }
00200
00212 static function validKey( $key ) {
00213 return preg_match( '/^[0-9a-z]{31,32}(\.[0-9a-z]{1,31})?$/', $key );
00214 }
00215
00216
00226 static function calculateKey( $path, $extension ) {
00227 wfSuppressWarnings();
00228 $hash = sha1_file( $path );
00229 wfRestoreWarnings();
00230 if( $hash === false ) {
00231 wfDebug( __METHOD__.": couldn't hash file '$path'\n" );
00232 return false;
00233 }
00234
00235 $base36 = wfBaseConvert( $hash, 16, 36, 31 );
00236 if( $extension == '' ) {
00237 $key = $base36;
00238 } else {
00239 $key = $base36 . '.' . $extension;
00240 }
00241
00242
00243 if( self::validKey( $key ) ) {
00244 return $key;
00245 } else {
00246 wfDebug( __METHOD__.": generated bad key '$key'\n" );
00247 return false;
00248 }
00249 }
00250
00256 function filePath( $key ) {
00257 if( self::validKey( $key ) ) {
00258 return $this->mDirectory . DIRECTORY_SEPARATOR .
00259 $this->hashPath( $key, DIRECTORY_SEPARATOR );
00260 } else {
00261 return false;
00262 }
00263 }
00264
00269 function urlPath( $key ) {
00270 if( $this->mUrl && self::validKey( $key ) ) {
00271 return $this->mUrl . '/' . $this->hashPath( $key, '/' );
00272 } else {
00273 return false;
00274 }
00275 }
00276
00277 private function hashPath( $key, $separator ) {
00278 $parts = array();
00279 for( $i = 0; $i < $this->mHashLevel; $i++ ) {
00280 $parts[] = $key{$i};
00281 }
00282 $parts[] = $key;
00283 return implode( $separator, $parts );
00284 }
00285 }
00286
00294 class FSTransaction {
00295 const DELETE_FILE = 1;
00296
00300 function add( FSTransaction $transaction ) {
00301 $this->mOnCommit = array_merge(
00302 $this->mOnCommit, $transaction->mOnCommit );
00303 $this->mOnRollback = array_merge(
00304 $this->mOnRollback, $transaction->mOnRollback );
00305 }
00306
00311 function commit() {
00312 return $this->apply( $this->mOnCommit );
00313 }
00314
00319 function rollback() {
00320 return $this->apply( $this->mOnRollback );
00321 }
00322
00323
00324
00325 function __construct() {
00326 $this->mOnCommit = array();
00327 $this->mOnRollback = array();
00328 }
00329
00330 function addCommit( $action, $path ) {
00331 $this->mOnCommit[] = array( $action, $path );
00332 }
00333
00334 function addRollback( $action, $path ) {
00335 $this->mOnRollback[] = array( $action, $path );
00336 }
00337
00338 private function apply( $actions ) {
00339 $result = true;
00340 foreach( $actions as $item ) {
00341 list( $action, $path ) = $item;
00342 if( $action == self::DELETE_FILE ) {
00343 wfSuppressWarnings();
00344 $ok = unlink( $path );
00345 wfRestoreWarnings();
00346 if( $ok )
00347 wfDebug( __METHOD__.": deleting file '$path'\n" );
00348 else
00349 wfDebug( __METHOD__.": failed to delete file '$path'\n" );
00350 $result = $result && $ok;
00351 }
00352 }
00353 return $result;
00354 }
00355 }
00356
00360 class FSException extends MWException { }