Index: trunk/phase3/includes/filerepo/backend/SwiftFileBackend.php |
— | — | @@ -9,9 +9,9 @@ |
10 | 10 | /** |
11 | 11 | * Class for an OpenStack Swift based file backend. |
12 | 12 | * |
13 | | - * This requires that the php-cloudfiles library is present, |
14 | | - * which is available at https://github.com/rackspace/php-cloudfiles. |
15 | | - * All of the library classes must be registed in $wgAutoloadClasses. |
| 13 | + * This requires the SwiftCloudFiles MediaWiki extension, which includes |
| 14 | + * the php-cloudfiles library (https://github.com/rackspace/php-cloudfiles). |
| 15 | + * php-cloudfiles requires the curl, fileinfo, and mb_string PHP extensions. |
16 | 16 | * |
17 | 17 | * Status messages should avoid mentioning the Swift account name |
18 | 18 | * Likewise, error suppression should be used to avoid path disclosure. |
— | — | @@ -22,35 +22,40 @@ |
23 | 23 | class SwiftFileBackend extends FileBackend { |
24 | 24 | /** @var CF_Authentication */ |
25 | 25 | protected $auth; // Swift authentication handler |
| 26 | + protected $authTTL; // integer seconds |
| 27 | + protected $swiftAnonUser; // string; username to handle unauthenticated requests |
| 28 | + protected $maxContCacheSize = 20; // integer; max containers with entries |
26 | 29 | |
27 | 30 | /** @var CF_Connection */ |
28 | 31 | protected $conn; // Swift connection handle |
29 | | - protected $connTTL = 120; // integer seconds |
30 | 32 | protected $connStarted = 0; // integer UNIX timestamp |
31 | 33 | protected $connContainers = array(); // container object cache |
32 | 34 | |
33 | | - protected $swiftProxyUser; // string |
34 | | - |
35 | 35 | /** |
36 | 36 | * @see FileBackend::__construct() |
37 | 37 | * Additional $config params include: |
38 | 38 | * swiftAuthUrl : Swift authentication server URL |
39 | | - * swiftUser : Swift user used by MediaWiki |
| 39 | + * swiftUser : Swift user used by MediaWiki (account:username) |
40 | 40 | * swiftKey : Swift authentication key for the above user |
41 | | - * swiftProxyUser : Swift user used for end-user hits to proxy server |
| 41 | + * swiftAuthTTL : Swift authentication TTL (seconds) |
| 42 | + * swiftAnonUser : Swift user used for end-user requests (account:username) |
42 | 43 | * shardViaHashLevels : Map of container names to the number of hash levels |
43 | 44 | */ |
44 | 45 | public function __construct( array $config ) { |
45 | 46 | parent::__construct( $config ); |
46 | 47 | // Required settings |
47 | 48 | $this->auth = new CF_Authentication( |
48 | | - $config['swiftUser'], $config['swiftKey'], null, $config['swiftAuthUrl'] ); |
| 49 | + $config['swiftUser'], |
| 50 | + $config['swiftKey'], |
| 51 | + null, // account; unused |
| 52 | + $config['swiftAuthUrl'] |
| 53 | + ); |
49 | 54 | // Optional settings |
50 | | - $this->connTTL = isset( $config['connTTL'] ) |
51 | | - ? $config['connTTL'] |
52 | | - : 60; // some sane number |
53 | | - $this->swiftProxyUser = isset( $config['swiftProxyUser'] ) |
54 | | - ? $config['swiftProxyUser'] |
| 55 | + $this->authTTL = isset( $config['swiftAuthTTL'] ) |
| 56 | + ? $config['authTTL'] |
| 57 | + : 120; // some sane number |
| 58 | + $this->swiftAnonUser = isset( $config['swiftAnonUser'] ) |
| 59 | + ? $config['swiftAnonUser'] |
55 | 60 | : ''; |
56 | 61 | $this->shardViaHashLevels = isset( $config['shardViaHashLevels'] ) |
57 | 62 | ? $config['shardViaHashLevels'] |
— | — | @@ -73,15 +78,23 @@ |
74 | 79 | protected function doCreateInternal( array $params ) { |
75 | 80 | $status = Status::newGood(); |
76 | 81 | |
77 | | - list( $dstCont, $destRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
78 | | - if ( $destRel === null ) { |
| 82 | + list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
| 83 | + if ( $dstRel === null ) { |
79 | 84 | $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
80 | 85 | return $status; |
81 | 86 | } |
82 | 87 | |
83 | | - // (a) Check the destination container |
| 88 | + // (a) Check the destination container and object |
84 | 89 | try { |
85 | 90 | $dContObj = $this->getContainer( $dstCont ); |
| 91 | + if ( empty( $params['overwriteDest'] ) ) { |
| 92 | + $destObj = $dContObj->create_object( $dstRel ); |
| 93 | + // Check if the object already exists (fields populated) |
| 94 | + if ( $destObj->last_modified ) { |
| 95 | + $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
| 96 | + return $status; |
| 97 | + } |
| 98 | + } |
86 | 99 | } catch ( NoSuchContainerException $e ) { |
87 | 100 | $status->fatal( 'backend-fail-create', $params['dst'] ); |
88 | 101 | return $status; |
— | — | @@ -94,31 +107,14 @@ |
95 | 108 | return $status; |
96 | 109 | } |
97 | 110 | |
98 | | - // (b) Check if the destination object already exists |
99 | | - try { |
100 | | - $dContObj->get_object( $destRel ); // throws NoSuchObjectException |
101 | | - // NoSuchObjectException not thrown: file must exist |
102 | | - if ( empty( $params['overwriteDest'] ) ) { |
103 | | - $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
104 | | - return $status; |
105 | | - } |
106 | | - } catch ( NoSuchObjectException $e ) { |
107 | | - // NoSuchObjectException thrown: file does not exist |
108 | | - } catch ( InvalidResponseException $e ) { |
109 | | - $status->fatal( 'backend-fail-connect', $this->name ); |
110 | | - return $status; |
111 | | - } catch ( Exception $e ) { // some other exception? |
112 | | - $status->fatal( 'backend-fail-internal', $this->name ); |
113 | | - $this->logException( $e, __METHOD__, $params ); |
114 | | - return $status; |
115 | | - } |
116 | | - |
117 | | - // (c) Get a SHA-1 hash of the object |
| 111 | + // (b) Get a SHA-1 hash of the object |
118 | 112 | $sha1Hash = wfBaseConvert( sha1( $params['content'] ), 16, 36, 31 ); |
119 | 113 | |
120 | | - // (d) Actually create the object |
| 114 | + // (c) Actually create the object |
121 | 115 | try { |
122 | | - $obj = $dContObj->create_object( $destRel ); |
| 116 | + // Create a fresh CF_Object with no fields preloaded. |
| 117 | + // We don't want to preserve headers, metadata, and such. |
| 118 | + $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD |
123 | 119 | // Note: metadata keys stored as [Upper case char][[Lower case char]...] |
124 | 120 | $obj->metadata = array( 'Sha1base36' => $sha1Hash ); |
125 | 121 | $obj->write( $params['content'] ); |
— | — | @@ -140,15 +136,23 @@ |
141 | 137 | protected function doStoreInternal( array $params ) { |
142 | 138 | $status = Status::newGood(); |
143 | 139 | |
144 | | - list( $dstCont, $destRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
145 | | - if ( $destRel === null ) { |
| 140 | + list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
| 141 | + if ( $dstRel === null ) { |
146 | 142 | $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
147 | 143 | return $status; |
148 | 144 | } |
149 | 145 | |
150 | | - // (a) Check the destination container |
| 146 | + // (a) Check the destination container and object |
151 | 147 | try { |
152 | 148 | $dContObj = $this->getContainer( $dstCont ); |
| 149 | + if ( empty( $params['overwriteDest'] ) ) { |
| 150 | + $destObj = $dContObj->create_object( $dstRel ); |
| 151 | + // Check if the object already exists (fields populated) |
| 152 | + if ( $destObj->last_modified ) { |
| 153 | + $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
| 154 | + return $status; |
| 155 | + } |
| 156 | + } |
153 | 157 | } catch ( NoSuchContainerException $e ) { |
154 | 158 | $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
155 | 159 | return $status; |
— | — | @@ -161,26 +165,7 @@ |
162 | 166 | return $status; |
163 | 167 | } |
164 | 168 | |
165 | | - // (b) Check if the destination object already exists |
166 | | - try { |
167 | | - $dContObj->get_object( $destRel ); // throws NoSuchObjectException |
168 | | - // NoSuchObjectException not thrown: file must exist |
169 | | - if ( empty( $params['overwriteDest'] ) ) { |
170 | | - $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
171 | | - return $status; |
172 | | - } |
173 | | - } catch ( NoSuchObjectException $e ) { |
174 | | - // NoSuchObjectException thrown: file does not exist |
175 | | - } catch ( InvalidResponseException $e ) { |
176 | | - $status->fatal( 'backend-fail-connect', $this->name ); |
177 | | - return $status; |
178 | | - } catch ( Exception $e ) { // some other exception? |
179 | | - $status->fatal( 'backend-fail-internal', $this->name ); |
180 | | - $this->logException( $e, __METHOD__, $params ); |
181 | | - return $status; |
182 | | - } |
183 | | - |
184 | | - // (c) Get a SHA-1 hash of the object |
| 169 | + // (b) Get a SHA-1 hash of the object |
185 | 170 | $sha1Hash = sha1_file( $params['src'] ); |
186 | 171 | if ( $sha1Hash === false ) { // source doesn't exist? |
187 | 172 | $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
— | — | @@ -188,9 +173,11 @@ |
189 | 174 | } |
190 | 175 | $sha1Hash = wfBaseConvert( $sha1Hash, 16, 36, 31 ); |
191 | 176 | |
192 | | - // (d) Actually store the object |
| 177 | + // (c) Actually store the object |
193 | 178 | try { |
194 | | - $obj = $dContObj->create_object( $destRel ); |
| 179 | + // Create a fresh CF_Object with no fields preloaded. |
| 180 | + // We don't want to preserve headers, metadata, and such. |
| 181 | + $obj = new CF_Object( $dContObj, $dstRel, false, false ); // skip HEAD |
195 | 182 | // Note: metadata keys stored as [Upper case char][[Lower case char]...] |
196 | 183 | $obj->metadata = array( 'Sha1base36' => $sha1Hash ); |
197 | 184 | $obj->load_from_filename( $params['src'], True ); // calls $obj->write() |
— | — | @@ -220,16 +207,24 @@ |
221 | 208 | return $status; |
222 | 209 | } |
223 | 210 | |
224 | | - list( $dstCont, $destRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
225 | | - if ( $destRel === null ) { |
| 211 | + list( $dstCont, $dstRel ) = $this->resolveStoragePathReal( $params['dst'] ); |
| 212 | + if ( $dstRel === null ) { |
226 | 213 | $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
227 | 214 | return $status; |
228 | 215 | } |
229 | 216 | |
230 | | - // (a) Check the source and destination containers |
| 217 | + // (a) Check the source/destination containers and destination object |
231 | 218 | try { |
232 | 219 | $sContObj = $this->getContainer( $srcCont ); |
233 | 220 | $dContObj = $this->getContainer( $dstCont ); |
| 221 | + if ( empty( $params['overwriteDest'] ) ) { |
| 222 | + $destObj = $dContObj->create_object( $dstRel ); |
| 223 | + // Check if the object already exists (fields populated) |
| 224 | + if ( $destObj->last_modified ) { |
| 225 | + $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
| 226 | + return $status; |
| 227 | + } |
| 228 | + } |
234 | 229 | } catch ( NoSuchContainerException $e ) { |
235 | 230 | $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
236 | 231 | return $status; |
— | — | @@ -242,28 +237,9 @@ |
243 | 238 | return $status; |
244 | 239 | } |
245 | 240 | |
246 | | - // (b) Check if the destination object already exists |
| 241 | + // (b) Actually copy the file to the destination |
247 | 242 | try { |
248 | | - $dContObj->get_object( $destRel ); // throws NoSuchObjectException |
249 | | - // NoSuchObjectException not thrown: file must exist |
250 | | - if ( empty( $params['overwriteDest'] ) ) { |
251 | | - $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
252 | | - return $status; |
253 | | - } |
254 | | - } catch ( NoSuchObjectException $e ) { |
255 | | - // NoSuchObjectException thrown: file does not exist |
256 | | - } catch ( InvalidResponseException $e ) { |
257 | | - $status->fatal( 'backend-fail-connect', $this->name ); |
258 | | - return $status; |
259 | | - } catch ( Exception $e ) { // some other exception? |
260 | | - $status->fatal( 'backend-fail-internal', $this->name ); |
261 | | - $this->logException( $e, __METHOD__, $params ); |
262 | | - return $status; |
263 | | - } |
264 | | - |
265 | | - // (c) Actually copy the file to the destination |
266 | | - try { |
267 | | - $sContObj->copy_object_to( $srcRel, $dContObj, $destRel ); |
| 243 | + $sContObj->copy_object_to( $srcRel, $dContObj, $dstRel ); |
268 | 244 | } catch ( NoSuchObjectException $e ) { // source object does not exist |
269 | 245 | $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
270 | 246 | } catch ( InvalidResponseException $e ) { |
— | — | @@ -288,24 +264,11 @@ |
289 | 265 | return $status; |
290 | 266 | } |
291 | 267 | |
292 | | - // (a) Check the source container |
293 | 268 | try { |
294 | 269 | $sContObj = $this->getContainer( $srcCont ); |
| 270 | + $sContObj->delete_object( $srcRel ); |
295 | 271 | } catch ( NoSuchContainerException $e ) { |
296 | 272 | $status->fatal( 'backend-fail-delete', $params['src'] ); |
297 | | - return $status; |
298 | | - } catch ( InvalidResponseException $e ) { |
299 | | - $status->fatal( 'backend-fail-connect', $this->name ); |
300 | | - return $status; |
301 | | - } catch ( Exception $e ) { // some other exception? |
302 | | - $status->fatal( 'backend-fail-internal', $this->name ); |
303 | | - $this->logException( $e, __METHOD__, $params ); |
304 | | - return $status; |
305 | | - } |
306 | | - |
307 | | - // (b) Actually delete the object |
308 | | - try { |
309 | | - $sContObj->delete_object( $srcRel ); |
310 | 273 | } catch ( NoSuchObjectException $e ) { |
311 | 274 | if ( empty( $params['ignoreMissingSource'] ) ) { |
312 | 275 | $status->fatal( 'backend-fail-delete', $params['src'] ); |
— | — | @@ -326,15 +289,42 @@ |
327 | 290 | protected function doPrepareInternal( $fullCont, $dir, array $params ) { |
328 | 291 | $status = Status::newGood(); |
329 | 292 | |
| 293 | + // (a) Check if container already exists |
330 | 294 | try { |
331 | | - $this->createContainer( $fullCont ); |
| 295 | + $contObj = $this->getContainer( $fullCont ); |
| 296 | + // NoSuchContainerException not thrown: container must exist |
| 297 | + return $status; // already exists |
| 298 | + } catch ( NoSuchContainerException $e ) { |
| 299 | + // NoSuchContainerException thrown: container does not exist |
332 | 300 | } catch ( InvalidResponseException $e ) { |
333 | 301 | $status->fatal( 'backend-fail-connect', $this->name ); |
| 302 | + return $status; |
334 | 303 | } catch ( Exception $e ) { // some other exception? |
335 | 304 | $status->fatal( 'backend-fail-internal', $this->name ); |
336 | 305 | $this->logException( $e, __METHOD__, $params ); |
| 306 | + return $status; |
337 | 307 | } |
338 | 308 | |
| 309 | + // (b) Create container as needed |
| 310 | + try { |
| 311 | + $contObj = $this->createContainer( $fullCont ); |
| 312 | + if ( $this->swiftAnonUser != '' ) { |
| 313 | + // Make container public to end-users... |
| 314 | + $status->merge( $this->setContainerAccess( |
| 315 | + $contObj, |
| 316 | + array( $this->auth->username, $this->swiftAnonUser ), // read |
| 317 | + array( $this->auth->username ) // write |
| 318 | + ) ); |
| 319 | + } |
| 320 | + } catch ( InvalidResponseException $e ) { |
| 321 | + $status->fatal( 'backend-fail-connect', $this->name ); |
| 322 | + return $status; |
| 323 | + } catch ( Exception $e ) { // some other exception? |
| 324 | + $status->fatal( 'backend-fail-internal', $this->name ); |
| 325 | + $this->logException( $e, __METHOD__, $params ); |
| 326 | + return $status; |
| 327 | + } |
| 328 | + |
339 | 329 | return $status; |
340 | 330 | } |
341 | 331 | |
— | — | @@ -343,7 +333,32 @@ |
344 | 334 | */ |
345 | 335 | protected function doSecureInternal( $fullCont, $dir, array $params ) { |
346 | 336 | $status = Status::newGood(); |
347 | | - // @TODO: restrict container from $this->swiftProxyUser |
| 337 | + |
| 338 | + if ( $this->swiftAnonUser != '' ) { |
| 339 | + // Restrict container from end-users... |
| 340 | + try { |
| 341 | + // doPrepareInternal() should have been called, |
| 342 | + // so the Swift container should already exist... |
| 343 | + $contObj = $this->getContainer( $fullCont ); // normally a cache hit |
| 344 | + // NoSuchContainerException not thrown: container must exist |
| 345 | + if ( !isset( $contObj->mw_wasSecured ) ) { |
| 346 | + $status->merge( $this->setContainerAccess( |
| 347 | + $contObj, |
| 348 | + array( $this->auth->username ), // read |
| 349 | + array( $this->auth->username ) // write |
| 350 | + ) ); |
| 351 | + // @TODO: when php-cloudfiles supports container |
| 352 | + // metadata, we can make use of that to avoid RTTs |
| 353 | + $contObj->mw_wasSecured = true; // avoid useless RTTs |
| 354 | + } |
| 355 | + } catch ( InvalidResponseException $e ) { |
| 356 | + $status->fatal( 'backend-fail-connect', $this->name ); |
| 357 | + } catch ( Exception $e ) { // some other exception? |
| 358 | + $status->fatal( 'backend-fail-internal', $this->name ); |
| 359 | + $this->logException( $e, __METHOD__, $params ); |
| 360 | + } |
| 361 | + } |
| 362 | + |
348 | 363 | return $status; |
349 | 364 | } |
350 | 365 | |
— | — | @@ -353,6 +368,11 @@ |
354 | 369 | protected function doCleanInternal( $fullCont, $dir, array $params ) { |
355 | 370 | $status = Status::newGood(); |
356 | 371 | |
| 372 | + // Only containers themselves can be removed, all else is virtual |
| 373 | + if ( $dir != '' ) { |
| 374 | + return $status; // nothing to do |
| 375 | + } |
| 376 | + |
357 | 377 | // (a) Check the container |
358 | 378 | try { |
359 | 379 | $contObj = $this->getContainer( $fullCont, true ); |
— | — | @@ -397,16 +417,15 @@ |
398 | 418 | |
399 | 419 | $stat = false; |
400 | 420 | try { |
401 | | - $container = $this->getContainer( $srcCont ); |
402 | | - // @TODO: handle 'latest' param as "X-Newest: true" |
403 | | - $obj = $container->get_object( $srcRel ); |
| 421 | + $contObj = $this->getContainer( $srcCont ); |
| 422 | + $srcObj = $contObj->get_object( $srcRel, $this->headersFromParams( $params ) ); |
404 | 423 | // Convert dates like "Tue, 03 Jan 2012 22:01:04 GMT" to TS_MW |
405 | | - $date = DateTime::createFromFormat( 'D, d F Y G:i:s e', $obj->last_modified ); |
| 424 | + $date = DateTime::createFromFormat( 'D, d F Y G:i:s e', $srcObj->last_modified ); |
406 | 425 | if ( $date ) { |
407 | 426 | $stat = array( |
408 | 427 | 'mtime' => $date->format( 'YmdHis' ), |
409 | | - 'size' => $obj->content_length, |
410 | | - 'sha1' => $obj->metadata['Sha1base36'] |
| 428 | + 'size' => $srcObj->content_length, |
| 429 | + 'sha1' => $srcObj->metadata['Sha1base36'] |
411 | 430 | ); |
412 | 431 | } else { // exception will be caught below |
413 | 432 | throw new Exception( "Could not parse date for object {$srcRel}" ); |
— | — | @@ -465,6 +484,7 @@ |
466 | 485 | */ |
467 | 486 | public function getFileListPageInternal( $fullCont, $dir, $after, $limit ) { |
468 | 487 | $files = array(); |
| 488 | + |
469 | 489 | try { |
470 | 490 | $container = $this->getContainer( $fullCont ); |
471 | 491 | $files = $container->list_objects( $limit, $after, "{$dir}/" ); |
— | — | @@ -517,7 +537,8 @@ |
518 | 538 | |
519 | 539 | try { |
520 | 540 | $output = fopen( 'php://output', 'w' ); |
521 | | - $obj = new CF_Object( $cont, $srcRel, False, False ); // skip HEAD request |
| 541 | + // FileBackend::streamFile() already checks existence |
| 542 | + $obj = new CF_Object( $cont, $srcRel, false, false ); // skip HEAD request |
522 | 543 | $obj->stream( $output, $this->headersFromParams( $params ) ); |
523 | 544 | } catch ( InvalidResponseException $e ) { // 404? connection problem? |
524 | 545 | $status->fatal( 'backend-fail-stream', $params['src'] ); |
— | — | @@ -538,23 +559,22 @@ |
539 | 560 | return null; |
540 | 561 | } |
541 | 562 | |
542 | | - // Get source file extension |
543 | | - $ext = FileBackend::extensionFromPath( $srcRel ); |
544 | | - // Create a new temporary file... |
545 | | - $tmpFile = TempFSFile::factory( wfBaseName( $srcRel ) . '_', $ext ); |
546 | | - if ( !$tmpFile ) { |
547 | | - return null; |
548 | | - } |
549 | | - |
| 563 | + $tmpFile = null; |
550 | 564 | try { |
551 | 565 | $cont = $this->getContainer( $srcCont ); |
552 | 566 | $obj = $cont->get_object( $srcRel ); |
553 | | - $handle = fopen( $tmpFile->getPath(), 'w' ); |
554 | | - if ( $handle ) { |
555 | | - $obj->stream( $handle, $this->headersFromParams( $params ) ); |
556 | | - fclose( $handle ); |
557 | | - } else { |
558 | | - $tmpFile = null; // couldn't open temp file |
| 567 | + // Get source file extension |
| 568 | + $ext = FileBackend::extensionFromPath( $srcRel ); |
| 569 | + // Create a new temporary file... |
| 570 | + $tmpFile = TempFSFile::factory( wfBaseName( $srcRel ) . '_', $ext ); |
| 571 | + if ( $tmpFile ) { |
| 572 | + $handle = fopen( $tmpFile->getPath(), 'w' ); |
| 573 | + if ( $handle ) { |
| 574 | + $obj->stream( $handle, $this->headersFromParams( $params ) ); |
| 575 | + fclose( $handle ); |
| 576 | + } else { |
| 577 | + $tmpFile = null; // couldn't open temp file |
| 578 | + } |
559 | 579 | } |
560 | 580 | } catch ( NoSuchContainerException $e ) { |
561 | 581 | $tmpFile = null; |
— | — | @@ -587,6 +607,30 @@ |
588 | 608 | } |
589 | 609 | |
590 | 610 | /** |
| 611 | + * Set read/write permissions for a Swift container |
| 612 | + * |
| 613 | + * @param $contObj CF_Container Swift container |
| 614 | + * @param $readGrps Array Swift users who can read (account:user) |
| 615 | + * @param $writeGrps Array Swift users who can write (account:user) |
| 616 | + * @return Status |
| 617 | + */ |
| 618 | + protected function setContainerAccess( |
| 619 | + CF_Container $contObj, array $readGrps, array $writeGrps |
| 620 | + ) { |
| 621 | + $creds = $contObj->cfs_auth->export_credentials(); |
| 622 | + |
| 623 | + $url = $creds['storage_url'] . '/' . rawurlencode( $contObj->name ); |
| 624 | + |
| 625 | + // Note: 10 second timeout consistent with php-cloudfiles |
| 626 | + $req = new CurlHttpRequest( $url, array( 'method' => 'POST', 'timeout' => 10 ) ); |
| 627 | + $req->setHeader( 'X-Auth-Token', $creds['auth_token'] ); |
| 628 | + $req->setHeader( 'X-Container-Read', implode( ',', $readGrps ) ); |
| 629 | + $req->setHeader( 'X-Container-Write', implode( ',', $writeGrps ) ); |
| 630 | + |
| 631 | + return $req->execute(); // should return 204 |
| 632 | + } |
| 633 | + |
| 634 | + /** |
591 | 635 | * Get a connection to the Swift proxy |
592 | 636 | * |
593 | 637 | * @return CF_Connection|false |
— | — | @@ -596,9 +640,13 @@ |
597 | 641 | if ( $this->conn === false ) { |
598 | 642 | return false; // failed last attempt |
599 | 643 | } |
600 | | - // Authenticate with proxy and get a session key. |
601 | | - // Session keys expire after a while, so we renew them periodically. |
602 | | - if ( $this->conn === null || ( time() - $this->connStarted ) > $this->connTTL ) { |
| 644 | + // Session keys expire after a while, so we renew them periodically |
| 645 | + if ( $this->conn && ( time() - $this->connStarted ) > $this->authTTL ) { |
| 646 | + $this->conn->close(); // close active cURL connections |
| 647 | + $this->conn = null; |
| 648 | + } |
| 649 | + // Authenticate with proxy and get a session key... |
| 650 | + if ( $this->conn === null ) { |
603 | 651 | $this->connContainers = array(); |
604 | 652 | try { |
605 | 653 | $this->auth->authenticate(); |
— | — | @@ -631,7 +679,12 @@ |
632 | 680 | } |
633 | 681 | if ( !isset( $this->connContainers[$container] ) ) { |
634 | 682 | $contObj = $conn->get_container( $container ); |
635 | | - // Exception not thrown: container must exist |
| 683 | + // NoSuchContainerException not thrown: container must exist |
| 684 | + if ( count( $this->connContainers ) >= $this->maxContCacheSize ) { // trim cache? |
| 685 | + reset( $this->connContainers ); |
| 686 | + $key = key( $this->connContainers ); |
| 687 | + unset( $this->connContainers[$key] ); |
| 688 | + } |
636 | 689 | $this->connContainers[$container] = $contObj; // cache it |
637 | 690 | } |
638 | 691 | return $this->connContainers[$container]; |