blob: 6488862ed8341894cc1380bffd391c76e6ad38fd [file] [log] [blame]
<?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;
}
}
}
?>