Arbit - project tracking

PHPillow - PHP CouchDB connector

Browse source code

File: / src/ classes/ tool.php

Type
text/plain text/plain
Last Author
kore
Version
185
Line Rev. Author Source
1 113 kore <?php
2 kore /**
3 kore * phpillow tool
4 kore *
5 kore * This file is part of phpillow.
6 kore *
7 kore * phpillow is free software; you can redistribute it and/or modify it under
8 kore * the terms of the GNU Lesser General Public License as published by the Free
9 kore * Software Foundation; version 3 of the License.
10 kore *
11 kore * phpillow is distributed in the hope that it will be useful, but WITHOUT ANY
12 kore * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13 kore * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
14 kore * more details.
15 kore *
16 kore * You should have received a copy of the GNU Lesser General Public License
17 kore * along with phpillow; if not, write to the Free Software Foundation, Inc., 51
18 kore * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 kore *
20 kore * @package Core
21 kore * @version $Revision: 185 $
22 kore * @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPL
23 kore */
24 kore
25 kore /**
26 kore * Basic tool handling in- end exports of CouchDB dumps.
27 kore *
28 kore * API and format should be compatible with couchdb-python [1].
29 kore *
30 kore * [1] http://code.google.com/p/couchdb-python/
31 kore *
32 kore * @package Core
33 kore * @version $Revision: 185 $
34 kore * @license http://www.gnu.org/licenses/lgpl-3.0.txt LGPL
35 kore */
36 kore class phpillowTool
37 kore {
38 kore /**
39 114 kore * Data source name for the CouchDB connection
40 159 kore *
41 114 kore * @var string
42 kore */
43 kore protected $dsn;
44 kore
45 kore /**
46 kore * CLI tool options
47 159 kore *
48 114 kore * @var array
49 kore */
50 kore protected $options;
51 kore
52 kore /**
53 kore * Parsed connection information
54 159 kore *
55 114 kore * @var array
56 kore */
57 kore protected $connectionInfo = array(
58 kore 'host' => 'localhost',
59 kore 'port' => '5984',
60 kore 'user' => null,
61 kore 'pass' => null,
62 kore 'path' => '/',
63 kore );
64 kore
65 kore /**
66 125 kore * Standard output stream
67 159 kore *
68 125 kore * @var resource
69 kore */
70 kore protected $stdout = STDOUT;
71 kore
72 kore /**
73 kore * Standard error stream
74 159 kore *
75 125 kore * @var resource
76 kore */
77 kore protected $stderr = STDERR;
78 kore
79 kore /**
80 113 kore * Construct tool
81 kore *
82 kore * Construct tool from database DSN (Data-Source-Name, the URL defining the
83 kore * databases location) and an optional set of options.
84 159 kore *
85 kore * @param mixed $dsn
86 kore * @param array $options
87 113 kore * @return void
88 kore */
89 kore public function __construct( $dsn, array $options = array() )
90 kore {
91 kore $this->dsn = $dsn;
92 kore $this->options = $options;
93 117 kore
94 kore if ( !array_search( 'string', stream_get_wrappers() ) )
95 kore {
96 kore stream_wrapper_register( 'string', 'phpillowToolStringStream' );
97 kore }
98 113 kore }
99 kore
100 kore /**
101 159 kore * Set output streams
102 125 kore *
103 kore * Set the output streams to be used by the tool.
104 159 kore *
105 kore * @param resource $stdout
106 kore * @param resource $stderr
107 125 kore * @return void
108 kore */
109 kore public function setOutputStreams( $stdout = STDOUT, $stderr = STDERR )
110 kore {
111 kore $this->stdout = $stdout;
112 kore $this->stderr = $stderr;
113 kore }
114 kore
115 kore /**
116 132 kore * Echo a message
117 kore *
118 kore * Echo a progress message to STDERR, if the verbose flag is set.
119 159 kore *
120 kore * @param string $message
121 132 kore * @return void
122 kore */
123 kore protected function out( $message )
124 kore {
125 kore if ( isset( $this->options['v'] ) ||
126 kore isset( $this->options['verbose'] ) )
127 kore {
128 kore fwrite( $this->stderr, $message );
129 kore }
130 kore }
131 kore
132 kore /**
133 113 kore * Print version
134 kore *
135 kore * Print version of the tool, if the version flag has been set.
136 159 kore *
137 113 kore * @return bool
138 kore */
139 kore protected function printVersion()
140 kore {
141 kore if ( !isset( $this->options['version'] ) )
142 kore {
143 kore return false;
144 kore }
145 kore
146 kore $version = '$Revision: 185 $';
147 kore if ( preg_match( '(\\$Revision:\\s+(?P<revision>\\d+)\\s*\\$)', $version, $match ) )
148 kore {
149 kore $version = 'svn-' . $match['revision'];
150 kore }
151 kore
152 132 kore fwrite( $this->stdout, "PHPillow backup tool - version: $version\n" );
153 113 kore return true;
154 kore }
155 kore
156 kore /**
157 114 kore * Parse the provided connection information
158 kore *
159 159 kore * Returns false,if the connection information could not be parser
160 114 kore * properly.
161 159 kore *
162 114 kore * @return bool
163 kore */
164 kore protected function parseConnectionInformation()
165 kore {
166 kore if ( ( $info = @parse_url( $this->dsn ) ) === false )
167 kore {
168 132 kore fwrite( $this->stderr, "Could not parse provided DSN: {$this->dsn}\n" );
169 114 kore return false;
170 kore }
171 kore
172 kore foreach ( $info as $key => $value )
173 kore {
174 kore if ( array_key_exists( $key, $this->connectionInfo ) )
175 kore {
176 kore $this->connectionInfo[$key] = $value;
177 kore }
178 kore }
179 kore
180 kore if ( isset( $this->options['username'] ) )
181 kore {
182 kore $this->connectionInfo['user'] = $this->options['username'];
183 kore }
184 kore
185 kore if ( isset( $this->options['password'] ) )
186 kore {
187 kore $this->connectionInfo['pass'] = $this->options['password'];
188 kore }
189 kore
190 kore return true;
191 kore }
192 kore
193 kore /**
194 113 kore * Execute dump command
195 kore *
196 kore * Returns a proper status code indicating successful execution of the
197 kore * command.
198 kore *
199 kore * @return int
200 kore */
201 kore public function dump()
202 kore {
203 kore if ( $this->printVersion() )
204 kore {
205 kore return 0;
206 kore }
207 114 kore
208 kore if ( !$this->parseConnectionInformation() )
209 kore {
210 kore return 1;
211 kore }
212 kore
213 125 kore $db = new phpillowCustomConnection(
214 122 kore $this->connectionInfo['host'],
215 kore $this->connectionInfo['port'],
216 kore $this->connectionInfo['user'],
217 kore $this->connectionInfo['pass']
218 kore );
219 kore
220 125 kore $writer = new phpillowToolMultipartWriter( $this->stdout );
221 150 kore
222 159 kore // Fetch and dump documents in chunks of 1000 documents, since the
223 150 kore // memory consumption might be too high otherwise
224 kore // @TODO: Make chunk-size configurable.
225 kore $offset = null;
226 kore $limit = 1000;
227 kore do {
228 kore $docs = $db->get( $this->connectionInfo['path'] . '/_all_docs?limit=' . $limit .
229 kore ( $offset !== null ? '&startkey="' . $offset . '"' : '' )
230 122 kore );
231 159 kore
232 150 kore foreach ( $docs->rows as $nr => $doc )
233 kore {
234 kore if ( ( $nr === 0 ) &&
235 kore ( $offset !== null ) )
236 kore {
237 159 kore // The document which equals the startkey and already has
238 150 kore // been dumped.
239 kore continue;
240 kore }
241 122 kore
242 150 kore $offset = $doc['id'];
243 kore
244 kore $this->out( "Dumping document " . $doc['id'] . "\n" );
245 kore $doc = $db->get( $this->connectionInfo['path'] . '/' . urlencode( $doc['id'] ) );
246 kore
247 kore // Skip deleted documents
248 kore // @TODO: Make this configurable
249 kore if ( isset( $doc->deleted ) &&
250 kore ( $doc->deleted === true ) )
251 kore {
252 kore continue;
253 kore }
254 kore
255 159 kore // Fetch attachments explicitly. Including the attachments in
256 150 kore // the doc sometimes causes errors on CouchDB 0.10
257 kore $doc = $doc->getFullDocument();
258 kore if ( isset( $doc['_attachments'] ) )
259 kore {
260 kore foreach ( $doc['_attachments'] as $name => $attachment )
261 kore {
262 kore $data = $db->get( $this->connectionInfo['path'] . '/' . urlencode( $doc['_id'] ) . '/' . $name, null, true );
263 kore $doc['_attachments'][$name]['data'] = $data->data;
264 kore }
265 kore }
266 kore
267 kore $writer->writeDocument( $doc );
268 kore }
269 kore } while ( count( $docs->rows ) > 1 );
270 kore
271 122 kore unset( $writer );
272 114 kore return 0;
273 113 kore }
274 kore
275 kore /**
276 117 kore * Clean up document definition
277 kore *
278 kore * Returns the cleaned up document body as a result.
279 159 kore *
280 kore * @param array $document
281 117 kore * @return string
282 kore */
283 kore protected function getDocumentBody( array $document )
284 kore {
285 kore if ( strpos( $document['Content-Type'], 'application/json' ) === 0 )
286 kore {
287 kore $source = json_decode( $document['body'], true );
288 kore unset( $source['_rev'] );
289 kore return json_encode( $source );
290 kore }
291 kore
292 kore if ( is_array( $document['body'] ) )
293 kore {
294 kore $main = array_shift( $document['body'] );
295 kore $source = json_decode( $main['body'], true );
296 kore unset( $source['_rev'] );
297 kore
298 kore $source['_attachments'] = array();
299 kore foreach ( $document['body'] as $attachment )
300 kore {
301 kore $source['_attachments'][$attachment['Content-ID']] = array(
302 kore 'content_type' => $attachment['Content-Type'],
303 185 kore 'data' => base64_encode( $attachment['body'] ),
304 117 kore );
305 kore }
306 kore
307 kore return json_encode( $source );
308 kore }
309 kore
310 119 kore throw new phpillowMultipartParserException( "Invalid document: " . var_export( $document, true ) );
311 117 kore }
312 kore
313 kore /**
314 113 kore * Execute load command
315 kore *
316 kore * Returns a proper status code indicating successful execution of the
317 kore * command.
318 kore *
319 kore * @return int
320 kore */
321 kore public function load()
322 kore {
323 kore if ( $this->printVersion() )
324 kore {
325 kore return 0;
326 kore }
327 114 kore
328 kore if ( !$this->parseConnectionInformation() )
329 kore {
330 kore return 1;
331 kore }
332 kore
333 117 kore // Open input stream to read contents from
334 119 kore $stream = isset( $this->options['input'] ) ? fopen( $this->options['input'], 'r' ) : STDIN;
335 117 kore $multipartParser = new phpillowToolMultipartParser( $stream );
336 kore
337 125 kore $db = new phpillowCustomConnection(
338 117 kore $this->connectionInfo['host'],
339 kore $this->connectionInfo['port'],
340 kore $this->connectionInfo['user'],
341 kore $this->connectionInfo['pass']
342 kore );
343 kore
344 128 kore // Create database if it does not exist yet
345 kore try
346 kore {
347 kore $db->get( $this->connectionInfo['path'] );
348 kore }
349 kore catch ( phpillowResponseNotFoundErrorException $e )
350 kore {
351 kore $db->put( $this->connectionInfo['path'] );
352 kore }
353 kore
354 kore // Import the documents
355 117 kore while ( ( $document = $multipartParser->getDocument() ) !== false )
356 kore {
357 118 kore try
358 kore {
359 132 kore $this->out( "Loading document " . $document['Content-ID'] . "\n" );
360 118 kore $path = $this->connectionInfo['path'] . '/' . $document['Content-ID'];
361 kore $db->put( $path, $this->getDocumentBody( $document ) );
362 kore }
363 kore catch ( phpillowException $e )
364 kore {
365 142 kore fwrite( $this->stderr, $document['Content-ID'] . ': ' . $e->getMessage() . "\n" );
366 118 kore if ( !isset( $this->options['ignore-errors'] ) )
367 kore {
368 kore return 1;
369 kore }
370 kore }
371 117 kore }
372 kore
373 114 kore return 0;
374 113 kore }
375 126 kore
376 kore /**
377 kore * Prime caches of all views
378 159 kore *
379 126 kore * Returns a proper status code indicating successful execution of the
380 kore * command.
381 kore *
382 kore * @return int
383 kore */
384 kore public function primeCaches()
385 kore {
386 kore if ( $this->printVersion() )
387 kore {
388 kore return 0;
389 kore }
390 kore
391 kore if ( !$this->parseConnectionInformation() )
392 kore {
393 kore return 1;
394 kore }
395 kore
396 kore // Open connection
397 kore $db = new phpillowCustomConnection(
398 kore $this->connectionInfo['host'],
399 kore $this->connectionInfo['port'],
400 kore $this->connectionInfo['user'],
401 kore $this->connectionInfo['pass']
402 kore );
403 kore $designDocs = $db->get( $this->connectionInfo['path'] . '/_all_docs?startkey=%22_design%2F%22&endkey=%22_design0%22' );
404 kore foreach ( $designDocs->rows as $doc )
405 kore {
406 kore $views = $db->get( $this->connectionInfo['path'] . '/' . $doc['id'] );
407 kore foreach ( $views->views as $view => $functions )
408 kore {
409 132 kore $this->out( "Priming view " . $doc['id'] . "/" . $view . "\n" );
410 126 kore $db->get( $this->connectionInfo['path'] . '/' . $doc['id'] . '/_view/' . $view );
411 kore }
412 kore }
413 kore }
414 113 kore }
415 kore