1<?php
2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
3
4/**
5 * Abstract base class for all the readers
6 *
7 * A reader is a compilation of serveral files that can be read
8 *
9 * PHP versions 4 and 5
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 * Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with this library; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330,Boston,MA 02111-1307 USA
24 *
25 * @category   File Formats
26 * @package    File_Archive
27 * @author     Vincent Lascaux <vincentlascaux@php.net>
28 * @copyright  1997-2005 The PHP Group
29 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL
30 * @version    CVS: $Id: Reader.php,v 1.33 2005/07/07 12:24:57 vincentlascaux Exp $
31 * @link       http://pear.php.net/package/File_Archive
32 */
33
34require_once "PEAR.php";
35
36/**
37 * Abstract base class for all the readers
38 *
39 * A reader is a compilation of serveral files that can be read
40 */
41class File_Archive_Reader
42{
43    /**
44     * Move to the next file in the reader
45     *
46     * @return bool false iif no more files are available
47     */
48    function next()
49    {
50        return false;
51    }
52
53    /**
54     * Move to the next file whose name is in directory $filename
55     * or is exactly $filename
56     *
57     * @param string $filename Name of the file to find in the archive
58     * @param bool $close If true, close the reader and search from the first file
59     * @return bool whether the file was found in the archive or not
60     */
61    function select($filename, $close = true)
62    {
63        $std = $this->getStandardURL($filename);
64
65        if ($close) {
66            $error = $this->close();
67            if (PEAR::isError($error)) {
68                return $error;
69            }
70        }
71        while (($error = $this->next()) === true) {
72            $sourceName = $this->getFilename();
73            if (
74                  empty($std) ||
75
76                //$std is a file
77                  $std == $sourceName ||
78
79                //$std is a directory
80                strncmp($std.'/', $sourceName, strlen($std)+1) == 0
81               ) {
82                return true;
83            }
84        }
85        return $error;
86    }
87
88    /**
89     * Returns the standard path
90     * Changes \ to /
91     * Removes the .. and . from the URL
92     * @param string $path a valid URL that may contain . or .. and \
93     * @static
94     */
95    function getStandardURL($path)
96    {
97        if ($path == '.') {
98            return '';
99        }
100        $std = str_replace("\\", "/", $path);
101        while ($std != ($std = preg_replace("/[^\/:?]+\/\.\.\//", "", $std))) ;
102        $std = str_replace("/./", "", $std);
103        if (strncmp($std, "./", 2) == 0) {
104            return substr($std, 2);
105        } else {
106            return $std;
107        }
108    }
109
110    /**
111     * Returns the name of the file currently read by the reader
112     *
113     * Warning: undefined behaviour if no call to next have been
114     * done or if last call to next has returned false
115     *
116     * @return string Name of the current file
117     */
118    function getFilename()
119    {
120        return PEAR::raiseError("Reader abstract function call (getFilename)");
121    }
122
123    /**
124     * Returns the list of filenames from the current pos to the end of the source
125     * The source will be closed after having called this function
126     * This function goes through the whole archive (which may be slow).
127     * If you intend to work on the reader, doing it in one pass would be faster
128     *
129     * @return array filenames from the current pos to the end of the source
130     */
131    function getFileList()
132    {
133        $result = array();
134        while ( ($error = $this->next()) === true) {
135            $result[] = $this->getFilename();
136        }
137        $this->close();
138        if (PEAR::isError($error)) {
139            return $error;
140        } else {
141            return $result;
142        }
143    }
144
145    /**
146     * Returns an array of statistics about the file
147     * (see the PHP stat function for more information)
148     *
149     * The returned array may be empty, even if readers should try
150     * their best to return as many data as possible
151     */
152    function getStat() { return array(); }
153
154    /**
155     * Returns the MIME associated with the current file
156     * The default function does that by looking at the extension of the file
157     */
158    function getMime()
159    {
160        require_once "File/Archive/Reader/MimeList.php";
161        return File_Archive_Reader_GetMime($this->getFilename());
162    }
163
164    /**
165     * If the current file of the archive is a physical file,
166     *
167     * @return the name of the physical file containing the data
168     *         or null if no such file exists
169     *
170     * The data filename may not be the same as the filename.
171     */
172    function getDataFilename() { return null; }
173
174    /**
175     * Reads some data from the current file
176     * If the end of the file is reached, returns null
177     * If $length is not specified, reads up to the end of the file
178     * If $length is specified reads up to $length
179     */
180    function getData($length = -1)
181    {
182        return PEAR::raiseError("Reader abstract function call (getData)");
183    }
184
185    /**
186     * Skip some data and returns how many bytes have been skipped
187     * This is strictly equivalent to
188     *  return strlen(getData($length))
189     * But could be far more efficient
190     */
191    function skip($length = -1)
192    {
193        $data = $this->getData($length);
194        if (PEAR::isError($data)) {
195            return $data;
196        } else {
197            return strlen($data);
198        }
199    }
200
201    /**
202     * Move the current position back of a given amount of bytes.
203     * Not all readers may implement this function (a PEAR error will
204     * be returned if the reader can't rewind)
205     *
206     * @param int $length number of bytes to seek before the current pos
207     *        or -1 to move back to the begining of the current file
208     * @return the number of bytes really rewinded (which may be less than
209     *        $length if the current pos is less than $length
210     */
211    function rewind($length = -1)
212    {
213        return PEAR::raiseError('Rewind function is not implemented on this reader');
214    }
215
216    /**
217     * Returns the current offset in the current file
218     */
219    function tell()
220    {
221        $offset = $this->rewind();
222        $this->skip($offset);
223        return $offset;
224    }
225
226    /**
227     * Put back the reader in the state it was before the first call
228     * to next()
229     */
230    function close()
231    {
232    }
233
234    /**
235     * Sends the current file to the Writer $writer
236     * The data will be sent by chunks of at most $bufferSize bytes
237     * If $bufferSize <= 0 (default), the blockSize option is used
238     */
239    function sendData(&$writer, $bufferSize = 0)
240    {
241        if (PEAR::isError($writer)) {
242            return $writer;
243        }
244        if ($bufferSize <= 0) {
245            $bufferSize = File_Archive::getOption('blockSize');
246        }
247
248        $filename = $this->getDataFilename();
249        if ($filename !== null) {
250            $error = $writer->writeFile($filename);
251            if (PEAR::isError($error)) {
252                return $error;
253            }
254        } else {
255            while (($data = $this->getData($bufferSize)) !== null) {
256                if (PEAR::isError($data)) {
257                    return $data;
258                }
259                $error = $writer->writeData($data);
260                if (PEAR::isError($error)) {
261                    return $error;
262                }
263            }
264        }
265    }
266
267    /**
268     * Sends the whole reader to $writer and close the reader
269     *
270     * @param File_Archive_Writer $writer Where to write the files of the reader
271     * @param bool $autoClose If true, close $writer at the end of the function.
272     *        Default value is true
273     * @param int $bufferSize Size of the chunks that will be sent to the writer
274     *        If $bufferSize <= 0 (default value), the blockSize option is used
275     */
276    function extract(&$writer, $autoClose = true, $bufferSize = 0)
277    {
278        if (PEAR::isError($writer)) {
279            $this->close();
280            return $writer;
281        }
282
283        while (($error = $this->next()) === true) {
284            if ($writer->newFileNeedsMIME()) {
285                $mime = $this->getMime();
286            } else {
287                $mime = null;
288            }
289            $error = $writer->newFile(
290                $this->getFilename(),
291                $this->getStat(),
292                $mime
293            );
294            if (PEAR::isError($error)) {
295                break;
296            }
297            $error = $this->sendData($writer, $bufferSize);
298            if (PEAR::isError($error)) {
299                break;
300            }
301        }
302        $this->close();
303        if ($autoClose) {
304            $writer->close();
305        }
306        if (PEAR::isError($error)) {
307            return $error;
308        }
309    }
310
311    /**
312     * Extract only one file (given by the URL)
313     *
314     * @param string $filename URL of the file to extract from this
315     * @param File_Archive_Writer $writer Where to write the file
316     * @param bool $autoClose If true, close $writer at the end of the function
317     *        Default value is true
318     * @param int $bufferSize Size of the chunks that will be sent to the writer
319     *        If $bufferSize <= 0 (default value), the blockSize option is used
320     */
321    function extractFile($filename, &$writer,
322                         $autoClose = true, $bufferSize = 0)
323    {
324        if (PEAR::isError($writer)) {
325            return $writer;
326        }
327
328        if (($error = $this->select($filename)) === true) {
329            $result = $this->sendData($writer, $bufferSize);
330            if (!PEAR::isError($result)) {
331                $result = true;
332            }
333        } else if ($error === false) {
334            $result = PEAR::raiseError("File $filename not found");
335        } else {
336            $result = $error;
337        }
338        if ($autoClose) {
339            $error = $writer->close();
340            if (PEAR::isError($error)) {
341                return $error;
342            }
343        }
344        return $result;
345    }
346
347    /**
348     * Return a writer that allows appending files to the archive
349     * After having called makeAppendWriter, $this is closed and should not be
350     * used until the returned writer is closed.
351     *
352     * @return a writer that will allow to append files to an existing archive
353     * @see makeWriter
354     */
355    function makeAppendWriter()
356    {
357        require_once "File/Archive/Predicate/False.php";
358        return $this->makeWriterRemoveFiles(new File_Archive_Predicate_False());
359    }
360
361    /**
362     * Return a writer that has the same properties as the one returned by
363     * makeAppendWriter, but after having removed all the files that follow a
364     * given predicate.
365     * After a call to makeWriterRemoveFiles, $this is closed and should not
366     * be used until the returned writer is closed
367     *
368     * @param File_Archive_Predicate $pred the predicate verified by removed files
369     * @return File_Archive_Writer that allows to append files to the archive
370     */
371    function makeWriterRemoveFiles($pred)
372    {
373        return PEAR::raiseError("Reader abstract function call (makeWriterRemoveFiles)");
374    }
375
376    /**
377     * Returns a writer that removes the current file
378     * This is a syntaxic sugar for makeWriterRemoveFiles(new File_Archive_Predicate_Current());
379     */
380    function makeWriterRemove()
381    {
382        require_once "File/Archive/Predicate/Current.php";
383        return $this->makeWriterRemoveFiles(new File_Archive_Predicate_Current());
384    }
385
386    /**
387     * Removes the current file from the reader
388     */
389    function remove()
390    {
391        $writer = $this->makeWriterRemove();
392        if (PEAR::isError($writer)) {
393            return $writer;
394        }
395        $writer->close();
396    }
397
398    /**
399     * Return a writer that has the same properties as the one returned by makeWriter, but after
400     * having removed a block of data from the current file. The writer will append data to the current file
401     * no data (other than the block) will be removed
402     *
403     * @param array Lengths of the blocks. The first one will be discarded, the second one kept, the third
404     *        one discarded... If the sum of the blocks is less than the size of the file, the comportment is the
405     *        same as if a last block was set in the array to reach the size of the file
406     *        if $length is -1, the file is truncated from the specified pos
407     *        It is possible to specify blocks of size 0
408     * @param int $seek relative pos of the block
409     */
410    function makeWriterRemoveBlocks($blocks, $seek = 0)
411    {
412        return PEAR::raiseError("Reader abstract function call (makeWriterRemoveBlocks)");
413    }
414}
415
416?>