| <?php |
| /** |
| * File containing the ezcArchiveFile class. |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| * @package Archive |
| * @version //autogentag// |
| * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0 |
| * @access private |
| */ |
| |
| /** |
| * The ezcArchiveFile should implement the common interface between the |
| * ezcArchiveBlockFile and ezcArchiveCharacterFile. |
| * |
| * @package Archive |
| * @version //autogentag// |
| * @access private |
| */ |
| abstract class ezcArchiveFile implements Iterator |
| { |
| /** |
| * The file is read-only. |
| * The file permissions can be set to read-only or file is compressed with a |
| * stream that can only be read. E.g. bzip2. |
| */ |
| const READ_ONLY = 1; |
| |
| /** |
| * The file is write-only. |
| * The file permissions can be set to write-only or file should be compressed with a |
| * stream that can only write. E.g. bzip2. |
| */ |
| const WRITE_ONLY = 2; |
| |
| /** |
| * The file is either read or append mode. |
| * Some compressed streams (zlib) do not support reading and writing. But seperate reading |
| * and appending does work. |
| */ |
| const READ_APPEND = 3; |
| |
| /** |
| * The file is opened in a read and write mode. |
| */ |
| const READ_WRITE = 4; |
| |
| /** |
| * The mode the file is opened in. It has one of the following constant values: |
| * READ_ONLY, WRITE_ONLY, READ_APPEND, or READ_WRITE. |
| * |
| * @var int |
| */ |
| protected $fileAccess = null; |
| |
| /** |
| * The current resource of the opened file. |
| * If the file is closed, this resource should point to NULL. |
| * |
| * @var resource |
| */ |
| protected $fp = null; |
| |
| /** |
| * The name of the file. |
| * |
| * @var string |
| */ |
| protected $fileName; |
| |
| /** |
| * True when the current file does not have any blocks, otherwise false. |
| * |
| * @var boolean |
| */ |
| protected $isEmpty; |
| |
| /** |
| * True if the file-pointer supports seeking, otherwise false. |
| * For example, files that use the bzip2 stream cannot seek. |
| * |
| * @var boolean |
| */ |
| protected $fileMetaData; |
| |
| /** |
| * True when the current block is valid, otherwise false. |
| * |
| * @var boolean |
| */ |
| protected $isValid = false; |
| |
| /** |
| * Read-mode for the archive file. |
| */ |
| const SWITCH_READ = 0; |
| |
| /** |
| * Append-mode for the archive file. |
| */ |
| const SWITCH_APPEND = 1; |
| |
| /** |
| * Switch for read-mode and append-mode. |
| * |
| * @var int |
| */ |
| protected $readAppendSwitch; |
| |
| /** |
| * Is the file new. |
| * |
| * @var bool |
| */ |
| protected $isNew; |
| |
| /** |
| * Is the file modified. |
| * |
| * @var bool |
| */ |
| protected $isModified; |
| |
| /** |
| * Opens the specified archive. |
| * |
| * If $createIfNotExist is true, then the file will be created if it does |
| * not exist. |
| * |
| * @param string $fileName |
| * @param bool $createIfNotExist |
| * @param bool $readOnly |
| * @return bool |
| */ |
| protected function openFile( $fileName, $createIfNotExist, $readOnly = false ) |
| { |
| if ( !$readOnly && $createIfNotExist && !self::fileExists( $fileName ) ) |
| { |
| $this->isNew = true; |
| $this->isEmpty = true; |
| if ( !self::touch( $fileName ) ) |
| { |
| throw new ezcBaseFilePermissionException( self::getPureFileName( $fileName ), ezcBaseFilePermissionException::WRITE ); |
| } |
| } |
| else |
| { |
| $this->isNew = false; |
| } |
| |
| // Try to open it in read and write mode. |
| $opened = false; |
| if ( !$readOnly ) |
| { |
| $this->fp = @fopen( $fileName, "r+b" ); |
| if ( $this->fp ) |
| { |
| $this->fileAccess = self::READ_WRITE; |
| $opened = true; |
| } |
| } |
| |
| if ( !$opened ) |
| { |
| // Try to open it in read-only mode. |
| $this->fp = @fopen( $fileName, "rb" ); |
| $this->fileAccess = self::READ_ONLY; |
| |
| // Check if we opened the file. |
| if ( !$this->fp ) |
| { |
| if ( !self::fileExists( $fileName ) ) |
| { |
| throw new ezcBaseFileNotFoundException( $fileName ); |
| } |
| |
| // Cannot read the file. |
| throw new ezcBaseFilePermissionException( $fileName, ezcBaseFilePermissionException::READ ); |
| } |
| } |
| |
| $this->fileMetaData = stream_get_meta_data( $this->fp ); |
| |
| // Hardcode BZip2 to read-only. |
| // For some reason we can open the file in read-write mode, but we cannot rewind the fp. Strange.. |
| if ( $this->fileMetaData["wrapper_type"] == "BZip2" ) |
| { |
| $this->fileAccess = self::READ_ONLY; |
| } |
| |
| // Why is it read only? |
| if ( !$readOnly && $this->fileAccess == self::READ_ONLY ) |
| { |
| if ( $this->fileMetaData["wrapper_type"] == "ZLIB" || $this->fileMetaData["wrapper_type"] == "BZip2" ) |
| { |
| // Append mode available? |
| $b = @fopen( $fileName, "ab" ); |
| if ( $b !== false ) |
| { |
| // We have also a write-only mode. |
| fclose( $b ); |
| |
| // The file is either read-only or write-only. |
| $this->fileAccess = self::READ_APPEND; |
| $this->readAppendSwitch = self::SWITCH_READ; |
| } |
| else |
| { |
| // Maybe we should write only to the archive. |
| // Test this only, when the archive is new. |
| |
| if ( $this->isNew ) |
| { |
| $b = @fopen( $fileName, "wb" ); |
| if ( $b !== false ) |
| { |
| // XXX Clean up. |
| $this->fp = $b; |
| |
| $this->isEmpty = true; |
| $this->fileAccess = self::WRITE_ONLY; |
| |
| $this->fileName = $fileName; |
| $this->isModified = false; |
| |
| return true; |
| } |
| } |
| } |
| } |
| } |
| |
| // Check if the archive is empty. |
| if ( fgetc( $this->fp ) === false ) |
| { |
| $this->isEmpty = true; |
| } |
| else |
| { |
| $this->rewind(); |
| $this->isEmpty = false; |
| } |
| |
| $this->fileName = $fileName; |
| $this->isModified = false; |
| } |
| |
| /** |
| * Returns the file name or file path. |
| * |
| * @return string |
| */ |
| public function getFileName() |
| { |
| return $this->fileName; |
| } |
| |
| /** |
| * Switch to write mode. |
| */ |
| public function switchWriteMode() |
| { |
| // Switch only when we are in read (only) mode. |
| if ( $this->fileAccess == self::READ_APPEND && $this->readAppendSwitch == self::SWITCH_READ ) |
| { |
| fclose( $this->fp ); |
| $this->fp = @fopen( $this->fileName, "ab" ); |
| if ( $this->fp === false ) |
| { |
| throw new ezcBaseFilePermissionException( self::getPureFileName( $this->fileName ), ezcBaseFilePermissionException::WRITE, "Cannot switch to write mode" ); |
| } |
| $this->readAppendSwitch = self::SWITCH_APPEND; |
| } |
| } |
| |
| /** |
| * Switch to read mode. |
| * |
| * @param int $pos Position to seek to; not used |
| */ |
| public function switchReadMode( $pos = 0 ) |
| { |
| // Switch only when we are in write (only) mode. |
| if ( $this->fileAccess == self::READ_APPEND && $this->readAppendSwitch == self::SWITCH_APPEND ) |
| { |
| fclose( $this->fp ); |
| |
| $this->fp = fopen( $this->fileName, "rb" ); |
| |
| if ( $this->fp === false ) |
| { |
| throw new ezcBaseFilePermissionException( self::getPureFileName( $this->fileName ), ezcBaseFilePermissionException::READ, "Cannot switch back to read mode" ); |
| } |
| $this->readAppendSwitch = self::SWITCH_READ; |
| |
| $this->positionSeek( 0, SEEK_END ); |
| |
| // Doesn't Make sense, Seek-end should be at the end! |
| while ( fgetc( $this->fp ) !== false ); |
| } |
| } |
| |
| /** |
| * Returns if the file access is in append mode. |
| * |
| * @return bool |
| */ |
| public function isReadOnlyWriteOnlyStream() |
| { |
| return $this->fileAccess == self::READ_APPEND; |
| } |
| |
| |
| |
| /** |
| * Touches the specified file (sets the access and modification time). |
| * |
| * PHP system touch doesn't work correctly with the compress.zlib file. |
| * |
| * @param string $fileName |
| * @return bool |
| */ |
| public static function touch( $fileName ) |
| { |
| return touch( self::getPureFileName( $fileName ) ); |
| } |
| |
| /** |
| * Returns if the specified file exists. |
| * |
| * @param string $fileName |
| * @return bool |
| */ |
| public static function fileExists( $fileName ) |
| { |
| return file_exists( self::getPureFileName( $fileName ) ); |
| } |
| |
| /** |
| * Returns the specified file name without any filters or compression stream. |
| * |
| * @param string $fileName |
| * @return string |
| */ |
| private static function getPureFileName( $fileName ) |
| { |
| // TODO: Multistream goes wrong. |
| if ( strncmp( $fileName, "compress.zlib://", 16 ) == 0 ) |
| { |
| return substr( $fileName, 16 ); |
| } |
| |
| if ( strncmp( $fileName, "compress.bzip2://", 17 ) == 0 ) |
| { |
| return substr( $fileName, 17 ); |
| } |
| |
| return $fileName; |
| } |
| |
| /** |
| * Rewind the current file, and the current() method will return the |
| * data from the first block, if available. |
| */ |
| public function rewind() |
| { |
| if ( !is_null( $this->fp ) ) |
| { |
| $this->isValid = true; |
| |
| if ( !$this->fileMetaData["seekable"] ) |
| { |
| fclose( $this->fp ); |
| $this->fp = fopen( $this->fileMetaData["uri"], $this->fileMetaData["mode"] ); |
| } |
| else |
| { |
| rewind( $this->fp ); |
| } |
| |
| $this->next(); |
| } |
| else |
| { |
| $this->isValid = false; |
| } |
| } |
| |
| /** |
| * Seeks in the file to/by the specified position. |
| * |
| * Ways of seeking ($whence): |
| * - SEEK_SET - $pos is absolute, seek to that position in the file |
| * - SEEK_CUR - $pos is relative, seek by $pos bytes from the current position |
| * |
| * @throws ezcArchiveException |
| * if trying to use SEEK_END for $whence |
| * @param int $pos |
| * @param int $whence |
| * @return int If seek was successful or not |
| */ |
| protected function positionSeek( $pos, $whence = SEEK_SET ) |
| { |
| // Seek the end of the file in a write only file always succeeds. |
| if ( $this->fileAccess == self::WRITE_ONLY && $pos == 0 && $whence == SEEK_END ) |
| { |
| return true; |
| } |
| |
| if ( $this->fileMetaData["seekable"] ) |
| { |
| /** |
| * Ugh, for some reason fseek starts throwing warnings for |
| * zlib streams with SEEK_END. And there is no way to know this |
| * upfront, so we need to use @ here. #fail. |
| */ |
| return @fseek( $this->fp, $pos, $whence ); |
| } |
| else |
| { |
| switch ( $whence ) |
| { |
| case SEEK_SET: |
| $transPos = $pos; |
| break; |
| |
| case SEEK_CUR: |
| $transPos = $pos + ftell( $this->fp ); |
| break; |
| |
| case SEEK_END: |
| throw new ezcArchiveException( "SEEK_END in a non-seekable file is not supported (yet)." ); |
| } |
| |
| $cur = ftell( $this->fp ); |
| if ( $transPos < $cur ) |
| { |
| fclose( $this->fp ); |
| $this->fp = fopen( $this->fileMetaData["uri"], $this->fileMetaData["mode"] ); |
| |
| $cur = 0; |
| } |
| |
| for ( $i = $cur; $i < $transPos; $i++ ) |
| { |
| $c = fgetc( $this->fp ); |
| if ( $c === false ) |
| { |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| } |
| |
| /** |
| * Returns the current file access mode. |
| * |
| * @var int |
| */ |
| public function getFileAccess() |
| { |
| return $this->fileAccess; |
| } |
| |
| /** |
| * Returns if the file is in read-only mode. |
| * |
| * @var bool |
| */ |
| public function isReadOnly() |
| { |
| return $this->fileAccess == self::READ_ONLY; |
| } |
| |
| /** |
| * Returns if the file is new. |
| * |
| * @var bool |
| */ |
| public function isNew() |
| { |
| return $this->isNew; |
| } |
| |
| /** |
| * Returns if the file is modified. |
| * |
| * @var bool |
| */ |
| public function isModified() |
| { |
| return $this->isModified; |
| } |
| |
| /** |
| * Closes the file. |
| */ |
| public function close() |
| { |
| if ( is_resource( $this->fp ) ) |
| { |
| fclose( $this->fp ); |
| $this->fp = null; |
| } |
| } |
| } |
| ?> |