Index: branches/FileBackend/phase3/includes/filerepo/backend/SwiftFileBackend.php |
— | — | @@ -2,6 +2,8 @@ |
3 | 3 | /** |
4 | 4 | * @file |
5 | 5 | * @ingroup FileBackend |
| 6 | + * @author Russ Nelson |
| 7 | + * @author Aaron Schulz |
6 | 8 | */ |
7 | 9 | |
8 | 10 | /** |
— | — | @@ -9,16 +11,23 @@ |
10 | 12 | * Status messages should avoid mentioning the Swift account name |
11 | 13 | * Likewise, error suppression should be used to avoid path disclosure. |
12 | 14 | * |
13 | | - * @FIXME: resuse connections with auto-connect and don't let connection |
14 | | - * exceptions bubble up from read/write operations. |
| 15 | + * This requires the php-cloudfiles library is present, |
| 16 | + * which is available at https://github.com/rackspace/php-cloudfiles. |
| 17 | + * All of the library classes must be registed in $wgAutoloadClasses. |
15 | 18 | * |
| 19 | + * @TODO: update MessagesEn for status errors. |
| 20 | + * |
16 | 21 | * @ingroup FileBackend |
17 | 22 | */ |
18 | 23 | class SwiftFileBackend extends FileBackend { |
19 | | - protected $swiftUser; // string |
20 | | - protected $swiftKey; // string |
21 | | - protected $swiftAuthUrl; // string |
| 24 | + /** @var CF_Authentication */ |
| 25 | + protected $auth; // swift authentication handler |
| 26 | + /** @var CF_Connection */ |
| 27 | + protected $conn; // swift connection handle |
| 28 | + protected $connStarted = 0; // integer UNIX timestamp |
| 29 | + |
22 | 30 | protected $swiftProxyUser; // string |
| 31 | + protected $connTTL = 60; // integer seconds |
23 | 32 | |
24 | 33 | /** |
25 | 34 | * @see FileBackend::__construct() |
— | — | @@ -28,308 +37,293 @@ |
29 | 38 | * swiftKey : Authentication key for the above user (used to get sessions) |
30 | 39 | * swiftProxyUser : Swift user used for end-user hits to proxy server |
31 | 40 | */ |
32 | | - function __construct( array $config ) { |
| 41 | + public function __construct( array $config ) { |
33 | 42 | parent::__construct( $config ); |
34 | 43 | // Required settings |
35 | | - $this->swiftUser = $config['swiftUser']; |
36 | | - $this->swiftKey = $config['swiftKey']; |
37 | | - $this->swiftAuthUrl = $config['swiftAuthUrl']; |
| 44 | + $this->auth = new CF_Authentication( |
| 45 | + $config['swiftUser'], $config['swiftKey'], $config['swiftAuthUrl'] ); |
38 | 46 | // Optional settings |
| 47 | + $this->connTTL = isset( $config['connTTL'] ) |
| 48 | + ? $config['connTTL'] |
| 49 | + : 60; // some sane number |
39 | 50 | $this->swiftProxyUser = isset( $config['swiftProxyUser'] ) |
40 | 51 | ? $config['swiftProxyUser'] |
41 | 52 | : ''; |
42 | 53 | } |
43 | 54 | |
44 | 55 | /** |
45 | | - * Get a connection to the swift proxy. |
46 | | - * |
47 | | - * @return CF_Connection |
| 56 | + * @see FileBackend::resolveContainerPath() |
48 | 57 | */ |
49 | | - protected function connect() { |
50 | | - $auth = new CF_Authentication( |
51 | | - $this->swiftUser, $this->swiftKey, NULL, $this->swiftAuthUrl ); |
52 | | - try { |
53 | | - $auth->authenticate(); |
54 | | - } catch ( AuthenticationException $e ) { |
55 | | - throw new MWException( "We can't authenticate ourselves." ); |
56 | | - # } catch (InvalidResponseException $e) { |
57 | | - # throw new MWException( __METHOD__ . "unexpected response '$e'" ); |
| 58 | + protected function resolveContainerPath( $container, $relStoragePath ) { |
| 59 | + if ( strlen( urlencode( $relStoragePath ) ) > 1024 ) { |
| 60 | + return null; // too long for swift |
58 | 61 | } |
59 | | - return new CF_Connection( $auth ); |
| 62 | + return $relStoragePath; |
60 | 63 | } |
61 | 64 | |
62 | 65 | /** |
63 | | - * Given a connection and container name, return the container. |
64 | | - * We KNOW the container should exist, so puke if it doesn't. |
65 | | - * |
66 | | - * @param $conn CF_Connection |
67 | | - * |
68 | | - * @return CF_Container |
| 66 | + * @see FileBackend::doStoreInternal() |
69 | 67 | */ |
70 | | - protected function get_container( $conn, $cont ) { |
71 | | - try { |
72 | | - return $conn->get_container( $cont ); |
73 | | - } catch ( NoSuchContainerException $e ) { |
74 | | - throw new MWException( "A container we thought existed, doesn't." ); |
75 | | - # } catch (InvalidResponseException $e) { |
76 | | - # throw new MWException( __METHOD__ . "unexpected response '$e'" ); |
77 | | - } |
78 | | - } |
| 68 | + function doStoreInternal( array $params ) { |
| 69 | + $status = Status::newGood(); |
79 | 70 | |
80 | | - /** |
81 | | - * Copy a file from one place to another place |
82 | | - * |
83 | | - * @param $srcContainer CF_Container |
84 | | - * @param $srcRel String: relative path to the source file. |
85 | | - * @param $dstContainer CF_Container |
86 | | - * @param $dstRel String: relative path to the destination. |
87 | | - */ |
88 | | - protected function swiftcopy( $srcContainer, $srcRel, $dstContainer, $dstRel ) { |
89 | | - // The destination must exist already. |
90 | | - $obj = $dstContainer->create_object( $dstRel ); |
91 | | - $obj->content_type = 'text/plain'; // overwritten by source object. |
92 | | - |
93 | | - try { |
94 | | - $obj->write( '.' ); |
95 | | - } catch ( SyntaxException $e ) { |
96 | | - throw new MWException( "Write failed: $e" ); |
97 | | - } catch ( BadContentTypeException $e ) { |
98 | | - throw new MWException( "Missing Content-Type: $e" ); |
99 | | - } catch ( MisMatchedChecksumException $e ) { |
100 | | - throw new MWException( __METHOD__ . "should not happen: '$e'" ); |
| 71 | + list( $dstCont, $destRel ) = $this->resolveStoragePath( $params['dst'] ); |
| 72 | + if ( $destRel === null ) { |
| 73 | + $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
| 74 | + return $status; |
101 | 75 | } |
102 | 76 | |
103 | | - try { |
104 | | - $obj = $dstContainer->get_object( $dstRel ); |
105 | | - } catch ( NoSuchObjectException $e ) { |
106 | | - throw new MWException( 'The object we just created does not exist: ' . |
107 | | - $dstContainer->name . "/$dstRel: $e" ); |
| 77 | + // (a) Get a swift proxy connection |
| 78 | + $conn = $this->getConnection(); |
| 79 | + if ( !$conn ) { |
| 80 | + $status->fatal( 'backend-fail-connect' ); |
| 81 | + return $status; |
108 | 82 | } |
109 | 83 | |
| 84 | + // (b) Check the destination container |
110 | 85 | try { |
111 | | - $srcObj = $srcContainer->get_object( $srcRel ); |
112 | | - } catch ( NoSuchObjectException $e ) { |
113 | | - throw new MWException( 'Source file does not exist: ' . |
114 | | - $srcContainer->name . "/$srcRel: $e" ); |
115 | | - } |
116 | | - |
117 | | - try { |
118 | | - $dstContainer->copy_object_from($srcObj,$srcContainer,$dstRel); |
119 | | - } catch ( SyntaxException $e ) { |
120 | | - throw new MWException( 'Source file does not exist: ' . |
121 | | - $srcContainer->name . "/$srcRel: $e" ); |
122 | | - } catch ( MisMatchedChecksumException $e ) { |
123 | | - throw new MWException( "Checksums do not match: $e" ); |
| 86 | + $dContObj = $conn->get_container( $conn, $dstCont ); |
| 87 | + } catch ( NoSuchContainerException $e ) { |
| 88 | + $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
| 89 | + return $status; |
| 90 | + } catch ( InvalidResponseException $e ) { |
| 91 | + $status->fatal( 'backend-fail-connect' ); |
| 92 | + return $status; |
| 93 | + } catch ( Exception $e ) { // some other exception? |
| 94 | + $status->fatal( 'backend-fail-internal' ); |
| 95 | + return $status; |
124 | 96 | } |
125 | | - } |
126 | 97 | |
127 | | - /** |
128 | | - * @see FileBackend::doStore() |
129 | | - */ |
130 | | - function doStore( array $params ) { |
131 | | - $status = Status::newGood(); |
132 | | - |
133 | | - list( $destc, $dest ) = $this->resolveStoragePath( $params['dst'] ); |
134 | | - if ( $dest === null ) { |
135 | | - $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
136 | | - return $status; |
137 | | - } |
138 | | - $conn = $this->connect(); |
139 | | - $dstc = $this->get_container( $conn, $destc ); |
| 98 | + // (c) Check if the destination object already exists |
140 | 99 | try { |
141 | | - $objd = $dstc->get_object( $dest ); |
142 | | - // if we are still here, it exists. |
| 100 | + $dContObj->get_object( $destRel ); // throws NoSuchObjectException |
| 101 | + // NoSuchObjectException not thrown: file must exist |
143 | 102 | if ( empty( $params['overwriteDest'] ) ) { |
144 | 103 | $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
145 | 104 | return $status; |
146 | 105 | } |
147 | | - $exists = true; |
148 | 106 | } catch ( NoSuchObjectException $e ) { |
149 | | - $exists = false; |
| 107 | + // NoSuchObjectException thrown: file does not exist |
| 108 | + } catch ( InvalidResponseException $e ) { |
| 109 | + $status->fatal( 'backend-fail-connect' ); |
| 110 | + return $status; |
| 111 | + } catch ( Exception $e ) { // some other exception? |
| 112 | + $status->fatal( 'backend-fail-internal' ); |
| 113 | + return $status; |
150 | 114 | } |
151 | 115 | |
| 116 | + // (d) Actually store the object |
152 | 117 | try { |
153 | | - $obj = $dstc->create_object( $dest); |
| 118 | + $obj = $dContObj->create_object( $destRel ); |
154 | 119 | $obj->load_from_filename( $params['src'], True ); |
155 | | - } catch ( SyntaxException $e ) { |
156 | | - throw new MWException( 'missing required parameters' ); |
157 | 120 | } catch ( BadContentTypeException $e ) { |
158 | | - throw new MWException( 'No Content-Type was/could be set' ); |
159 | | - } catch (InvalidResponseException $e) { |
| 121 | + $status->fatal( 'backend-fail-contenttype', $params['dst'] ); |
| 122 | + } catch ( IOException $e ) { |
160 | 123 | $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
161 | | - } catch ( IOException $e ) { |
162 | | - throw new MWException( "error opening file '$e'" ); |
| 124 | + } catch ( InvalidResponseException $e ) { |
| 125 | + $status->fatal( 'backend-fail-connect' ); |
| 126 | + } catch ( Exception $e ) { // some other exception? |
| 127 | + $status->fatal( 'backend-fail-internal' ); |
163 | 128 | } |
| 129 | + |
164 | 130 | return $status; |
165 | 131 | } |
166 | 132 | |
167 | 133 | /** |
168 | | - * @see FileBackend::doCopy() |
| 134 | + * @see FileBackend::doCopyInternal() |
169 | 135 | */ |
170 | | - function doCopy( array $params ) { |
| 136 | + function doCopyInternal( array $params ) { |
171 | 137 | $status = Status::newGood(); |
172 | 138 | |
173 | | - list( $sourcec, $source ) = $this->resolveStoragePath( $params['src'] ); |
174 | | - if ( $source === null ) { |
| 139 | + list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 140 | + if ( $srcRel === null ) { |
175 | 141 | $status->fatal( 'backend-fail-invalidpath', $params['src'] ); |
176 | 142 | return $status; |
177 | 143 | } |
178 | 144 | |
179 | | - list( $destc, $dest ) = $this->resolveStoragePath( $params['dst'] ); |
180 | | - if ( $dest === null ) { |
| 145 | + list( $dstCont, $destRel ) = $this->resolveStoragePath( $params['dst'] ); |
| 146 | + if ( $destRel === null ) { |
181 | 147 | $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
182 | 148 | return $status; |
183 | 149 | } |
184 | 150 | |
185 | | - $conn = $this->connect(); |
186 | | - $srcc = $this->get_container( $conn, $sourcec ); |
187 | | - $dstc = $this->get_container( $conn, $destc ); |
| 151 | + // (a) Get a swift proxy connection |
| 152 | + $conn = $this->getConnection(); |
| 153 | + if ( !$conn ) { |
| 154 | + $status->fatal( 'backend-fail-connect' ); |
| 155 | + return $status; |
| 156 | + } |
| 157 | + |
| 158 | + // (b) Check the source and destination containers |
188 | 159 | try { |
189 | | - $objd = $dstc->get_object( $dest ); |
190 | | - // if we are still here, it exists. |
| 160 | + $sContObj = $this->get_container( $conn, $srcCont ); |
| 161 | + $dContObj = $conn->get_container( $conn, $dstCont ); |
| 162 | + } catch ( NoSuchContainerException $e ) { |
| 163 | + $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
| 164 | + return $status; |
| 165 | + } catch ( InvalidResponseException $e ) { |
| 166 | + $status->fatal( 'backend-fail-connect' ); |
| 167 | + return $status; |
| 168 | + } catch ( Exception $e ) { // some other exception? |
| 169 | + $status->fatal( 'backend-fail-internal' ); |
| 170 | + return $status; |
| 171 | + } |
| 172 | + |
| 173 | + // (c) Check if the destination object already exists |
| 174 | + try { |
| 175 | + $dContObj->get_object( $destRel ); // throws NoSuchObjectException |
| 176 | + // NoSuchObjectException not thrown: file must exist |
191 | 177 | if ( empty( $params['overwriteDest'] ) ) { |
192 | 178 | $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
193 | 179 | return $status; |
194 | 180 | } |
195 | | - $exists = true; |
196 | 181 | } catch ( NoSuchObjectException $e ) { |
197 | | - $exists = false; |
| 182 | + // NoSuchObjectException thrown: file does not exist |
| 183 | + } catch ( InvalidResponseException $e ) { |
| 184 | + $status->fatal( 'backend-fail-connect' ); |
| 185 | + return $status; |
| 186 | + } catch ( Exception $e ) { // some other exception? |
| 187 | + $status->fatal( 'backend-fail-internal' ); |
| 188 | + return $status; |
198 | 189 | } |
| 190 | + |
| 191 | + // (d) Actually copy the file to the destination |
199 | 192 | try { |
200 | | - $this->swiftcopy( $srcc, $source, $dstc, $dest ); |
| 193 | + $this->swiftcopy( $sContObj, $srcRel, $dContObj, $destRel ); |
201 | 194 | } catch ( InvalidResponseException $e ) { |
202 | | - $status->fatal( 'backend-fail-copy', $params['src'], $params['dst'] ); |
203 | | - } |
| 195 | + $status->fatal( 'backend-fail-connect' ); |
| 196 | + } catch ( Exception $e ) { // some other exception? |
| 197 | + $status->fatal( 'backend-fail-internal' ); |
| 198 | + } |
| 199 | + |
204 | 200 | return $status; |
205 | 201 | } |
206 | 202 | |
207 | 203 | /** |
208 | | - * @see FileBackend::doDelete() |
| 204 | + * @see FileBackend::doDeleteInternal() |
209 | 205 | */ |
210 | | - function doDelete( array $params ) { |
| 206 | + function doDeleteInternal( array $params ) { |
211 | 207 | $status = Status::newGood(); |
212 | 208 | |
213 | | - list( $sourcec, $source ) = $this->resolveStoragePath( $params['src'] ); |
214 | | - if ( $source === null ) { |
| 209 | + list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 210 | + if ( $srcRel === null ) { |
215 | 211 | $status->fatal( 'backend-fail-invalidpath', $params['src'] ); |
216 | 212 | return $status; |
217 | 213 | } |
218 | 214 | |
219 | | - $conn = $this->connect(); |
220 | | - $container = $this->get_container( $conn, $sourcec ); |
| 215 | + // (a) Get a swift proxy connection |
| 216 | + $conn = $this->getConnection(); |
| 217 | + if ( !$conn ) { |
| 218 | + $status->fatal( 'backend-fail-connect' ); |
| 219 | + return $status; |
| 220 | + } |
221 | 221 | |
| 222 | + // (b) Check the source container |
222 | 223 | try { |
223 | | - $obj = $container->get_object( $source ); |
224 | | - $exists = true; |
| 224 | + $sContObj = $this->get_container( $conn, $srcCont ); |
| 225 | + } catch ( NoSuchContainerException $e ) { |
| 226 | + $status->fatal( 'backend-fail-delete', $params['src'] ); |
| 227 | + return $status; |
| 228 | + } catch ( InvalidResponseException $e ) { |
| 229 | + $status->fatal( 'backend-fail-connect' ); |
| 230 | + return $status; |
| 231 | + } catch ( Exception $e ) { // some other exception? |
| 232 | + $status->fatal( 'backend-fail-internal' ); |
| 233 | + return $status; |
| 234 | + } |
| 235 | + |
| 236 | + // (c) Actually delete the object |
| 237 | + try { |
| 238 | + $sContObj->delete_object( $srcRel ); |
225 | 239 | } catch ( NoSuchObjectException $e ) { |
226 | 240 | if ( empty( $params['ignoreMissingSource'] ) ) { |
227 | 241 | $status->fatal( 'backend-fail-delete', $params['src'] ); |
228 | 242 | } |
229 | | - $exists = false; |
| 243 | + } catch ( InvalidResponseException $e ) { |
| 244 | + $status->fatal( 'backend-fail-connect' ); |
| 245 | + } catch ( Exception $e ) { // some other exception? |
| 246 | + $status->fatal( 'backend-fail-internal' ); |
230 | 247 | } |
231 | 248 | |
232 | | - if ( $exists ) { |
233 | | - try { |
234 | | - $container->delete_object( $source ); |
235 | | - } catch ( SyntaxException $e ) { |
236 | | - throw new MWException( "Swift object name not well-formed: '$e'" ); |
237 | | - } catch ( NoSuchObjectException $e ) { |
238 | | - throw new MWException( "Swift object we are trying to delete does not exist: '$e'" ); |
239 | | - } catch ( InvalidResponseException $e ) { |
240 | | - $status->fatal( 'backend-fail-delete', $params['src'] ); |
241 | | - } |
242 | | - } |
243 | | - return $status; // do nothing; either OK or bad status |
| 249 | + return $status; |
244 | 250 | } |
245 | 251 | |
246 | 252 | /** |
247 | | - * @see FileBackend::doConcatenate() |
| 253 | + * @see FileBackend::doCopyInternal() |
248 | 254 | */ |
249 | | - function doConcatenate( array $params ) { |
| 255 | + function doCreateInternal( array $params ) { |
250 | 256 | $status = Status::newGood(); |
251 | 257 | |
252 | | - list( $destc, $dest ) = $this->resolveStoragePath( $params['dst'] ); |
253 | | - if ( $dest === null ) { |
| 258 | + list( $dstCont, $destRel ) = $this->resolveStoragePath( $params['dst'] ); |
| 259 | + if ( $destRel === null ) { |
254 | 260 | $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
255 | 261 | return $status; |
256 | 262 | } |
257 | 263 | |
258 | | - $conn = $this->connect(); |
259 | | - $dstc = $this->get_container( $conn, $destc ); |
260 | | - try { |
261 | | - $objd = $dstc->get_object( $dest ); |
262 | | - // if we are still here, it exists. |
263 | | - if ( empty( $params['overwriteDest'] ) ) { |
264 | | - $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
265 | | - return $status; |
266 | | - } |
267 | | - $exists = true; |
268 | | - } catch ( NoSuchObjectException $e ) { |
269 | | - $exists = false; |
| 264 | + // (a) Get a swift proxy connection |
| 265 | + $conn = $this->getConnection(); |
| 266 | + if ( !$conn ) { |
| 267 | + $status->fatal( 'backend-fail-connect' ); |
| 268 | + return $status; |
270 | 269 | } |
271 | 270 | |
| 271 | + // (b) Check the destination container |
272 | 272 | try { |
273 | | - $biggie = $dstc->create_object( $dest ); |
| 273 | + $dContObj = $conn->get_container( $conn, $dstCont ); |
| 274 | + } catch ( NoSuchContainerException $e ) { |
| 275 | + $status->fatal( 'backend-fail-create', $params['dst'] ); |
| 276 | + return $status; |
274 | 277 | } catch ( InvalidResponseException $e ) { |
275 | | - $status->fatal( 'backend-fail-opentemp', $tmpPath ); |
| 278 | + $status->fatal( 'backend-fail-connect' ); |
276 | 279 | return $status; |
277 | | - } |
278 | | - |
279 | | - foreach ( $params['srcs'] as $virtualSource ) { |
280 | | - list( $sourcec, $source ) = $this->resolveStoragePath( $virtualSource ); |
281 | | - if ( $source === null ) { |
282 | | - $status->fatal( 'backend-fail-invalidpath', $virtualSource ); |
283 | | - return $status; |
284 | | - } |
285 | | - $srcc = $this->get_container( $conn, $sourcec ); |
286 | | - $obj = $srcc->get_object( $source ); |
287 | | - $biggie->write( $obj->read() ); |
288 | | - } |
289 | | - return $status; |
290 | | - } |
291 | | - |
292 | | - /** |
293 | | - * @see FileBackend::doCopy() |
294 | | - */ |
295 | | - function doCreate( array $params ) { |
296 | | - $status = Status::newGood(); |
297 | | - |
298 | | - list( $destc, $dest ) = $this->resolveStoragePath( $params['dst'] ); |
299 | | - if ( $dest === null ) { |
300 | | - $status->fatal( 'backend-fail-invalidpath', $params['dst'] ); |
| 280 | + } catch ( Exception $e ) { // some other exception? |
| 281 | + $status->fatal( 'backend-fail-internal' ); |
301 | 282 | return $status; |
302 | 283 | } |
303 | 284 | |
304 | | - $conn = $this->connect(); |
305 | | - $dstc = $this->get_container( $conn, $destc ); |
| 285 | + // (c) Check if the destination object already exists |
306 | 286 | try { |
307 | | - $objd = $dstc->get_object( $dest ); |
308 | | - // if we are still here, it exists. |
| 287 | + $dContObj->get_object( $destRel ); // throws NoSuchObjectException |
| 288 | + // NoSuchObjectException not thrown: file must exist |
309 | 289 | if ( empty( $params['overwriteDest'] ) ) { |
310 | 290 | $status->fatal( 'backend-fail-alreadyexists', $params['dst'] ); |
311 | 291 | return $status; |
312 | 292 | } |
313 | | - $exists = true; |
314 | 293 | } catch ( NoSuchObjectException $e ) { |
315 | | - $exists = false; |
| 294 | + // NoSuchObjectException thrown: file does not exist |
| 295 | + } catch ( InvalidResponseException $e ) { |
| 296 | + $status->fatal( 'backend-fail-connect' ); |
| 297 | + return $status; |
| 298 | + } catch ( Exception $e ) { // some other exception? |
| 299 | + $status->fatal( 'backend-fail-internal' ); |
| 300 | + return $status; |
316 | 301 | } |
317 | 302 | |
318 | | - $obj = $dstc->create_object( $dest ); |
319 | | - //FIXME how do we know what the content type is? |
320 | | - // This *should* work...cloudfiles can figure content type from strings too. |
321 | | - $obj->content_type = 'text/plain'; |
322 | | - |
| 303 | + // (d) Actually create the object |
323 | 304 | try { |
| 305 | + $obj = $dContObj->create_object( $destRel ); |
324 | 306 | $obj->write( $params['content'] ); |
| 307 | + } catch ( BadContentTypeException $e ) { |
| 308 | + $status->fatal( 'backend-fail-contenttype', $params['dst'] ); |
325 | 309 | } catch ( InvalidResponseException $e ) { |
326 | | - $status->fatal( 'backend-fail-create', $params['dst'] ); |
327 | | - return $status; |
| 310 | + $status->fatal( 'backend-fail-connect' ); |
| 311 | + } catch ( Exception $e ) { // some other exception? |
| 312 | + $status->fatal( 'backend-fail-internal' ); |
328 | 313 | } |
329 | 314 | |
330 | 315 | return $status; |
331 | 316 | } |
332 | 317 | |
333 | 318 | /** |
| 319 | + * @see FileBackend::prepate() |
| 320 | + */ |
| 321 | + function prepare( array $params ) { |
| 322 | + $status = Status::newGood(); |
| 323 | + // @TODO: create containers as needed |
| 324 | + return $status; // badgers? We don't need no steenking badgers! |
| 325 | + } |
| 326 | + |
| 327 | + /** |
334 | 328 | * @see FileBackend::secure() |
335 | 329 | */ |
336 | 330 | function secure( array $params ) { |
— | — | @@ -342,18 +336,29 @@ |
343 | 337 | * @see FileBackend::fileExists() |
344 | 338 | */ |
345 | 339 | function fileExists( array $params ) { |
346 | | - list( $sourcec, $source ) = $this->resolveStoragePath( $params['src'] ); |
347 | | - if ( $source === null ) { |
| 340 | + list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 341 | + if ( $srcRel === null ) { |
348 | 342 | return false; // invalid storage path |
349 | 343 | } |
350 | | - $conn = $this->connect(); |
351 | | - $container = $this->get_container( $conn, $sourcec ); |
| 344 | + |
| 345 | + $conn = $this->getConnection(); |
| 346 | + if ( !$conn ) { |
| 347 | + $status->fatal( 'backend-fail-connect' ); |
| 348 | + return $status; |
| 349 | + } |
| 350 | + |
352 | 351 | try { |
353 | | - $obj = $container->get_object( $source ); |
| 352 | + $container = $this->get_container( $conn, $srcCont ); |
| 353 | + $container->get_object( $srcRel ); |
354 | 354 | $exists = true; |
| 355 | + } catch ( NoSuchContainerException $e ) { |
| 356 | + $exists = false; |
355 | 357 | } catch ( NoSuchObjectException $e ) { |
356 | 358 | $exists = false; |
| 359 | + } catch ( Exception $e ) { // some other exception? |
| 360 | + $exists = false; // fail vs not exists? |
357 | 361 | } |
| 362 | + |
358 | 363 | return $exists; |
359 | 364 | } |
360 | 365 | |
— | — | @@ -361,18 +366,28 @@ |
362 | 367 | * @see FileBackend::getFileTimestamp() |
363 | 368 | */ |
364 | 369 | function getFileTimestamp( array $params ) { |
365 | | - list( $sourcec, $source ) = $this->resolveStoragePath( $params['src'] ); |
366 | | - if ( $source === null ) { |
| 370 | + list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 371 | + if ( $srcRel === null ) { |
367 | 372 | return false; // invalid storage path |
368 | 373 | } |
369 | 374 | |
370 | | - $conn = $this->connect(); |
371 | | - $container = $this->get_container( $conn, $sourcec); |
| 375 | + $conn = $this->getConnection(); |
| 376 | + if ( !$conn ) { |
| 377 | + $status->fatal( 'backend-fail-connect' ); |
| 378 | + return $status; |
| 379 | + } |
| 380 | + |
372 | 381 | try { |
373 | | - $obj = $container->get_object( $source ); |
| 382 | + $container = $this->get_container( $conn, $srcCont); |
| 383 | + $obj = $container->get_object( $srcRel ); |
| 384 | + } catch ( NoSuchContainerException $e ) { |
| 385 | + $obj = NULL; |
374 | 386 | } catch ( NoSuchObjectException $e ) { |
375 | 387 | $obj = NULL; |
| 388 | + } catch ( Exception $e ) { // some other exception? |
| 389 | + $obj = NULL; // fail vs not exists? |
376 | 390 | } |
| 391 | + |
377 | 392 | if ( $obj ) { |
378 | 393 | $thumbTime = $obj->last_modified; |
379 | 394 | // @FIXME: strptime() UNIX-only (http://php.net/manual/en/function.strptime.php) |
— | — | @@ -394,10 +409,23 @@ |
395 | 410 | return array(); // empty result |
396 | 411 | } |
397 | 412 | |
398 | | - $conn = $this->connect(); |
| 413 | + $conn = $this->getConnection(); |
| 414 | + if ( !$conn ) { |
| 415 | + return null; |
| 416 | + } |
| 417 | + |
399 | 418 | // @TODO: return an Iterator class that pages via list_objects() |
400 | | - $container = $this->get_container( $conn, $dirc ); |
401 | | - $files = $container->list_objects( 0, NULL, $dir ); |
| 419 | + try { |
| 420 | + $container = $this->get_container( $conn, $dirc ); |
| 421 | + $files = $container->list_objects( 0, NULL, $dir ); |
| 422 | + } catch ( NoSuchContainerException $e ) { |
| 423 | + $files = array(); |
| 424 | + } catch ( NoSuchObjectException $e ) { |
| 425 | + $files = array(); |
| 426 | + } catch ( Exception $e ) { // some other exception? |
| 427 | + $files = null; |
| 428 | + } |
| 429 | + |
402 | 430 | // if there are no files matching the prefix, return empty array |
403 | 431 | return $files; |
404 | 432 | } |
— | — | @@ -406,39 +434,111 @@ |
407 | 435 | * @see FileBackend::getLocalCopy() |
408 | 436 | */ |
409 | 437 | function getLocalCopy( array $params ) { |
410 | | - list( $sourcec, $source ) = $this->resolveStoragePath( $params['src'] ); |
411 | | - if ( $source === null ) { |
| 438 | + list( $srcCont, $srcRel ) = $this->resolveStoragePath( $params['src'] ); |
| 439 | + if ( $srcRel === null ) { |
412 | 440 | return null; |
413 | 441 | } |
414 | 442 | |
415 | 443 | // Get source file extension |
416 | | - $i = strrpos( $source, '.' ); |
417 | | - $ext = strtolower( $i ? substr( $source, $i + 1 ) : '' ); |
| 444 | + $ext = FileBackend::extensionFromPath( $srcRel ); |
418 | 445 | // Create a new temporary file... |
419 | | - $tmpFile = TempFSFile::factory( wfBaseName( $source ) . '_', $ext ); |
| 446 | + $tmpFile = TempFSFile::factory( wfBaseName( $srcRel ) . '_', $ext ); |
420 | 447 | if ( !$tmpFile ) { |
421 | 448 | return null; |
422 | 449 | } |
423 | 450 | $tmpPath = $tmpFile->getPath(); |
424 | 451 | |
425 | | - $conn = $this->connect(); |
426 | | - $cont = $this->get_container( $conn, $sourcec ); |
427 | | - |
428 | | - try { |
429 | | - $obj = $cont->get_object( $source ); |
430 | | - } catch ( NoSuchObjectException $e ) { |
431 | | - throw new MWException( "Unable to open original file at", $params['src'] ); |
| 452 | + $conn = $this->getConnection(); |
| 453 | + if ( !$conn ) { |
| 454 | + return null; |
432 | 455 | } |
433 | 456 | |
434 | 457 | try { |
| 458 | + $cont = $this->get_container( $conn, $srcCont ); |
| 459 | + $obj = $cont->get_object( $srcRel ); |
435 | 460 | $obj->save_to_filename( $tmpPath ); |
| 461 | + } catch ( NoSuchContainerException $e ) { |
| 462 | + $tmpFile = null; |
| 463 | + } catch ( NoSuchObjectException $e ) { |
| 464 | + $tmpFile = null; |
436 | 465 | } catch ( IOException $e ) { |
437 | | - // throw new MWException( __METHOD__ . ": error opening '$e'" ); |
438 | | - return null; |
439 | | - } catch ( InvalidResponseException $e ) { |
440 | | - // throw new MWException( __METHOD__ . "unexpected response '$e'" ); |
441 | | - return null; |
| 466 | + $tmpFile = null; |
| 467 | + } catch ( Exception $e ) { // some other exception? |
| 468 | + $tmpFile = null; |
442 | 469 | } |
| 470 | + |
443 | 471 | return $tmpFile; |
444 | 472 | } |
| 473 | + |
| 474 | + /** |
| 475 | + * Get a connection to the swift proxy |
| 476 | + * |
| 477 | + * @return CF_Connection|null |
| 478 | + */ |
| 479 | + protected function getConnection() { |
| 480 | + if ( $this->conn === false ) { |
| 481 | + return null; // failed last attempt |
| 482 | + } |
| 483 | + // Authenticate with proxy and get a session key. |
| 484 | + // Session keys expire after a while, so we renew them periodically. |
| 485 | + if ( $this->conn === null || ( time() - $this->connStarted ) > $this->connTTL ) { |
| 486 | + try { |
| 487 | + $this->auth->authenticate(); |
| 488 | + $this->conn = new CF_Connection( $this->auth ); |
| 489 | + $this->connStarted = time(); |
| 490 | + } catch ( AuthenticationException $e ) { |
| 491 | + $this->conn = false; // don't keep re-trying |
| 492 | + } catch ( InvalidResponseException $e ) { |
| 493 | + $this->conn = false; // don't keep re-trying |
| 494 | + } |
| 495 | + } |
| 496 | + return $this->conn; |
| 497 | + } |
| 498 | + |
| 499 | + /** |
| 500 | + * Copy a file from one place to another place |
| 501 | + * |
| 502 | + * @param $srcContainer CF_Container |
| 503 | + * @param $srcRel String: relative path to the source file. |
| 504 | + * @param $dstContainer CF_Container |
| 505 | + * @param $dstRel String: relative path to the destination. |
| 506 | + */ |
| 507 | + protected function swiftcopy( $srcContainer, $srcRel, $dstContainer, $dstRel ) { |
| 508 | + // The destination must exist already. |
| 509 | + $obj = $dstContainer->create_object( $dstRel ); |
| 510 | + $obj->content_type = 'text/plain'; // overwritten by source object. |
| 511 | + |
| 512 | + try { |
| 513 | + $obj->write( '.' ); |
| 514 | + } catch ( SyntaxException $e ) { |
| 515 | + throw new MWException( "Write failed: $e" ); |
| 516 | + } catch ( BadContentTypeException $e ) { |
| 517 | + throw new MWException( "Missing Content-Type: $e" ); |
| 518 | + } catch ( MisMatchedChecksumException $e ) { |
| 519 | + throw new MWException( __METHOD__ . "should not happen: '$e'" ); |
| 520 | + } |
| 521 | + |
| 522 | + try { |
| 523 | + $obj = $dstContainer->get_object( $dstRel ); |
| 524 | + } catch ( NoSuchObjectException $e ) { |
| 525 | + throw new MWException( 'The object we just created does not exist: ' . |
| 526 | + $dstContainer->name . "/$dstRel: $e" ); |
| 527 | + } |
| 528 | + |
| 529 | + try { |
| 530 | + $srcObj = $srcContainer->get_object( $srcRel ); |
| 531 | + } catch ( NoSuchObjectException $e ) { |
| 532 | + throw new MWException( 'Source file does not exist: ' . |
| 533 | + $srcContainer->name . "/$srcRel: $e" ); |
| 534 | + } |
| 535 | + |
| 536 | + try { |
| 537 | + $dstContainer->copy_object_from($srcObj,$srcContainer,$dstRel); |
| 538 | + } catch ( SyntaxException $e ) { |
| 539 | + throw new MWException( 'Source file does not exist: ' . |
| 540 | + $srcContainer->name . "/$srcRel: $e" ); |
| 541 | + } catch ( MisMatchedChecksumException $e ) { |
| 542 | + throw new MWException( "Checksums do not match: $e" ); |
| 543 | + } |
| 544 | + } |
445 | 545 | } |