myaac/system/libs/pot/OTS_FileLoader.php
2017-05-01 20:05:36 +02:00

362 lines
8.5 KiB
PHP

<?php
/**#@+
* @version 0.0.6
* @since 0.0.6
*/
/**
* Code in this file bases on oryginal OTServ binary format loading C++ code (fileloader.h, fileloader.cpp).
*
* @package POT
* @version 0.1.3
* @author Wrzasq <wrzasq@gmail.com>
* @copyright 2007 - 2008 (C) by Wrzasq
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU Lesser General Public License, Version 3
*/
/**
* Universal OTServ binary formats reader.
*
* <p>
* This class is general class that handle node-based binary OTServ files. It provides no file format-related logic, only loading nodes from files. To open given file format you need to use proper class like {@link OTS_OTBMFile OTS_OTBMFile} or {@link OTS_ItemsList OTS_ItemsList} classes.
* </p>
*
* <p>
* This class is mostly usefull when you create own extensions for POT code.
* </p>
*
* @package POT
* @version 0.1.3
* @property-write IOTS_FileCache $cacheDriver Cache driver.
*/
class OTS_FileLoader
{
/**
* Start of node.
*/
const NODE_START = 0xFE;
/**
* End of node.
*/
const NODE_END = 0xFF;
/**
* Escape another special byte.
*/
const ESCAPE_CHAR = 0xFD;
/**
* File handle.
*
* @var resource
*/
private $file;
/**
* Root node.
*
* @var OTS_FileNode
*/
protected $root;
/**
* Cache handler.
*
* @var IOTS_FileCache
*/
protected $cache;
/**
* Magic PHP5 method.
*
* <p>
* Allows object serialisation.
* </p>
*
* @version 0.0.6
* @since 0.0.6
* @return array List of properties that should be saved.
*/
public function __sleep()
{
return array('root', 'cache');
}
/**
* Creates clone of object.
*
* <p>
* Copy of object needs to have different ID.
* </p>
*
* @version 0.0.6
* @since 0.0.6
*/
public function __clone()
{
// clones node tree
$this->root = clone $this->root;
}
/**
* Magic PHP5 method.
*
* <p>
* Allows object importing from {@link http://www.php.net/manual/en/function.var-export.php var_export()}.
* </p>
*
* @version 0.0.6
* @since 0.0.6
* @param array $properties List of object properties.
*/
public static function __set_state($properties)
{
$object = new self();
// loads properties
foreach($properties as $name => $value)
{
$object->$name = $value;
}
return $object;
}
/**
* Sets cache handler.
*
* <p>
* You have to set cache driver before loading/saving any file in order to apply it to that file.
* </p>
*
* @param IOTS_FileCache $cache Cache handler (leave this parameter if you want to unset caching).
*/
public function setCacheDriver(IOTS_FileCache $cache = null)
{
$this->cache = $cache;
}
/**
* Opens file.
*
* @version 0.1.3
* @param string $file Filepath.
* @throws E_OTS_FileLoaderError When error occurs during file operation.
*/
public function loadFile($file)
{
// attemts to read from cache
if( isset($this->cache) )
{
$this->root = $this->cache->readCache( md5_file($file) );
// checks if cache was loaded
if( isset($this->root) )
{
return;
}
}
// opens for read in binary mode
$this->file = fopen($file, 'rb');
if($this->file)
{
// reads file version
$version = unpack('V', fread($this->file, 4) );
if($version[1] > 0)
{
throw new E_OTS_FileLoaderError(E_OTS_FileLoaderError::ERROR_INVALID_FILE_VERSION);
}
$this->safeSeek(4);
if( $this->readByte() != self::NODE_START)
{
throw new E_OTS_FileLoaderError(E_OTS_FileLoaderError::ERROR_INVALID_FORMAT);
}
// reads root node
$this->root = new OTS_FileNode();
$this->parseNode($this->root);
fclose($this->file);
// writes new cache
if( isset($this->cache) )
{
$this->cache->writeCache( md5_file($file), $this->root);
}
}
// failed to open the file
else
{
throw new E_OTS_FileLoaderError(E_OTS_FileLoaderError::ERROR_CAN_NOT_OPEN);
}
}
/**
* Loades node from file.
*
* @param OTS_FileNode $node Node into which file fragment should be parsed.
* @throws E_OTS_FileLoaderError When error occurs during file operation.
*/
private function parseNode(OTS_FileNode $node)
{
$current = $node;
// reads node type
$current->setType( $this->readByte() );
$buffer = '';
$propertiesSet = false;
while(true)
{
// reads single byte from file
$byte = $this->readByte();
switch($byte)
{
// start of new node
case self::NODE_START:
// reads child node
$child = new OTS_FileNode();
$current->setBuffer($buffer);
$propertiesSet = true;
$current->setChild($child);
$this->parseNode($child);
break;
// end of current node
case self::NODE_END:
if(!$propertiesSet)
{
$current->setBuffer($buffer);
}
// end of file
if($node === $this->root)
{
return;
}
switch( $this->readByte() )
{
// starts next node
case self::NODE_START:
$next = new OTS_FileNode();
$current->setNext($next);
$current = $next;
$current->setType( $this->readByte() );
$propertiesSet = false;
$buffer = '';
break;
// up 1 level and move 1 position back
case self::NODE_END:
$this->safeSeek( $this->safeTell() - 1);
return;
// invliad file format
default:
throw new E_OTS_FileLoaderError(E_OTS_FileLoaderError::ERROR_INVALID_FORMAT);
}
break;
// ignores next character
case self::ESCAPE_CHAR:
$buffer .= chr( $this->readByte() );
break;
// normal character
default:
$buffer .= chr($byte);
}
}
}
/**
* Reads single byte from file.
*
* @return int Read value.
* @throws E_OTS_FileLoaderError When error occurs during file operation.
*/
private function readByte()
{
$value = fgetc($this->file);
// checks eof
if($value === false)
{
throw new E_OTS_FileLoaderError(E_OTS_FileLoaderError::ERROR_EOF);
}
return ord($value);
}
/**
* Seeks file pointer into given position.
*
* @param int $pos Seek position.
* @throws E_OTS_FileLoaderError When error occurs during file operation.
*/
private function safeSeek($pos)
{
if( fseek($this->file, $pos, SEEK_SET) == -1)
{
// error occured
throw new E_OTS_FileLoaderError(E_OTS_FileLoaderError::ERROR_SEEK_ERROR);
}
}
/**
* Returns current position in file.
*
* @return int Position in file.
* @throws E_OTS_FileLoaderError When error occurs during file operation.
*/
private function safeTell()
{
// reads file position
$pos = ftell($this->file);
// checks if operation succeeded
if($pos === false)
{
throw new E_OTS_FileLoaderError(E_OTS_FileLoaderError::ERROR_TELL_ERROR);
}
return $pos;
}
/**
* Magic PHP5 method.
*
* @version 0.1.0
* @since 0.1.0
* @param string $name Property name.
* @param mixed $value Property value.
* @throws OutOfBoundsException For non-supported properties.
*/
public function __set($name, $value)
{
switch($name)
{
case 'cacheDriver':
$this->setCacheDriver($value);
break;
// not-supported
default:
throw new OutOfBoundsException();
}
}
}
/**#@-*/
?>