5 * @author Markus Glaser
7 * @author Hallo Welt! - Medienwerkstatt GmbH for Microsoft Corp.
11 * Copied and modified from Swift FileBackend:
13 * Class for a Windows Azure Blob Storage based file backend.
14 * Status messages should avoid mentioning the Azure account name
15 * Likewise, error suppression should be used to avoid path disclosure.
17 * This requires the PHPAzure library to be present,
18 * which is available at http://phpazure.codeplex.com/.
19 * All of the library classes must be registed in $wgAutoloadClasses.
20 * You may use the WindowsAzureSDK MediaWiki extension to fulfill this
23 * @ingroup FileBackend
25 class WindowsAzureFileBackend
extends FileBackend
{
27 function doStore( array $p ) {
28 return $this->doStoreInternal( $p );
31 function doCopy( array $p ) {
32 return $this->doCopyInternal( $p );
35 function doDelete( array $p ) {
36 return $this->doDeleteInternal( $p );
39 function doConcatenate( array $p ) {
40 return $this->dodoConcatenateInternal( $p );
43 function doCreate( array $p ) {
44 return $this->doCreateInternal( $p );
48 * @see FileBackend::move()
50 protected function doMove( array $params ) {
51 // Copy source to dest
52 // TODO: Remove backend. I assume, this function does not need to be overridden.
53 $status = $this->backend
->copy( $params );
54 if ( !$status->isOK() ) {
57 // Delete source (only fails due to races or medium going down)
58 // TODO: Remoce backend
59 $status->merge( $this->backend
->delete( array( 'src' => $params['src'] ) ) );
60 $status->setResult( true, $status->value
); // ignore delete() errors
64 /** @var Microsoft_WindowsAzure_Storage_Blob */
65 protected $storageClient = null;
67 /** @var Array Map of container names to Azure container names */
68 protected $containerPaths = array();
71 * @see FileBackend::__construct()
72 * Additional $config params include:
73 * azureHost : Windows Azure server URL
74 * azureAccount : Windows Azure user used by MediaWiki
75 * azureKey : Authentication key for the above user (used to get sessions)
76 * //azureContainer : Identifier of the container. (Optional. If not provided wikiId will be used as container name)
77 * containerPaths : Map of container names to Azure container names
79 public function __construct( array $config ) {
80 parent
::__construct( $config );
81 $this->storageClient
= new Microsoft_WindowsAzure_Storage_Blob(
83 $config['azureAccount'],
87 $this->containerPaths
= (array)$config['containerPaths'];
91 * @see FileBackend::resolveContainerPath()
93 protected function resolveContainerPath( $container, $relStoragePath ) {
94 //Azure container naming conventions; http://msdn.microsoft.com/en-us/library/dd135715.aspx
96 if ( strlen( urlencode( $relStoragePath ) ) > 1024 ) {
99 // TODO: Should storagepath not be urlencoded?
100 return $relStoragePath;
104 * @see FileBackend::doStoreInternal()
106 function doStoreInternal( array $params ) {
107 $status = Status
::newGood();
108 // TODO: Use more telling names
109 list( $c, $dir ) = $this->resolveStoragePath( $params['dst'] );
111 $result = $this->storageClient
->putBlob( $c, $dir, $params['src']);
113 catch ( Exception
$e ) {
114 // TODO: Read exception. Are there different ones?
115 $status->fatal( 'backend-fail-put' );
117 //error_log( __METHOD__.'::putBlob - result: '.print_r( $result, true ) );
122 * @see FileBackend::doCopyInternal()
124 function doCopyInternal( array $params ) {
125 $status = Status
::newGood();
126 list( $srcContainer, $srcDir ) = $this->resolveStoragePath( $params['src'] );
127 list( $dstContainer, $dstDir ) = $this->resolveStoragePath( $params['dst'] );
128 // TODO: check for null
130 $result = $this->storageClient
->copyBlob( $srcContainer, $srcDir, $dstContainer, $dstDir);
132 catch ( Exception
$e ) {
133 $status->fatal( 'backend-fail-copy', $e->getMessage() );
135 //error_log( __METHOD__.'::copyBlob - result: '.print_r( $result, true ) );
140 * @see FileBackend::doDeleteInternal()
142 function doDeleteInternal( array $params ) {
143 $status = Status
::newGood();
145 list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] );
146 if ( $srcRel === null ) {
147 $status->fatal( 'backend-fail-invalidpath', $params['src'] );
151 // (a) Check the source container
152 try { //TODO: Unnecessary --> remove
153 $container = $this->storageClient
->getContainer( $srcCont );
155 catch ( Exception
$e ) {
156 // TODO: remove error_log
157 error_log( __METHOD__
.':'.__LINE__
.' '.$e->getMessage() );
158 $status->fatal( 'backend-fail-internal' );
162 // (b) Actually delete the object
164 $this->storageClient
->deleteBlob( $srcCont, $srcRel );
166 catch ( Exception
$e ) {
167 error_log( __METHOD__
.':'.__LINE__
.' '.$e->getMessage() );
168 $status->fatal( 'backend-fail-internal' );
175 * @see FileBackend::doCreateInternal()
177 function doCreateInternal( array $params ) {
178 $status = Status
::newGood();
180 list( $dstCont, $dstRel ) = $this->resolveStoragePath( $params['dst'] );
181 if ( $dstRel === null ) {
182 $status->fatal( 'backend-fail-invalidpath', $params['dst'] );
186 // (a) Check if the destination object already exists
187 $blobExists = $this->storageClient
->blobExists( $dstCont, $dstRel );
188 if ( $blobExists && empty( $params['overwriteDest'] ) ) { //Blob exists _and_ should not be overridden
189 $status->fatal( 'backend-fail-alreadyexists', $params['dst'] );
193 // (b) Actually create the object
195 // TODO: how do I know the container exists? Should we call prepare?
196 $this->storageClient
->putBlobData( $dstCont, $dstRel, $params['content'] );
198 catch ( Exception
$e ) {
199 error_log( __METHOD__
.':'.__LINE__
.' '.$e->getMessage() );
200 $status->fatal( 'backend-fail-internal' );
207 * @see FileBackend::prepare()
209 function prepare( array $params ) {
210 $status = Status
::newGood();
212 list( $c, $dir ) = $this->resolveStoragePath( $params['dir'] );
213 if ( $dir === null ) {
214 $status->fatal( 'backend-fail-invalidpath', $params['dir'] );
215 return $status; // invalid storage path
218 $this->storageClient
->createContainerIfNotExists( $c );
219 $this->storageClient
->setContainerAcl( $c, Microsoft_WindowsAzure_Storage_Blob
::ACL_PUBLIC
);//TODO: Really set public?
221 //TODO: check if readable and writeable
222 //$container = $this->storageClient->getContainer( $c );
223 //$status->fatal( 'directoryreadonlyerror', $params['dir'] );
224 //$status->fatal( 'directorynotreadableerror', $params['dir'] );
226 catch (Exception
$e ) {
227 $status->fatal( 'directorycreateerror', $params['dir'] );
234 * @see FileBackend::resolveContainerName()
236 protected function resolveContainerName( $container ) {
237 //Azure container naming conventions; http://msdn.microsoft.com/en-us/library/dd135715.aspx
238 $container = strtolower($container);
239 $container = preg_replace( '#[^a-z0-9\-]#', '', $container );
240 // TODO: -test und test- geht auch nicht
241 $container = preg_replace( '#-{2,}#', '-', $container );
247 * @see FileBackend::secure()
249 function secure( array $params ) {
250 $status = Status
::newGood();
251 // @TODO: restrict container from $this->swiftProxyUser
252 return $status; // badgers? We don't need no steenking badgers!
256 * @see FileBackend::fileExists()
258 function fileExists( array $params ) {
259 list( $c, $dir ) = $this->resolveStoragePath( $params['src'] );
260 // TODO: null? Telling names
261 $exists = $this->storageClient
->blobExists( $c, $dir );
262 //error_log( __METHOD__.'::blobExists - result: '.$exists );
267 * @see FileBackend::getFileTimestamp()
269 function getFileTimestamp( array $params ) {
270 list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] );
271 if ( $srcRel === null ) {
272 return false; // invalid storage path
277 //TODO Maybe use getBlobData()?
278 $blob = $this->storageClient
->getBlobInstance( $srcCont, $srcRel );
279 $timestamp = wfTimestamp( TS_MW
, $blob->lastmodified
); //TODO: Timezone?
280 } catch ( Exception
$e ) { // some other exception?
281 error_log( __METHOD__
.':'.__LINE__
.' '.$e->getMessage() );
287 * @see FileBackend::getFileList()
289 function getFileList( array $params ) {
291 list( $c, $dir ) = $this->resolveStoragePath( $params['dir'] );
293 if ( $dir === null ) {
294 $blobs = $this->storageClient
->listBlobs($c);
297 $blobs = $this->storageClient
->listBlobs( $c, $dir );//TODO:Check if $dir really is a startsequence of the blob name
299 foreach( $blobs as $blob ) {
300 $files[] = $blob->name
;
303 catch( Exception
$e ) {
304 error_log( __METHOD__
.':'.__LINE__
.' '.$e->getMessage() );
308 // if there are no files matching the prefix, return empty array
313 * @see FileBackend::getLocalCopy()
315 function getLocalCopy( array $params ) {
316 list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] );
317 if ( $srcRel === null ) {
321 // Get source file extension
322 $ext = FileBackend
::extensionFromPath( $srcRel );
323 // Create a new temporary file...
324 // TODO: Caution: tempfile should not write a local file.
325 $tmpFile = TempFSFile
::factory( wfBaseName( $srcRel ) . '_', $ext );
329 $tmpPath = $tmpFile->getPath();
332 $this->storageClient
->getBlob( $srcCont, $srcRel, $tmpPath );
334 catch ( Exception
$e ) {
335 error_log( __METHOD__
.':'.__LINE__
.' '.$e->getMessage() );