PageRenderTime 36ms CodeModel.GetById 13ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 0ms

/ezcomponents/Archive/src/archive.php

http://hppg.googlecode.com/
PHP | 951 lines | 561 code | 76 blank | 314 comment | 67 complexity | 1775383dca270e61680af527a93d0f68 MD5 | raw file
  1<?php
  2/**
  3 * File containing the abstract ezcArchive class.
  4 *
  5 * @package Archive
  6 * @version 1.4.1
  7 * @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
  8 * @license http://ez.no/licenses/new_bsd New BSD License
  9 */
 10
 11/**
 12 * The ezcArchive class provides the common interface for reading and writing
 13 * the archive formats Tar and Zip.
 14 *
 15 * ezcArchive provides the main API for reading and writing to an archive. The
 16 * archive itself can be compressed with GZip or BZip2 and will be handled
 17 * transparently.
 18 *
 19 * The {@link open()} method creates a new archive instance. For
 20 * existing archives, ezcArchive determines the correct archive format by the
 21 * mime-type and returns an instance of a subclass handling this format.
 22 * New archives should force a format type via a parameter in the open()
 23 * method.
 24 *
 25 * The instance of an ezcArchive class is also an iterator, which
 26 * points to the first file in the archive by default. Moving this pointer can
 27 * be done via the iterator methods: {@link rewind()}, {@link next()},
 28 * {@link valid()}, {@link key()}, and {@link current()}. This iterator is
 29 * defined as an object iterator and allows, for example, the {@link
 30 * http://www.php.net/foreach foreach} statement to iterate through the files.
 31 *
 32 * Extra methods that operate on the current iterator are: {@link
 33 * extractCurrent()} and {@link appendToCurrent()}. Which can be used,
 34 * respectively, to extract the files and to append a new file to the archive.
 35 * To append a directory to an archive you need to add a slash '/' at the end
 36 * of the directory name.
 37 *
 38 * The following example will open an existing tar.gz file and will append each
 39 * file to a zip archive:
 40 * <code>
 41 * $tar = ezcArchive::open( "/tmp/archive.tar.gz" );
 42 * $newZip = ezcArchive::open( "/tmp/new_archive.zip", ezcArchive::ZIP );
 43 *
 44 * foreach ( $tar as $entry )
 45 * {
 46 *    // $entry contains the information of the current entry in the archive.
 47 *    $tar->extractCurrent( "/tmp/" );
 48 *    $newZip->appendToCurrent( $entry->getPath(), "/tmp/" );
 49 *    $newZip->next();
 50 * }
 51 * </code>
 52 *
 53 * In order to extract an entire archive at once, use the {@link extract()}
 54 * method.
 55 *
 56 * @package Archive
 57 * @version 1.4.1
 58 * @mainclass
 59 */
 60abstract class ezcArchive implements Iterator
 61{
 62    /**
 63     * Normal tar archive.
 64     */
 65    const TAR        = 0;
 66
 67    /**
 68     * Tar version 7 archive.
 69     */
 70    const TAR_V7     = 1;
 71
 72    /**
 73     * USTAR tar archive.
 74     */
 75    const TAR_USTAR  = 2;
 76
 77    /**
 78     * PAX tar archive.
 79     */
 80    const TAR_PAX    = 3;
 81
 82    /**
 83     * GNU tar archive.
 84     */
 85    const TAR_GNU    = 4;
 86
 87    /**
 88     * ZIP archive.
 89     */
 90    const ZIP        = 10;
 91
 92    /**
 93     * Gnu ZIP compression format.
 94     */
 95    const GZIP       = 20;
 96
 97    /**
 98     * BZIP2 compression format.
 99     */
100    const BZIP2      = 30;
101
102    /**
103     * The entry or file number to which the iterator points.
104     *
105     * The first $fileNumber starts with 0.
106     *
107     * @var int
108     */
109    protected $fileNumber = 0;
110
111    /**
112     * The number of entries currently read from the archive.
113     *
114     * @var int
115     */
116    protected $entriesRead = 0;
117
118    /**
119     * Is true when the archive is read until the end, otherwise false.
120     *
121     * @var bool
122     */
123    protected $completed = false;
124
125    /**
126     * Stores the entries read from the archive.
127     *
128     * The array is not complete when the {@link $completed} variable is set to
129     * false.  The array may be over-complete, so the {@link $entriesRead}
130     * should be checked if the {@link $completed} variable is set to true.
131     *
132     * @var array(ezcArchiveEntry)
133     */
134    protected $entries;
135
136    /**
137     * Direct access to the archive file.
138     *
139     * @var ezcArchiveFile
140     */
141    protected $file = null;
142
143    /**
144     * Holds the options if passed to the open method.
145     *
146     * @var ezcArchiveOptions
147     */
148    protected $options;
149
150    /**
151     * Use the {@link open()} method to get an instance of this class.
152     */
153    private function __construct()
154    {
155    }
156
157    /**
158     * Returns a new ezcArchive instance.
159     *
160     * This method returns a new instance according to the mime-type or
161     * the given $forceType.
162     *
163     * - If $forceType is set to null, this method will try to determine the
164     *   archive format via the file data. Therefore the $forceType can only be
165     *   null when the archive contains data.
166     * - If $forceType is set, it will use the specified algorithm. Even when
167     *   the given archive is from another type than specified.
168     *
169     * @throws ezcArchiveUnknownTypeException if the type of the archive cannot be determined.
170     *
171     * @param string  $archiveName  Absolute or relative path to the archive.
172     * @param int     $forceType    Open the archive with the $forceType
173     *        algorithm. Possible values are: {@link ezcArchive::ZIP},
174     *        {@link ezcArchive::TAR}, {@link ezcArchive::TAR_V7}, {@link ezcArchive::TAR_USTAR},
175     *        {@link ezcArchive::TAR_PAX}, {@link ezcArchive::TAR_GNU}.
176     *        TAR will use the TAR_USTAR algorithm by default.
177     * @param ezcArchiveOptions $options
178     *
179     * @return ezcArchive
180     */
181    public static function open( $archiveName, $forceType = null, ezcArchiveOptions $options = null )
182    {
183        $options = self::initOptions( $options );
184
185        if ( !ezcArchiveFile::fileExists( $archiveName ) && $forceType === null )
186        {
187            throw new ezcArchiveUnknownTypeException( $archiveName );
188        }
189
190        if ( $forceType !== null )
191        {
192            return self::createInstance( $archiveName, $forceType, $options );
193        }
194
195        $h = ezcArchiveFileType::detect( $archiveName );
196
197        while ( $h == ezcArchive::GZIP || $h == ezcArchive::BZIP2 )
198        {
199            if ( $h == ezcArchive::GZIP )
200            {
201                $archiveName = "compress.zlib://$archiveName";
202                $h = ezcArchiveFileType::detect( $archiveName );
203            }
204
205            if ( $h == ezcArchive::BZIP2 )
206            {
207                $archiveName = "compress.bzip2://$archiveName";
208                $h = ezcArchiveFileType::detect( $archiveName );
209            }
210        }
211
212        return self::createInstance( $archiveName, $h, $options );
213    }
214
215    /**
216     * Close the current archive.
217     */
218    public function close()
219    {
220    }
221
222    /**
223     * Sets the property $name to $value.
224     *
225     * Because there are no properties available, this method will always
226     * throw an {@link ezcBasePropertyNotFoundException}.
227     *
228     * @throws ezcBasePropertyNotFoundException if the property does not exist.
229     * @param string $name
230     * @param mixed $value
231     * @ignore
232     */
233    public function __set( $name, $value )
234    {
235        throw new ezcBasePropertyNotFoundException( $name );
236    }
237
238    /**
239     * Returns the property $name.
240     *
241     * Because there are no properties available, this method will always
242     * throw an {@link ezcBasePropertyNotFoundException}.
243     *
244     * @throws ezcBasePropertyNotFoundException if the property does not exist.
245     * @param string $name
246     * @ignore
247     */
248    public function __get( $name )
249    {
250        throw new ezcBasePropertyNotFoundException( $name );
251    }
252
253    /**
254     * Returns the algorithm that is used currently.
255     *
256     * @return int   Possible values are: {@link ezcArchive::ZIP}, {@link ezcArchive::TAR}, {@link ezcArchive::TAR_V7},
257     *               {@link ezcArchive::TAR_USTAR}, {@link ezcArchive::TAR_PAX}, or {@link ezcArchive::TAR_GNU}.
258     */
259    public abstract function getAlgorithm();
260
261    /**
262     * Returns true if writing to the archive is implemented, otherwise false.
263     *
264     * @see isWritable()
265     *
266     * @return bool
267     */
268    public abstract function algorithmCanWrite();
269
270    /**
271     * Returns an instance of the archive with the given type.
272     *
273     * Similar to {@link open()}, but the type is required.
274     *
275     * @param string $archiveName  The path of the archive.
276     * @param int    $type        Open the archive with the $forceType
277     *        algorithm. Possible values are: {@link ezcArchive::ZIP},
278     *        {@link ezcArchive::TAR}, {@link ezcArchive::TAR_V7}, {@link ezcArchive::TAR_USTAR},
279     *        {@link ezcArchive::TAR_PAX}, {@link ezcArchive::TAR_GNU}.
280     *        TAR will use the TAR_USTAR algorithm by default.
281     *
282     * @return ezcArchive  Subclass of ezcArchive: {@link ezcArchiveZip},
283     *                     {@link ezcArchiveV7Tar}, {@link ezcArchivePax},
284     *                     {@link ezcArchiveGnuTar}, or {@link ezcArchiveUstar}.
285     */
286    protected static function createInstance( $archiveName, $type, ezcArchiveOptions $options = null )
287    {
288        $options = self::initOptions( $options );
289
290        if ( $type == self::ZIP )
291        {
292            $af = new ezcArchiveCharacterFile( $archiveName, true, $options->readOnly );
293            return self::getZipInstance( $af );
294        }
295
296        $af = new ezcArchiveBlockFile( $archiveName, true, 512, $options->readOnly );
297        $instance = self::getTarInstance( $af, $type );
298        $instance->options = $options;
299        return $instance;
300    }
301
302    /**
303     * This methods initializes the options by generating an options object if $options is null.
304     *
305     * @param null|ezcArchiveOptions $options
306     *
307     * @return ezcArchiveOptions
308     */
309    private static function initOptions( $options )
310    {
311        if ( $options === null )
312        {
313            $options = new ezcArchiveOptions;
314        }
315        return $options;
316    }
317
318    /**
319     * This method associates a new $options object with this archive.
320     *
321     * @param ezcArchiveOptions $options
322     */
323    public function setOptions( ezcArchiveOptions $options )
324    {
325        $this->options = $options;
326    }
327
328    /**
329     * Open a tar instance.
330     *
331     * This method is made public for testing purposes, and should not be used.
332     *
333     * @param ezcArchiveBlockFile $blockFile
334     * @param int    $type
335     *        The algorithm type. Possible values are:
336     *        {@link ezcArchive::TAR}, {@link ezcArchive::TAR_V7}, {@link ezcArchive::TAR_USTAR},
337     *        {@link ezcArchive::TAR_PAX}, {@link ezcArchive::TAR_GNU}.
338     *        TAR will use the TAR_USTAR algorithm by default.
339     *
340     * @return ezcArchive  Subclass of ezcArchive:
341     *                     {@link ezcArchiveV7Tar}, {@link ezcArchivePax},
342     *                     {@link ezcArchiveGnuTar}, or {@link ezcArchiveUstar}.
343     * @access private
344     */
345    public static function getTarInstance( ezcArchiveBlockFile $blockFile, $type )
346    {
347        switch ( $type )
348        {
349            case self::TAR_V7:
350                return new ezcArchiveV7Tar( $blockFile );
351
352            case self::TAR_USTAR:
353                return new ezcArchiveUstarTar( $blockFile );
354
355            case self::TAR_PAX:
356                return new ezcArchivePaxTar( $blockFile );
357
358            case self::TAR_GNU:
359                return new ezcArchiveGnuTar( $blockFile );
360
361            case self::TAR:
362                return new ezcArchiveUstarTar( $blockFile ); // Default type.
363        }
364
365        return null;
366    }
367
368    /**
369     * Open a zip instance. This method is made public for testing purposes, and
370     * should not be used.
371     *
372     * @param ezcArchiveCharacterFile $charFile  The character file which
373     *                                           contains the archive.
374     * @return ezcArchive  Subclass of ezcArchive: {@link ezcArchiveZip}.
375     * @access private
376     */
377    public static function getZipInstance( ezcArchiveCharacterFile $charFile )
378    {
379        return new ezcArchiveZip( $charFile );
380    }
381
382    /**
383     * Returns true if the iterator points to a valid entry, otherwise false.
384     *
385     * @return bool
386     */
387    public function valid()
388    {
389        return ( $this->fileNumber >= 0 && $this->fileNumber < $this->entriesRead );
390    }
391
392    /**
393     * Rewinds the iterator to the first entry.
394     *
395     * @return void
396     */
397    public function rewind()
398    {
399        $this->fileNumber = 0;
400    }
401
402    /**
403     * Returns the current ezcArchiveEntry if it is valid, otherwise false is returned.
404     *
405     * @return ezcArchiveEntry
406     */
407    public function current()
408    {
409        return ( $this->valid() ? $this->entries[$this->fileNumber] : false );
410    }
411
412    /**
413     * Returns the current key, entry number, if it is valid, otherwise false is returned.
414     *
415     * @return int
416     */
417    public function key()
418    {
419        return ( $this->valid() ? $this->fileNumber : false );
420    }
421
422    /**
423     * Forwards the iterator to the next entry.
424     *
425     * If there is no next entry all iterator methods except for {@link
426     * rewind()} will return false.
427     *
428     * @see rewind()
429     *
430     * @return ezcArchiveEntry  The next entry if it exists, otherwise false.
431     */
432    public function next()
433    {
434        if ( $this->valid() )
435        {
436            $this->fileNumber++;
437            if ( $this->valid() )
438            {
439                return $this->current();
440            }
441
442            if ( !$this->completed )
443            {
444                if ( $this->readCurrentFromArchive() )
445                {
446                    return $this->current();
447                }
448            }
449        }
450
451        return false;
452    }
453
454    /**
455     * Extract the current entry to which the iterator points.
456     *
457     * Extract the current entry to which the iterator points, and return true if the current entry is extracted.
458     * If the iterator doesn't point to a valid entry, this method returns false.
459     *
460     * True if the file is extracted correctly, otherwise false.
461     *
462     * @param string $target
463     *        The full path to which the target should be extracted.
464     * @param bool $keepExisting
465     *        True if the file shouldn't be overwritten if they already exist.
466     *        For the opposite behaviour, false should be given.
467     *
468     * @throws ezcArchiveValueException     if the archive contains invalid values.
469     * @throws ezcBaseFileNotFoundException if the link cannot be found.
470     *
471     * @return bool
472     */
473    public function extractCurrent( $target, $keepExisting = false )
474    {
475        if ( $this->file === null )
476        {
477            throw new ezcArchiveException( "The archive is closed" );
478        }
479
480        if ( !$this->valid() )
481        {
482            return false;
483        }
484
485        $isWindows = ( substr( php_uname( 's' ), 0, 7 ) == 'Windows' ) ? true : false;
486        $entry = $this->current();
487        $type = $entry->getType();
488        $fileName = $target . DIRECTORY_SEPARATOR. $entry->getPath();
489
490        if ( $type == ezcArchiveEntry::IS_LINK )
491        {
492            $linkName = $target . DIRECTORY_SEPARATOR . $entry->getLink();
493            if ( !file_exists( $linkName ) )
494            {
495                throw new ezcBaseFileNotFoundException( $linkName, "link", "Hard link could not be created." );
496            }
497        }
498
499        $this->createDefaultDirectory( $fileName );
500
501        if ( !$keepExisting || ( !is_link( $fileName ) && !file_exists( $fileName ) ) )
502        {
503            if ( ( file_exists( $fileName ) || is_link( $fileName ) ) && !is_dir( $fileName ) )
504            {
505                unlink ( $fileName );
506            }
507
508            if ( !file_exists( $fileName ) ) // For example, directories are not removed.
509            {
510                switch ( $type )
511                {
512                    case ezcArchiveEntry::IS_CHARACTER_DEVICE:
513                        if ( ezcBaseFeatures::hasFunction( 'posix_mknod' ) )
514                        {
515                            posix_mknod( $fileName, POSIX_S_IFCHR, $entry->getMajor(), $entry->getMinor() );
516                        }
517                        else
518                        {
519                            throw new ezcArchiveValueException( $type );
520                        }
521                        break;
522
523                    case ezcArchiveEntry::IS_BLOCK_DEVICE:
524                        if ( ezcBaseFeatures::hasFunction( 'posix_mknod' ) )
525                        {
526                            posix_mknod( $fileName, POSIX_S_IFBLK, $entry->getMajor(), $entry->getMinor() );
527                        }
528                        else
529                        {
530                            throw new ezcArchiveValueException( $type );
531                        }
532                        break;
533
534                    case ezcArchiveEntry::IS_FIFO:
535                        if ( ezcBaseFeatures::hasFunction( 'posix_mknod' ) )
536                        {
537                            posix_mknod( $fileName, POSIX_S_IFIFO );
538                        }
539                        else
540                        {
541                            throw new ezcArchiveValueException( $type );
542                        }
543                        break;
544
545                    case ezcArchiveEntry::IS_SYMBOLIC_LINK:
546                        if ( $isWindows )
547                        {
548                            // FIXME.. need to be sure that target file
549                            // already extracted before copying it to link destination.
550                            $sourcePath = dirname( $fileName ) . '/' . $entry->getLink();
551                            $fileName = str_replace( '/', '\\', $fileName );
552                            copy( $sourcePath, $fileName );
553                        }
554                        else
555                        {
556                            symlink( $entry->getLink(), $fileName );
557                        }
558                        break;
559
560                    case ezcArchiveEntry::IS_LINK:
561                        if ( $isWindows )
562                        {
563                            copy( $target . DIRECTORY_SEPARATOR . $entry->getLink(), $fileName );
564                        }
565                        else
566                        {
567                            link( $target . DIRECTORY_SEPARATOR . $entry->getLink(), $fileName );
568                        }
569                        break;
570
571                    case ezcArchiveEntry::IS_DIRECTORY:
572                        $permissions = $entry->getPermissions();
573
574                        if ( $permissions === null || $permissions === false )
575                        {
576                            $permissions = '0777';
577                        }
578                        mkdir( $fileName, octdec( $permissions ), true );
579                        break;
580
581                    case ezcArchiveEntry::IS_FILE:
582                        $this->writeCurrentDataToFile( $fileName );
583                        break;
584
585                    default:
586                        throw new ezcArchiveValueException( $type );
587                }
588
589                if ( $type == ezcArchiveEntry::IS_SYMBOLIC_LINK &&
590                     ezcBaseFeatures::hasFunction( 'posix_geteuid' ) &&
591                     posix_geteuid() == 0 )
592                {
593                    $user = $entry->getUserId();
594                    $group = $entry->getGroupId();
595                    @lchown( $fileName, $user );
596                    @lchgrp( $fileName, $group );
597                }
598
599                // Change the username and group if the filename exists and if
600                // the intention is to keep it as a file. A zip archive
601                // stores the symlinks in a file; thus don't change these.
602                if ( file_exists( $fileName ) && ( $type == ezcArchiveEntry::IS_FILE || $type == ezcArchiveEntry::IS_DIRECTORY ) )
603                {
604                    $group = $entry->getGroupId();
605                    $user  = $entry->getUserId();
606                    $time  = $entry->getModificationTime();
607                    $perms = octdec( $entry->getPermissions() );
608
609                    if ( $this->options && $this->options->extractCallback )
610                    {
611                        $this->options->extractCallback->{$type == ezcArchiveEntry::IS_DIRECTORY ? 'createDirectoryCallback' : 'createFileCallback'}( $fileName, $perms, $user, $group );
612                    }
613
614                    if ( ezcBaseFeatures::hasFunction( 'posix_geteuid' ) &&
615                         posix_geteuid() === 0 )
616                    {
617                        @chgrp( $fileName, $group );
618                        @chown( $fileName, $user );
619                    }
620
621                    if ( $perms != false )
622                    {
623                        chmod( $fileName, $perms );
624                    }
625
626                    touch( $fileName, $time );
627                }
628            }
629
630            return true;
631        }
632
633        return false;
634    }
635
636    /**
637     * Search for the entry number.
638     *
639     * The two parameters here are the same as the PHP {@link http://www.php.net/fseek fseek()} method.
640     * The internal iterator position will be set by $offset added to $whence iterations forward.
641     * Where $whence is:
642     * - SEEK_SET, Set the position equal to $offset.
643     * - SEEK_CUR, Set the current position plus $offset.
644     * - SEEK_END, Set the last file in archive position plus $offset.
645     *
646     * This method returns true if the new position is valid, otherwise false.
647     *
648     * @throws ezcArchiveException
649     *         if the archive is closed
650     * @param int    $offset
651     * @param int    $whence
652     * @return bool
653     */
654    public function seek( $offset, $whence = SEEK_SET )
655    {
656        if ( $this->file === null )
657        {
658            throw new ezcArchiveException( "The archive is closed" );
659        }
660
661        // Cannot trust the current position if the current position is invalid.
662        if ( $whence == SEEK_CUR && $this->valid() == false )
663        {
664            return false;
665        }
666
667        if ( $whence == SEEK_END && !$this->completed )
668        {
669            // read the entire archive.
670             $this->fileNumber = $this->entriesRead;
671             while ( $this->readCurrentFromArchive() )
672             {
673                 $this->fileNumber++;
674             }
675        }
676
677        switch ( $whence )
678        {
679            case SEEK_SET:
680                $requestedFileNumber = $offset;
681                break;
682
683            case SEEK_CUR:
684                $requestedFileNumber = $offset + $this->fileNumber;
685                break;
686
687            case SEEK_END:
688                $requestedFileNumber = $offset + $this->entriesRead - 1;
689                break;
690
691            default:
692                return false; // Invalid whence.
693        }
694
695        $this->fileNumber = $requestedFileNumber;
696        if ( $this->valid() )
697        {
698            return true;
699        }
700
701        if ( !$this->completed )
702        {
703            $this->fileNumber = $this->entriesRead - 1;
704
705            while ( $this->fileNumber != $requestedFileNumber )
706            {
707                $this->fileNumber++;
708                if ( !$this->readCurrentFromArchive() )
709                {
710                    break;
711                }
712            }
713
714            return $this->valid();
715        }
716
717        return false;
718    }
719
720    /**
721     * Creates all the directories needed to create the file $file.
722     *
723     * @param string $file  Path to a file, where all the base directory names will be created.
724     */
725    protected function createDefaultDirectory( $file )
726    {
727        // Does the directory exist?
728        $dirName = dirname( $file );
729
730        if ( !file_exists( $dirName ) )
731        {
732            // Try to create the directory.
733            if ( substr( php_uname( 's' ), 0, 7 ) == 'Windows' )
734            {
735                // make all slashes to be '/'
736                $dirName = str_replace( '/', '\\', $dirName );
737            }
738
739            // Call the callback, to see whether we need to change permissions
740            $permissions = 0777;
741            $dummy = null;
742            if ( $this->options && $this->options->extractCallback )
743            {
744                $this->options->extractCallback->createDirectoryCallback( $dirName, $permissions, $dummy, $dummy );
745            }
746
747            mkdir( $dirName, $permissions, true );
748        }
749    }
750
751    /**
752     * Appends a file to the archive after the current entry.
753     *
754     * One or multiple files can be added directly after the current file.
755     * The remaining entries after the current are removed from the archive!
756     *
757     * The $files can either be a string or an array of strings. Which, respectively, represents a
758     * single file or multiple files.
759     *
760     * $prefix specifies the begin part of the $files path that should not be included in the archive.
761     * The files in the archive are always stored relatively.
762     *
763     * Example:
764     * <code>
765     * $tar = ezcArchive( "/tmp/my_archive.tar", ezcArchive::TAR );
766     *
767     * // Append two files to the end of the archive.
768     * $tar->seek( 0, SEEK_END );
769     * $tar->appendToCurrent( array( "/home/rb/file1.txt", "/home/rb/file2.txt" ), "/home/rb/" );
770     * </code>
771     *
772     * When multiple files are added to the archive at the same time, thus using an array, does not
773     * necessarily produce the same archive as repeatively adding one file to the archive.
774     * For example, the Tar archive format, can detect that files hardlink to each other and will store
775     * it in a more efficient way.
776     *
777     * @throws ezcArchiveWriteException  if one of the files cannot be written to the archive.
778     * @throws ezcFileReadException      if one of the files cannot be read from the local filesystem.
779     *
780     * @param string|array(string) $files  Array or a single path to a file.
781     * @param string $prefix               First part of the path used in $files.
782     * @return bool
783     */
784    public abstract function appendToCurrent( $files, $prefix );
785
786    /**
787     * Appends a file or directory to the end of the archive. Multiple files or directory can
788     * be added to the archive when an array is used as input parameter.
789     *
790     * @see appendToCurrent()
791     *
792     * @throws ezcArchiveWriteException  if one of the files cannot be written to the archive.
793     * @throws ezcFileReadException      if one of the files cannot be read from the local filesystem.
794     *
795     * @param string|array(string) $files  Array or a single path to a file.
796     * @param string $prefix               First part of the path used in $files.
797     * @return bool
798     */
799    public abstract function append( $files, $prefix );
800
801    /**
802     * Truncates the archive to $fileNumber of files.
803     *
804     * The $fileNumber parameter specifies the amount of files that should remain.
805     * If the default value, zero, is used then the entire archive file is cleared.
806     *
807     * @param int $fileNumber
808     * @return bool
809     */
810    public abstract function truncate( $fileNumber = 0 );
811
812    /**
813     * Writes the file data from the current entry to the given file.
814     *
815     * @param string $targetPath  The absolute or relative path of the target file.
816     * @return void
817     */
818    protected abstract function writeCurrentDataToFile( $targetPath );
819
820    /**
821     * Returns an array that lists the content of the archive.
822     *
823     * Use the getArchiveEntry method to get more information about an entry.
824     *
825     * @see __toString()
826     *
827     * @throws ezcArchiveException
828     *         if the archive is closed
829     * @return array(string)
830     */
831    public function getListing()
832    {
833        if ( $this->file === null )
834        {
835            throw new ezcArchiveException( "The archive is closed" );
836        }
837
838        $result = array();
839        $this->rewind();
840
841        do
842        {
843            $entry = $this->current();
844            $result[] = rtrim( $entry->__toString(), "\n" ); // remove newline.
845        } while ( $this->next() );
846
847        return $result;
848    }
849
850    /**
851     * Returns a string which represents all the entries from the archive.
852     *
853     * @throws ezcArchiveException
854     *         if the archive is closed
855     * @return string
856     */
857    public function __toString()
858    {
859        if ( $this->file === null )
860        {
861            throw new ezcArchiveException( "The archive is closed" );
862        }
863
864        $result = "";
865        $this->rewind();
866
867        while ( $this->valid() )
868        {
869            $result .= $this->current()->__toString() . "\n";
870            $this->next();
871        }
872
873        return $result;
874    }
875
876    /**
877     * Extract entries from the archive to the target directory.
878     *
879     * All entries from the archive are extracted to the target directory.
880     * By default the files in the target directory are overwritten.
881     * If the $keepExisting is set to true, the files from the archive will not overwrite existing files.
882     *
883     * @see extractCurrent()
884     *
885     * @throws ezcArchiveException
886     *         if the archive is closed
887     * @throws ezcArchiveEmptyException
888     *         if the archive is invalid
889     * @param string $target     Absolute or relative path of the directory.
890     * @param bool $keepExisting If set to true then the file will be overwritten, otherwise not.
891     * @return void
892     */
893    public function extract( $target, $keepExisting = false )
894    {
895        if ( $this->file === null )
896        {
897            throw new ezcArchiveException( "The archive is closed" );
898        }
899
900        $this->rewind();
901        if ( !$this->valid() )
902        {
903            throw new ezcArchiveEmptyException( );
904        }
905
906        while ( $this->valid() )
907        {
908            $this->extractCurrent( $target, $keepExisting );
909            $this->next();
910        }
911    }
912
913    /**
914     * Returns true if the current archive is empty, otherwise false.
915     *
916     * @return bool
917     */
918    public function isEmpty()
919    {
920        return ( $this->entriesRead == 0 );
921    }
922
923    /**
924     * Get the file entries from the archive.
925     *
926     * @param string|array(string) $files  Array or a single path to a file.
927     * @param string $prefix               First part of the path used in $files.
928     *
929     * @return ezcArchiveEntry
930     */
931    protected function getEntries( $files, $prefix )
932    {
933        if ( !is_array( $files ) )
934        {
935            $files = array( $files );
936        }
937
938        // Check whether the files are correct.
939        foreach ( $files as $file )
940        {
941            if ( !file_exists( $file ) && !is_link( $file ) )
942            {
943                throw new ezcBaseFileNotFoundException( $file );
944            }
945        }
946
947        // Search for all the entries, because otherwise hardlinked files show up as an ordinary file.
948        return ezcArchiveEntry::getEntryFromFile( $files, $prefix );
949    }
950}
951?>