PageRenderTime 41ms CodeModel.GetById 16ms app.highlight 18ms RepoModel.GetById 1ms app.codeStats 1ms

/Archive/src/archive.php

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