<?php
/**************************************************
* NZBirc v1
* Copyright (c) 2006 Harry Bragg
* tiberious.org
* Module: store
**************************************************
*
* Full GPL License: <http://www.gnu.org/licenses/gpl.txt>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
class Net_SmartIRC_module_store extends Net_SmartIRC_module_base
{
// default module variable
var $name = 'store';
var $version = 'v0.3';
var $description = 'saves variables to an xml file, and loads them again';
/**
* Store Module
*
* STARTCONFIG
* @var string filename - Filename to store the variables in
* @var string workerFilename - Filename to communicate with the worker thread
* @var string folder - Folder for all the storage variables
* ENDCONFIG
*/
var $_config = array(
'filename' => 'store.xml',
'workerFilename' => 'worker.xml',
'folder' => 'store/'
);
/**
* @var XML_Serializer
* @access private
*/
var $_toXML;
/**
* @var XML_UnSerializer
* @access private
*/
var $_fromXML;
var $_def = array(
'getRevision' => '/<revision _type="integer">(\d+)<\/revision>/i',
'getUpdated' => '/<updated _type="integer">(\d+)<\/updated>/i'
);
function _registerCommands()
{
global $irc;
$options = array(
XML_SERIALIZER_OPTION_INDENT => ' ',
XML_SERIALIZER_OPTION_RETURN_RESULT => true,
XML_SERIALIZER_OPTION_ATTRIBUTES_KEY => '_attributes',
XML_SERIALIZER_OPTION_TYPEHINTS => true,
XML_SERIALIZER_OPTION_ROOT_NAME => 'store',
XML_SERIALIZER_OPTION_XML_DECL_ENABLED => true,
XML_SERIALIZER_OPTION_XML_ENCODING => $irc->config->encoding
);
$this->_toXML = &new XML_Serializer( $options );
$options = array(
XML_UNSERIALIZER_OPTION_RETURN_RESULT => true,
XML_UNSERIALIZER_OPTION_GUESS_TYPES => true,
XML_UNSERIALIZER_OPTION_ENCODING_SOURCE => $irc->config->encoding
);
$this->_fromXML = &new XML_Unserializer( $options );
// do file/folder checks
if ( !is_dir( $irc->config->userPath.$this->_config['folder'] ) )
{
$irc->modDebug( 'store', 'Found no store folder, importing old files and creating new files...', __FILE__, __LINE__ );
// create directory
if (mkdir( $irc->config->userPath.$this->_config['folder'], 0755, true ))
{
$irc->modDebug( 'store', 'Created folder: '.$irc->config->userPath.$this->_config['folder'], __FILE__, __LINE__ );
}
else
{
$irc->modDebug( 'store', 'Failed to create folder: '.$irc->config->userPath.$this->_config['folder'], __FILE__, __LINE__ );
}
// import old settings
if ( file_exists( $this->_config['filename'] ) )
{
$this->xmlRead();
rename( $this->_config['filename'], $this->_config['filename'].'.bak' );
}
if ( file_exists( $this->_config['workerFilename'] ) )
{
$this->xmlRead( true );
rename( $this->_config['workerFilename'], $this->_config['workerFilename'].'.bak' );
}
}
}
function _customClose()
{
global $irc;
$irc->unregisterTimeid( $irc->_storeTimerID );
$irc->unregisterTimeid( $irc->_storeWorkerTimerID );
if ( isset( $irc->_storeDelayTimerID ) )
{
$irc->unregisterTimerid( $irc->_storeDelayTimerID );
unset( $irc->_storeDelayTimerID );
}
}
/*****************************************************
* Class interface instructions
*****************************************************/
/**
* Add a variable to the stored list
*
* @var string $name - name of the variable (inside $irc->)
* @return bool
* @access public
*/
function addVariable( $name )
{
global $irc;
$irc->_storeRevisions[$name] = array(
'revision' => 0,
'updated' => 0
);
$irc->modDebug('store', 'Added variable $irc->'.$name.' to the store list', __FILE__, __LINE__ );
return $this->xmlVarSync( $name );
}
/**
* Set an item in a variable (All store variables are assumed to be arrays)
*
* @var string $name - Name of the variable
* @var mixed $data - Data to send
* @var string $key - If a specfic key is required, use that key
* @return bool
* @access public
*/
function setVariable( $name, $data, $key = false )
{
global $irc;
// check the variable
if ( isset( $irc->_storeRevisions[$name] ) )
{
$this->xmlVarSync( $name );
$ref = &$irc->$name;
// check key
if ( $key !== false )
$ref[$key] = $data;
else
$ref = $data;
$irc->_storeRevisions[$name]['revision']++;
$irc->_storeRevisions[$name]['updated'] = time();
$irc->modDebug( 'store', 'Added data to variable: $irc->'.$name, __FILE__, __LINE__ );
$this->xmlVarSync( $name );
}
else
{
$irc->modDebug( 'store', 'Failed to add data to variable: $irc->'.$name.' (variable does not exist)', __FILE__, __LINE__ );
return false;
}
}
/**
* Unset an item in a variable (All store variables are assumed to be arrays)
*
* @var string $name - Name of the variable
* @var string $key - If a specfic key is required, use that key
* @return bool
* @access public
*/
function unsetVariable( $name, $key = false )
{
global $irc;
if ( isset( $irc->_storeRevisions[$name] ) )
{
$this->xmlVarSync( $name );
$ref = &$irc->$name;
// check key
if ( $key !== false )
unset( $ref[$key] );
else
unset( $ref );
$irc->_storeRevisions[$name]['revision']++;
$irc->_storeRevisions[$name]['updated'] = time();
$irc->modDebug( 'store', sprintf( 'Unset %s variable: $irc->%s', ( ( $key )? $key.' from':''), $name ), __FILE__, __LINE__ );
$this->xmlVarSync( $name );
return true;
}
else
{
$irc->modDebug( 'store', 'Failed to remove data to variable: $irc->'.$name.' (variable does not exist)', __FILE__, __LINE__ );
return false;
}
}
/**
* Updated a variable manually
*
* @var string $name
* @return void
* @access public
*/
function manualUpdate( $name, $new = false )
{
global $irc;
if ( is_array( $name ) )
{
foreach( $name as $var )
{
if ( $new )
{
$irc->_storeRevisions[$var]['revision']++;
$irc->_storeRevisions[$var]['updated'] = time();
}
$this->xmlVarSync( $var );
}
}
else
{
if ( $new )
{
$irc->_storeRevisions[$name]['revision']++;
$irc->_storeRevisions[$name]['updated'] = time();
}
$this->xmlVarSync( $name );
}
}
/**
* Syncronise a stored variable with the xml file
*
* @param string $name - The name of the stored variable
* @return boolean - On success/failure
*/
function xmlVarSync( $name )
{
global $irc;
$filename = $irc->config->userPath.$this->_config['folder'].$name.'.xml';
$irc->modDebug( 'store', 'xmlVarSync, name: '.$name.', filename: '.$filename, __FILE__, __LINE__, SMARTIRC_DEBUG_MESSAGEPARSER);
$xmlData = array();
$rev = 0;
$upd = 0;
if ( file_exists( $filename ))
{
// do some quick revivision checking
$var = file_get_contents( $filename );
if (preg_match( $this->_def['getRevision'], $var, $match ))
{
$rev = $match[1];
}
if (preg_match( $this->_def['getUpdated'], $var, $match ))
{
$upd = $match[1];
}
}
$toWrite = false;
// check revisions
if ( $rev > 0 )
{
if ( ( $rev > $irc->_storeRevisions[$name]['revision'] ) ||
( ( $rev == $irc->_storeRevisions[$name]['revision'] ) &&
( $upd > $irc->_storeRevisions[$name]['updated'] ) ) )
{
$irc->modDebug( 'store',
sprintf( 'Updating %s, file is newer, f[rev: %d, upd: %d] l[rev: %d, upd: %d]',
$name, $rev, $upd,
$irc->_storeRevisions[$name]['revision'],
$irc->_storeRevisions[$name]['updated'] ),
__FILE__, __LINE__, SMARTIRC_DEBUG_MESSAGEPARSER );
// if the file's revision is newer than the local revision, read from the file only
$xmlData = $this->_fromXML->unserialize( $filename, true );
if ( PEAR::isError( $xmlData ) )
{
$irc->modDebug( 'store', 'XML Error: '.$xmlData->getMessage(), __FILE__, __LINE__, SMARTIRC_DEBUG_MODULES );
$xmlData['revision'] = $irc->_storeRevisions[$name]['revision'];
$xmlData['updated'] = $irc->_storeRevisions[$name]['updated'];
$xmlData['value'] = $irc->$name;
}
else
{
$ref = &$irc->$name;
$ref = $xmlData['value'];
$irc->_storeRevisions[$name]['revision'] = $rev;
$irc->_storeRevisions[$name]['updated'] = $upd;
if ( isset( $ref['lastUpdate'] ) )
{
unset( $ref['lastUpdate'] );
}
}
}
else if ( ( $rev < $irc->_storeRevisions[$name]['revision'] ) ||
( ( $rev == $irc->_storeRevisions[$name]['revision'] ) &&
( $upd < $irc->_storeRevisions[$name]['updated'] ) ) )
{
$irc->modDebug( 'store',
sprintf( 'Updating %s, local is newer, f[rev: %d, upd: %d] l[rev: %d, upd: %d]',
$name, $rev, $upd,
$irc->_storeRevisions[$name]['revision'],
$irc->_storeRevisions[$name]['updated'] ),
__FILE__, __LINE__, SMARTIRC_DEBUG_MESSAGEPARSER );
// if the local variable is newer than the files revision, write to the file
$xmlData['revision'] = $irc->_storeRevisions[$name]['revision'];
$xmlData['updated'] = $irc->_storeRevisions[$name]['updated'];
if ( isset( $irc->$name ) )
{
$xmlData['value'] = $irc->$name;
}
else
{
$xmlData['value'] = '';
}
$toWrite = true;
}
else
{
$irc->modDebug( 'store',
sprintf( 'Updating %s, everything is the same, f[rev: %d, upd: %d] l[rev: %d, upd: %d]',
$name, $rev, $upd,
$irc->_storeRevisions[$name]['revision'],
$irc->_storeRevisions[$name]['updated'] ),
__FILE__, __LINE__, SMARTIRC_DEBUG_MESSAGEPARSER );
}
}
else
{
$irc->modDebug( 'store',
sprintf( 'Updating %s, file is empty/blank, f[rev: %d, upd: %d] l[rev: %d, upd: %d]',
$name, $rev, $upd,
$irc->_storeRevisions[$name]['revision'],
$irc->_storeRevisions[$name]['updated'] ),
__FILE__, __LINE__, SMARTIRC_DEBUG_MESSAGEPARSER );
// nothing in the file, lets write one
$xmlData['revision'] = $irc->_storeRevisions[$name]['revision'];
$xmlData['updated'] = $irc->_storeRevisions[$name]['updated'];
if ( isset( $irc->$name ) )
{
$xmlData['value'] = $irc->$name;
}
else
{
$xmlData['value'] = '';
}
$toWrite = true;
}
if ( $toWrite )
{
if ( isset( $xmlData['value']['lastUpdate'] ) )
{
unset( $xmlData['value']['lastUpdate'] );
}
$wxml = $this->_toXML->serialize( $xmlData );
$fp = fopen( $filename, 'w' );
fwrite( $fp, $wxml );
fflush( $fp );
fclose( $fp );
unset( $wxml, $fp );
}
unset( $xmlData );
return true;
}
/**
* Sync the variables with the xml file
* Checks to see if the file has been updated after the variable
* if so, overwrites the variable
* otherwise, variable gets written to the file
*
* @access public
*/
function xmlRead( $worker = false )
{
global $irc;
$toWrite = array();
$filename = ( $worker )? $this->_config['workerFilename']:$this->_config['filename'];
if ( !file_exists( $filename ) )
{
$xmlData = array();
}
else
{
$xmlData = $this->_fromXML->unserialize( $filename, true );
if ( PEAR::isError( $xmlData ) )
{
$irc->modDebug( 'store', 'XML Error: '.$xmlData->getMessage(), __FILE__, __LINE__, SMARTIRC_DEBUG_MODULES );
$xmlData = array();
}
}
foreach( $xmlData as $name => $value )
{
$irc->modDebug( 'store', 'Importing variable: '.$name, __FILE__, __LINE__ );
if ( isset( $value['lastUpdate'] ) )
{
unset( $value['lastUpdate'] );
}
$toWrite['revision'] = 1;
$toWrite['updated'] = time();
$toWrite['value'] = $value;
$wxml = $this->_toXML->serialize( $toWrite );
$fname = $irc->config->userPath.$this->_config['folder'].$name.'.xml';
$fp = fopen( $fname, 'w' );
fwrite( $fp, $wxml );
fflush( $fp );
fclose( $fp );
$irc->modDebug( 'store', 'Wrote file: '.$fname, __FILE__, __LINE__ );
unset( $toWrite, $wxml, $fp );
}
unset( $xmlData ); // clean up the major memory users
return true;
}
}
?>