dc404447f141c49061adf11e61a23ecf043acf42
4 * A repository for files accessible via the local filesystem. Does not support
5 * database access or registration.
8 class FSRepo
extends FileRepo
{
9 var $directory, $url, $hashLevels;
10 var $fileFactory = array( 'UnregisteredLocalFile', 'newFromTitle' );
11 var $oldFileFactory = false;
13 function __construct( $info ) {
14 parent
::__construct( $info );
17 $this->directory
= $info['directory'];
18 $this->url
= $info['url'];
19 $this->hashLevels
= $info['hashLevels'];
23 * Get the public root directory of the repository.
25 function getRootDirectory() {
26 return $this->directory
;
30 * Get the public root URL of the repository
32 function getRootUrl() {
37 * Returns true if the repository uses a multi-level directory structure
40 return (bool)$this->hashLevels
;
44 * Get the local directory corresponding to one of the three basic zones
46 function getZonePath( $zone ) {
49 return $this->directory
;
51 return "{$this->directory}/temp";
53 return $GLOBALS['wgFileStore']['deleted']['directory'];
60 * Get the URL corresponding to one of the three basic zones
62 function getZoneUrl( $zone ) {
67 return "{$this->url}/temp";
69 return $GLOBALS['wgFileStore']['deleted']['url'];
76 * Get a URL referring to this repository, with the private mwrepo protocol.
77 * The suffix, if supplied, is considered to be unencoded, and will be
78 * URL-encoded before being returned.
80 function getVirtualUrl( $suffix = false ) {
81 $path = 'mwrepo://' . $this->name
;
82 if ( $suffix !== false ) {
83 $path .= '/' . rawurlencode( $suffix );
89 * Get the local path corresponding to a virtual URL
91 function resolveVirtualUrl( $url ) {
92 if ( substr( $url, 0, 9 ) != 'mwrepo://' ) {
93 throw new MWException( __METHOD__
.': unknown protoocl' );
96 $bits = explode( '/', substr( $url, 9 ), 3 );
97 if ( count( $bits ) != 3 ) {
98 throw new MWException( __METHOD__
.": invalid mwrepo URL: $url" );
100 list( $repo, $zone, $rel ) = $bits;
101 if ( $repo !== $this->name
) {
102 throw new MWException( __METHOD__
.": fetching from a foreign repo is not supported" );
104 $base = $this->getZonePath( $zone );
106 throw new MWException( __METHOD__
.": invalid zone: $zone" );
108 return $base . '/' . rawurldecode( $rel );
112 * Store a file to a given destination.
114 function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
115 if ( !is_writable( $this->directory
) ) {
116 return new WikiErrorMsg( 'upload_directory_read_only', wfEscapeWikiText( $this->directory
) );
118 $root = $this->getZonePath( $dstZone );
120 throw new MWException( "Invalid zone: $dstZone" );
122 $dstPath = "$root/$dstRel";
124 if ( !is_dir( dirname( $dstPath ) ) ) {
125 wfMkdirParents( dirname( $dstPath ) );
128 if ( self
::isVirtualUrl( $srcPath ) ) {
129 $srcPath = $this->resolveVirtualUrl( $srcPath );
132 if ( $flags & self
::DELETE_SOURCE
) {
133 if ( !rename( $srcPath, $dstPath ) ) {
134 return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
135 wfEscapeWikiText( $dstPath ) );
138 if ( !copy( $srcPath, $dstPath ) ) {
139 return new WikiErrorMsg( 'filecopyerror', wfEscapeWikiText( $srcPath ),
140 wfEscapeWikiText( $dstPath ) );
143 chmod( $dstPath, 0644 );
148 * Pick a random name in the temp zone and store a file to it.
149 * Returns the URL, or a WikiError on failure.
150 * @param string $originalName The base name of the file as specified
151 * by the user. The file extension will be maintained.
152 * @param string $srcPath The current location of the file.
154 function storeTemp( $originalName, $srcPath ) {
155 $date = gmdate( "YmdHis" );
156 $hashPath = $this->getHashPath( $originalName );
157 $dstRel = "$hashPath$date!$originalName";
158 $dstUrlRel = $hashPath . $date . '!' . rawurlencode( $originalName );
160 $result = $this->store( $srcPath, 'temp', $dstRel );
161 if ( WikiError
::isError( $result ) ) {
164 return $this->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
169 * Remove a temporary file or mark it for garbage collection
170 * @param string $virtualUrl The virtual URL returned by storeTemp
171 * @return boolean True on success, false on failure
173 function freeTemp( $virtualUrl ) {
174 $temp = "mwrepo://{$this->name}/temp";
175 if ( substr( $virtualUrl, 0, strlen( $temp ) ) != $temp ) {
176 wfDebug( __METHOD__
.": Invalid virtual URL\n" );
179 $path = $this->resolveVirtualUrl( $virtualUrl );
180 wfSuppressWarnings();
181 $success = unlink( $path );
188 * Copy or move a file either from the local filesystem or from an mwrepo://
189 * virtual URL, into this repository at the specified destination location.
191 * @param string $srcPath The source path or URL
192 * @param string $dstRel The destination relative path
193 * @param string $archiveRel The relative path where the existing file is to
194 * be archived, if there is one. Relative to the public zone root.
195 * @param integer $flags Bitfield, may be FileRepo::DELETE_SOURCE to indicate
196 * that the source file should be deleted if possible
198 function publish( $srcPath, $dstRel, $archiveRel, $flags = 0 ) {
199 if ( !is_writable( $this->directory
) ) {
200 return new WikiErrorMsg( 'upload_directory_read_only', wfEscapeWikiText( $this->directory
) );
202 if ( substr( $srcPath, 0, 9 ) == 'mwrepo://' ) {
203 $srcPath = $this->resolveVirtualUrl( $srcPath );
205 if ( !$this->validateFilename( $dstRel ) ) {
206 throw new MWException( 'Validation error in $dstRel' );
208 if ( !$this->validateFilename( $archiveRel ) ) {
209 throw new MWException( 'Validation error in $archiveRel' );
211 $dstPath = "{$this->directory}/$dstRel";
212 $archivePath = "{$this->directory}/$archiveRel";
214 $dstDir = dirname( $dstPath );
215 if ( !is_dir( $dstDir ) ) wfMkdirParents( $dstDir );
217 // Check if the source is missing before we attempt to move the dest to archive
218 if ( !is_file( $srcPath ) ) {
219 return new WikiErrorMsg( 'filenotfound', wfEscapeWikiText( $srcPath ) );
222 if( is_file( $dstPath ) ) {
223 $archiveDir = dirname( $archivePath );
224 if ( !is_dir( $archiveDir ) ) wfMkdirParents( $archiveDir );
225 wfSuppressWarnings();
226 $success = rename( $dstPath, $archivePath );
230 return new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $dstPath ),
231 wfEscapeWikiText( $archivePath ) );
233 else wfDebug(__METHOD__
.": moved file $dstPath to $archivePath\n");
234 $status = 'archived';
241 wfSuppressWarnings();
242 if ( $flags & self
::DELETE_SOURCE
) {
243 if ( !rename( $srcPath, $dstPath ) ) {
244 $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
245 wfEscapeWikiText( $dstPath ) );
248 if ( !copy( $srcPath, $dstPath ) ) {
249 $error = new WikiErrorMsg( 'filerenameerror', wfEscapeWikiText( $srcPath ),
250 wfEscapeWikiText( $dstPath ) );
258 wfDebug(__METHOD__
.": wrote tempfile $srcPath to $dstPath\n");
261 chmod( $dstPath, 0644 );
266 * Get a relative path including trailing slash, e.g. f/fa/
267 * If the repo is not hashed, returns an empty string
269 function getHashPath( $name ) {
270 return FileRepo
::getHashPathForLevel( $name, $this->hashLevels
);
274 * Call a callback function for every file in the repository.
275 * Uses the filesystem even in child classes.
277 function enumFilesInFS( $callback ) {
278 $numDirs = 1 << ( $this->hashLevels
* 4 );
279 for ( $flatIndex = 0; $flatIndex < $numDirs; $flatIndex++
) {
280 $hexString = sprintf( "%0{$this->hashLevels}x", $flatIndex );
281 $path = $this->directory
;
282 for ( $hexPos = 0; $hexPos < $this->hashLevels
; $hexPos++
) {
283 $path .= '/' . substr( $hexString, 0, $hexPos +
1 );
285 if ( !file_exists( $path ) ||
!is_dir( $path ) ) {
288 $dir = opendir( $path );
289 while ( false !== ( $name = readdir( $dir ) ) ) {
290 call_user_func( $callback, $path . '/' . $name );
296 * Call a callback function for every file in the repository
297 * May use either the database or the filesystem
299 function enumFiles( $callback ) {
300 $this->enumFilesInFS( $callback );
304 * Get properties of a file with a given virtual URL
305 * The virtual URL must refer to this repo
307 function getFileProps( $virtualUrl ) {
308 $path = $this->resolveVirtualUrl( $virtualUrl );
309 return File
::getPropsFromPath( $path );