[Cs-webapplibs-commits] SF.net SVN: cs-webapplibs:[105] trunk/0.3
Status: Beta
Brought to you by:
crazedsanity
From: <cra...@us...> - 2009-08-19 15:35:48
|
Revision: 105 http://cs-webapplibs.svn.sourceforge.net/cs-webapplibs/?rev=105&view=rev Author: crazedsanity Date: 2009-08-19 15:35:35 +0000 (Wed, 19 Aug 2009) Log Message: ----------- Copied stuff from cs-webdbupgrade... Modified Paths: -------------- trunk/0.3/README.txt Added Paths: ----------- trunk/0.3/cs_webdbupgrade.class.php trunk/0.3/docs/ trunk/0.3/schema/ Modified: trunk/0.3/README.txt =================================================================== --- trunk/0.3/README.txt 2009-08-19 15:28:38 UTC (rev 104) +++ trunk/0.3/README.txt 2009-08-19 15:35:35 UTC (rev 105) @@ -1,4 +1,5 @@ +=== CS Web DB Logger === Once the appropriate schema has been built, code can be updated easily to start logging: //Create the class... Copied: trunk/0.3/cs_webdbupgrade.class.php (from rev 101, import/cs-webdbupgrade/trunk/0.2/cs_webdbupgrade.class.php) =================================================================== --- trunk/0.3/cs_webdbupgrade.class.php (rev 0) +++ trunk/0.3/cs_webdbupgrade.class.php 2009-08-19 15:35:35 UTC (rev 105) @@ -0,0 +1,1096 @@ +<?php +/* + * Created on Jul 2, 2007 + * + * SVN INFORMATION::: + * ------------------ + * SVN Signature::::::: $Id$ + * Last Author::::::::: $Author$ + * Current Revision:::: $Revision$ + * Repository Location: $HeadURL$ + * Last Updated:::::::: $Date$ + * + */ + +require_once(constant('LIBDIR') .'/cs-versionparse/cs_version.abstract.class.php'); +require_once(constant('LIBDIR') .'/cs_debug.php'); + +class cs_webdbupgrade extends cs_versionAbstract { + + /** cs_fileSystem{} object: for filesystem read/write operations. */ + private $fsObj; + + /** cs_globalFunctions{} object: debugging, array, and string operations. */ + protected $gfObj; + + /** Array of configuration parameters. */ + private $config = NULL; + + /** Name of primary key sequence of main table (for handling inserts with PostgreSQL) */ + private $sequenceName; + + /** Database object. */ + protected $db; + + /** Object used to log activity. */ + protected $logsObj; + + /** Name of the project as referenced in the database. */ + protected $projectName; + + /** Internal cache of the version string from the VERSION file. */ + private $versionFileVersion = NULL; + + /** Stored database version. */ + private $databaseVersion = NULL; + + /** Name (absolute location) of *.lock file that indicates an upgrade is running. */ + private $lockfile; + + /** Determines if an internal upgrade is happening (avoids some infinite loops) */ + private $internalUpgradeInProgress = false; + + /** */ + private $allowNoDBVersion=true; + + /** Log messages to store during an internal upgrade (to avoid problems) */ + private $storedLogs = array(); + private $debugLogs=array(); + + /** List of acceptable suffixes; example "1.0.0-BETA3" -- NOTE: these MUST be in + * an order that reflects newest -> oldest; "ALPHA happens before BETA, etc. */ + private $suffixList = array( + 'ALPHA', //very unstable + 'BETA', //kinda unstable, but probably useable + 'RC' //all known bugs fixed, searching for unknown ones + ); + + //========================================================================= + public function __construct($versionFileLocation, $upgradeConfigFile, $lockFile='upgrade.lock') { + + //setup configuration parameters for database connectivity. + $dbParams = array( + 'host' => constant(__CLASS__ .'-DB_CONNECT_HOST'), + 'port' => constant(__CLASS__ .'-DB_CONNECT_PORT'), + 'dbname' => constant(__CLASS__ .'-DB_CONNECT_DBNAME'), + 'user' => constant(__CLASS__ .'-DB_CONNECT_USER'), + 'password' => constant(__CLASS__ .'-DB_CONNECT_PASSWORD') + ); + $this->config['DBPARAMS'] = $dbParams; + //Check for some required constants. + $requisiteConstants = array('LIBDIR'); + if(!defined('LIBDIR')) { + throw new exception(__METHOD__ .": required constant 'LIBDIR' not set"); + } + if(!defined('SITE_ROOT')) { + throw new exception(__METHOD__ .": required constant 'SITE_ROOT' not set"); + } + + require_once(constant('LIBDIR') .'/cs-content/cs_globalFunctions.class.php'); + require_once(constant('LIBDIR') .'/cs-content/cs_fileSystem.class.php'); + require_once(constant('LIBDIR') .'/cs-content/cs_phpDB.class.php'); + require_once(constant('LIBDIR') .'/cs-webdblogger/cs_webdblogger.class.php'); + require_once(constant('LIBDIR') .'/cs-phpxml/cs_phpxmlParser.class.php'); + require_once(constant('LIBDIR') .'/cs-phpxml/cs_phpxmlCreator.class.php'); + require_once(constant('LIBDIR') .'/cs-phpxml/cs_arrayToPath.class.php'); + + $this->fsObj = new cs_fileSystem(constant('SITE_ROOT')); + $this->gfObj = new cs_globalFunctions; + if(defined('DEBUGPRINTOPT')) { + $this->gfObj->debugPrintOpt = constant('DEBUGPRINTOPT'); + } + + if(!defined(__CLASS__ .'-DB_PRIMARYKEY') || !defined(__CLASS__ .'-DB_TABLE')) { + throw new exception(__METHOD__ .": no setting for DB_TABLE or DB_PRIMARYKEY, cannot continue"); + } + else { + $this->config['DB_TABLE'] = constant(__CLASS__ .'-DB_TABLE'); + $this->config['DB_PRIMARYKEY'] = constant(__CLASS__ .'-DB_PRIMARYKEY'); + } + $this->sequenceName = $this->config['DB_TABLE'] .'_'. $this->config['DB_PRIMARYKEY'] .'_seq'; + + if(!defined('DBTYPE')) { + throw new exception(__METHOD__ .": required constant 'DBTYPE' not set"); + } + + if(!file_exists($upgradeConfigFile) || !is_readable($upgradeConfigFile)) { + throw new exception(__METHOD__ .": required upgrade config file location (". $upgradeConfigFile .") not set or unreadable"); + } + else { + $this->config['UPGRADE_CONFIG_FILE'] = $upgradeConfigFile; + } + if(!strlen($versionFileLocation) || !file_exists($versionFileLocation)) { + throw new exception(__METHOD__ .": unable to locate version file (". $versionFileLocation .")"); + } + $this->set_version_file_location($versionFileLocation); + + if(!defined(__CLASS__ .'-RWDIR') || !is_dir(constant(__CLASS__ .'-RWDIR')) || !is_readable(constant(__CLASS__ .'-RWDIR')) || !is_writable(constant(__CLASS__ .'-RWDIR'))) { + throw new exception(__METHOD__ .": missing RWDIR (". constant(__CLASS__ .'-RWDIR') .") or isn't readable/writable"); + } + else { + $this->config['RWDIR'] = constant(__CLASS__ .'-RWDIR'); + } + if(is_null($lockFile) || !strlen($lockFile)) { + $lockFile = 'upgrade.lock'; + } + $this->lockfile = $this->config['RWDIR'] .'/'. $lockFile; + + $this->db = new cs_phpDB(constant('DBTYPE')); + try { + $this->db->connect($this->config['DBPARAMS']); + } + catch(exception $e) { + throw new exception(__METHOD__ .": failed to connect to database or logger error: ". $e->getMessage()); + } + + if($this->check_lockfile()) { + //there is an existing lockfile... + throw new exception(__METHOD__ .": upgrade in progress: ". $this->fsObj->read($this->lockfile)); + } + + $this->check_internal_upgrades(); + + try { + $loggerDb = new cs_phpDB(constant('DBTYPE')); + $loggerDb->connect($this->config['DBPARAMS'], true); + $this->logsObj = new cs_webdblogger($loggerDb, "Upgrade ". $this->projectName, false); + } + catch(exception $e) { + throw new exception(__METHOD__ .": failed to create logger::: ". $e->getMessage()); + } + + $this->check_versions(false); + }//end __construct() + //========================================================================= + + + + //========================================================================= + /** + * Determine if there are any upgrades that need to be performed... + */ + private function check_internal_upgrades() { + $oldVersionFileLocation = $this->versionFileLocation; + $oldUpgradeConfigFile = $this->config['UPGRADE_CONFIG_FILE']; + $this->config['UPGRADE_CONFIG_FILE'] = dirname(__FILE__) .'/upgrades/upgrade.xml'; + + + //set a status flag so we can store log messages (for now). + $this->internalUpgradeInProgress = true; + + + //do stuff here... + $this->set_version_file_location(dirname(__FILE__) .'/VERSION'); + $this->read_version_file(); + + //if there is an error, then... uh... yeah. + try { + $this->get_database_version(); + } + catch(exception $e) { + throw new exception(__METHOD__ .": error while retrieving database version: ". $e->getMessage()); + + //try creating the version. + $this->load_initial_version(); + } + + //do upgrades here... + $this->check_versions(true); + $this->internalUpgradeInProgress = false; + + + + + //reset internal vars. + $this->set_version_file_location($oldVersionFileLocation); + $this->config['UPGRADE_CONFIG_FILE'] = $oldUpgradeConfigFile; + $this->read_version_file(); + + }//end check_internal_upgrades() + //========================================================================= + + + + //========================================================================= + /** + * Where everything begins: checks if the version held in config.xml lines-up + * with the one in the VERSION file; if it does, then it checks the version + * listed in the database. + */ + public function check_versions($performUpgrade=TRUE) { + if(!is_bool($performUpgrade) || is_null($performUpgrade)) { + $performUpgrade = true; + } + + //first, check that all files exist. + $retval = NULL; + + //check to see if the lock files for upgrading exist. + if($this->upgrade_in_progress()) { + $this->do_log("Upgrade in progress", 'notice'); + throw new exception(__METHOD__ .": upgrade in progress"); + } + else { + //okay, all files present: check the version in the VERSION file. + $versionFileVersion = $this->read_version_file(); + $dbVersion = $this->get_database_version(); + if(!is_array($dbVersion)) { + $this->load_initial_version(); + } + + $versionsDiffer = TRUE; + $retval = FALSE; + + if($this->check_for_version_conflict() == false) { + $versionsDiffer = false; + $performUpgrade = false; + } + else { + $versionsDiffer = true; + } + + if($versionsDiffer == TRUE && $performUpgrade === TRUE) { + //reset the return value, so it'll default to failure until we say otherwise. + $retval = NULL; + + //Perform the upgrade! + $this->perform_upgrade(); + } + } + + return($retval); + }//end check_versions() + //========================================================================= + + + + //========================================================================= + protected function read_version_file() { + $retval = NULL; + + //okay, all files present: check the version in the VERSION file. + $versionFileContents = $this->fsObj->read($this->versionFileLocation); + + + $versionMatches = array(); + preg_match_all('/\nVERSION: (.*)\n/', $versionFileContents, $versionMatches); + if(count($versionMatches) == 2 && count($versionMatches[1]) == 1) { + $retval = trim($versionMatches[1][0]); + $this->versionFileVersion = $this->get_full_version_string($retval); + + //now retrieve the PROJECT name. + $projectMatches = array(); + preg_match_all('/\nPROJECT: (.*)\n/', $versionFileContents, $projectMatches); + if(count($projectMatches) == 2 && count($projectMatches[1]) == 1) { + $this->projectName = trim($projectMatches[1][0]); + } + else { + $this->error_handler(__METHOD__ .": failed to find PROJECT name"); + } + } + else { + $this->error_handler(__METHOD__ .": could not find VERSION data"); + } + + return($retval); + }//end read_version_file() + //========================================================================= + + + + //========================================================================= + /** + * Read information from our config file, so we know what to expect. + */ + private function read_upgrade_config_file() { + try { + $xmlString = $this->fsObj->read($this->config['UPGRADE_CONFIG_FILE']); + } + catch(exception $e) { + throw new exception(__METHOD__ .": failed to read upgrade config file::: ". $e->getMessage()); + } + + //parse the file. + $xmlParser = new cs_phpxmlParser($xmlString); + + $config = $xmlParser->get_tree(TRUE); + + if(is_array($config['UPGRADE']) && count($config['UPGRADE'])) { + $this->config['UPGRADELIST'] = $config['UPGRADE']; + } + else { + $this->error_handler(__METHOD__ .": failed to retrieve 'UPGRADE' section; " . + "make sure upgrade.xml's ROOT element is 'UPGRADE'"); + } + }//end read_upgrade_config_file() + //========================================================================= + + + + //========================================================================= + private function perform_upgrade() { + $this->logsObj->suspendLogging=true; + //make sure there's not already a lockfile. + if($this->upgrade_in_progress()) { + //ew. Can't upgrade. + $this->error_handler(__METHOD__ .": upgrade already in progress...????"); + } + else { + $lockConfig = $this->upgrade_in_progress(TRUE); + $this->fsObj->cd("/"); + + $this->do_log("Starting upgrade process...", 'begin'); + + //TODO: not only should the "create_file()" method be run, but also do a sanity check by calling lock_file_exists(). + if($lockConfig === 0) { + //can't create the lockfile. Die. + $this->error_handler(__METHOD__ .": failed to set 'upgrade in progress'"); + } + else { + $this->do_log(__METHOD__ .": result of creating lockfile: (". $lockConfig .")", 'debug'); + + //push data into our internal "config" array. + $this->read_upgrade_config_file(); + $this->get_database_version(); + + //check for version conflicts. + $versionConflictInfo = $this->check_for_version_conflict(); + + + if($versionConflictInfo !== false) { + $this->do_log("Upgrading ". $versionConflictInfo ." versions, from " . + "(". $this->databaseVersion .") to (". $this->versionFileVersion .")", 'info'); + } + + $upgradeList = $this->get_upgrade_list(); + try { + $i=0; + $this->do_log(__METHOD__ .": starting to run through the upgrade list, starting at (". $this->databaseVersion ."), " . + "total number of upgrades to perform: ". count($upgradeList), 'debug'); + $this->db->beginTrans(__METHOD__); + foreach($upgradeList as $fromVersion=>$toVersion) { + + $details = __METHOD__ .": upgrading from ". $fromVersion ." to ". $toVersion ."... "; + $this->do_log($details, 'system'); + $this->do_single_upgrade($fromVersion, $toVersion); + $this->get_database_version(); + $i++; + + $details = __METHOD__ .": finished upgrade #". $i .", now at version (". $this->databaseVersion .")"; + $this->do_log($details, 'system'); + } + + if($i < count($upgradeList)) { + $this->do_log(__METHOD__ .": completed upgrade ". $i ." of ". count($upgradeList), 'debug'); + } + else { + if($this->databaseVersion == $this->versionFileVersion) { + $this->do_log(__METHOD__ .": finished upgrading after performing (". $i .") upgrades", 'debug'); + $this->newVersion = $this->databaseVersion; + } + else { + $this->do_log(__METHOD__ .": upgradeList::: ". $this->gfObj->debug_print($upgradeList,0), 'debug'); + $this->error_handler(__METHOD__ .": finished upgrade, but version wasn't updated (expecting '". $this->versionFileVersion ."', got '". $this->databaseVersion ."')!!!"); + } + } + $this->remove_lockfile(); + + $this->db->commitTrans(); + } + catch(exception $e) { + $transactionStatus = $this->db->get_transaction_status(false); + $this->error_handler(__METHOD__ .": transaction status=(". $transactionStatus ."), upgrade aborted:::". $e->getMessage()); + $this->db->rollbackTrans(); + } + $this->logsObj->suspendLogging=false; + $this->do_log("Upgrade process complete", 'end'); + } + } + $this->logsObj->suspendLogging=false; + $logsHandled = $this->logsObj->handle_suspended_logs(); + }//end perform_upgrade() + //========================================================================= + + + + //========================================================================= + public function upgrade_in_progress($makeItSo=FALSE) { + if($makeItSo === TRUE) { + if(strlen($this->databaseVersion)) { + $details = $this->projectName .': Upgrade from '. $this->databaseVersion .' started at '. date('Y-m-d H:i:s'); + $this->create_lockfile($details); + $retval = TRUE; + } + else { + $this->error_handler(__METHOD__ .": missing internal databaseVersion (". $this->databaseVersion .")"); + } + } + $retval = $this->check_lockfile(); + + return($retval); + }//end upgrade_in_progress() + //========================================================================= + + + + //========================================================================= + public function parse_version_string($versionString) { + if(is_null($versionString) || !strlen($versionString)) { + $this->error_handler(__METHOD__ .": invalid version string ($versionString)"); + } + + $suffix = ""; + $explodeThis = $versionString; + if(preg_match('/-[A-Z]{2,5}[0-9]{1,}/', $versionString)) { + $bits = explode('-', $versionString); + $suffix = $bits[1]; + $explodeThis = $bits[0]; + } + $tmp = explode('.', $explodeThis); + + + if(is_numeric($tmp[0]) && is_numeric($tmp[1])) { + $retval = array( + 'version_string' => $versionString, + 'version_major' => $tmp[0], + 'version_minor' => $tmp[1], + ); + if(isset($tmp[2])) { + $retval['version_maintenance'] = $tmp[2]; + } + else { + $retval['version_maintenance'] = 0; + } + + $retval['version_suffix'] = $suffix; + } + else { + $this->error_handler(__METHOD__ .": invalid version string format, requires MAJOR.MINOR syntax (". $versionString .")"); + } + + return($retval); + }//end parse_version_string() + //========================================================================= + + + + //========================================================================= + /** + * Checks for issues with versions. + * 0 == No problems. + * (string) == upgrade applicable (indicates "major"/"minor"/"maintenance"). + * NULL == encountered error + */ + private function check_for_version_conflict() { + //set a default return... + $retval = NULL; + + $this->read_version_file(); + + //parse the version strings. + $dbVersion = $this->get_database_version(); + $versionFileData = $this->parse_version_string($this->versionFileVersion); + + if($versionFileData['version_string'] == $dbVersion['version_string']) { + //good to go: no upgrade needed. + $retval = false; + } + else { + //NOTE: this seems very convoluted, but it works. + if($versionFileData['version_major'] == $dbVersion['version_major']) { + if($versionFileData['version_minor'] == $dbVersion['version_minor']) { + if($versionFileData['version_maintenance'] == $dbVersion['version_maintenance']) { + if($versionFileData['version_suffix'] == $dbVersion['version_suffix']) { + $this->error_handler(__METHOD__ .": no version upgrade detected, but version strings don't match (versionFile=". $versionFileData['version_string'] .", dbVersion=". $dbVersion['version_string'] .")"); + } + else { + $retval = "suffix"; + } + } + elseif($versionFileData['version_maintenance'] > $dbVersion['version_maintenance']) { + $retval = "maintenance"; + } + else { + $this->error_handler(__METHOD__ .": downgrading from maintenance versions is unsupported"); + } + } + elseif($versionFileData['version_minor'] > $dbVersion['version_minor']) { + $retval = "minor"; + } + else { + $this->error_handler(__METHOD__ .": downgrading minor versions is unsupported"); + } + } + elseif($versionFileData['version_major'] > $dbVersion['version_major']) { + $retval = "major"; + } + else { + $this->error_handler(__METHOD__ .": downgrading major versions is unsupported"); + } + } + + return($retval); + }//end check_for_version_conflict() + //========================================================================= + + + + //========================================================================= + private function get_database_version() { + $this->gfObj->debugPrintOpt=1; + //create a database object & attempt to read the database version. + + $sql = "SELECT * FROM ". $this->config['DB_TABLE'] ." WHERE project_name='" . + $this->gfObj->cleanString($this->projectName, 'sql') ."'"; + + $numrows = $this->db->exec($sql); + $dberror = $this->db->errorMsg(); + + if(strlen($dberror) || $numrows != 1) { + // + if(preg_match('/doesn\'t exist/', $dberror) || preg_match('/does not exist/', $dberror)) { + //add the table... + $loadTableResult = $this->load_table(); + if($loadTableResult === TRUE) { + //now try the SQL... + $numrows = $this->db->exec($sql); + $dberror = $this->db->errorMsg(); + } + else { + $this->error_handler(__METHOD__ .": no table in database, failed to create one... ORIGINAL " . + "ERROR: ". $dberror .", SCHEMA LOAD ERROR::: ". $loadTableResult); + } + } + elseif(!strlen($dberror) && $numrows == 0) { + if($this->allowNoDBVersion) { + $retval = false; + } + else { + $this->error_handler(__METHOD__ .": no version data found for (". $this->projectName .")"); + } + } + else { + $this->error_handler(__METHOD__ .": failed to retrieve version... numrows=(". $numrows ."), DBERROR::: ". $dberror); + } + } + else { + $data = $this->db->farray_fieldnames(); + $this->databaseVersion = $data['version_string']; + $retval = $this->parse_version_string($data['version_string']); + } + + return($retval); + }//end get_database_version() + //========================================================================= + + + + //========================================================================= + private function do_single_upgrade($fromVersion, $toVersion=null) { + //Use the "matching_syntax" data in the upgrade.xml file to determine the filename. + $versionIndex = "V". $this->get_full_version_string($fromVersion); + if(!isset($this->config['UPGRADELIST']['MATCHING'][$versionIndex])) { + //version-only upgrade. + $this->newVersion = $toVersion; + $this->update_database_version($toVersion); + } + else { + //scripted upgrade... + $scriptIndex = $versionIndex; + + $upgradeData = $this->config['UPGRADELIST']['MATCHING'][$versionIndex]; + + if(isset($upgradeData['TARGET_VERSION']) && count($upgradeData) > 1) { + $this->newVersion = $upgradeData['TARGET_VERSION']; + if(isset($upgradeData['SCRIPT_NAME']) && isset($upgradeData['CLASS_NAME']) && isset($upgradeData['CALL_METHOD'])) { + //good to go; it's a scripted upgrade. + $this->do_scripted_upgrade($upgradeData); + $this->update_database_version($upgradeData['TARGET_VERSION']); + } + else { + $this->error_handler(__METHOD__ .": not enough information to run scripted upgrade for ". $versionIndex); + } + } + else { + $this->error_handler(__METHOD__ .": target version not specified, unable to proceed with upgrade for ". $versionIndex); + } + } + $this->do_log("Finished upgrade to ". $this->newVersion, 'system'); + }//end do_single_upgrade() + //========================================================================= + + + + //========================================================================= + /** + * Updates information that's stored in the database, internal to cs-project, + * so the version there is consistent with all the others. + */ + protected function update_database_version($newVersionString) { + $versionInfo = $this->parse_version_string($newVersionString); + + $sql = "UPDATE ". $this->config['DB_TABLE'] ." SET version_string='". + $this->gfObj->cleanString($versionInfo['version_string'], 'sql') + ."' WHERE project_name='". + $this->gfObj->cleanString($this->projectName, 'sql') ."'"; + + + $updateRes = $this->db->run_update($sql,false); + if($updateRes == 1) { + $retval = $updateRes; + } + else { + $this->error_handler(__METHOD__ .": invalid result (". $updateRes .") "); + } + + //okay, now check that the version string matches the updated bits. + if(!$this->check_database_version($this->newVersion)) { + $this->error_handler(__METHOD__ .": database version information is invalid: (". $this->newVersion .")"); + } + + return($retval); + + }//end update_database_version() + //========================================================================= + + + + //========================================================================= + /** + * Checks consistency of version information in the database, and optionally + * against a given version string. + */ + private function check_database_version() { + //retrieve the internal version information. + if(!is_null($this->newVersion)) { + $data = $this->get_database_version(); + $versionString = $data['version_string']; + + if($versionString == $this->newVersion) { + $retval = TRUE; + } + else { + $retval = FALSE; + } + + if(!$retval) { + $this->do_log("Version check failed, versionString=(". $versionString ."), checkVersion=(". $this->newVersion .")", 'FATAL'); + } + + } + else { + $this->error_handler(__METHOD__ .": no version string given (". $this->newVersion .")"); + } + + return($retval); + }//end check_database_version() + //========================================================================= + + + + //========================================================================= + private function do_scripted_upgrade(array $upgradeData) { + $myConfigFile = $upgradeData['SCRIPT_NAME']; + + $this->do_log("Preparing to run script '". $myConfigFile ."'", 'debug'); + + //we've got the filename, see if it exists. + + $scriptsDir = dirname($this->config['UPGRADE_CONFIG_FILE']); + $fileName = $scriptsDir .'/'. $myConfigFile; + + if(file_exists($fileName)) { + + $this->do_log("(". __CLASS__ .") Performing scripted upgrade (". $myConfigFile .") from file '". $fileName ."'", 'DEBUG'); + $createClassName = $upgradeData['CLASS_NAME']; + $classUpgradeMethod = $upgradeData['CALL_METHOD']; + require_once($fileName); + + //now check to see that the class we need actually exists. + if(class_exists($createClassName)) { + $upgradeObj = new $createClassName($this->db); + if(method_exists($upgradeObj, $classUpgradeMethod)) { + $upgradeResult = $upgradeObj->$classUpgradeMethod(); + + if($upgradeResult === true) { + //yay, it worked! + $this->do_log("Upgrade succeeded (". $upgradeResult .")", 'success'); + } + else { + $this->error_handler(__METHOD__ .": upgrade failed (". $upgradeResult .")"); + } + $this->do_log("Finished running ". $createClassName ."::". $classUpgradeMethod ."(), result was (". $upgradeResult .")", 'debug'); + } + else { + $this->error_handler(__METHOD__ .": upgrade method doesn't exist (". $createClassName ."::". $classUpgradeMethod + ."), unable to perform upgrade "); + } + } + else { + $this->error_handler(__METHOD__ .": unable to locate upgrade class name (". $createClassName .")"); + } + } + else { + $this->error_handler(__METHOD__ .": upgrade filename (". $fileName .") does not exist"); + } + }//end do_scripted_upgrade() + //========================================================================= + + + + //========================================================================= + public function is_higher_version($version, $checkIfHigher) { + try { + $retval = parent::is_higher_version($version, $checkIfHigher); + } + catch(exception $e) { + $this->error_handler($e->getMessage()); + } + + return($retval); + + }//end is_higher_version() + //========================================================================= + + + + //========================================================================= + /** + * Determines list of upgrades to perform. + * + * If the current version is 1.0.1, the version file is 1.0.5, and there's a + * scripted upgrade at 1.0.4, this will update the database version to 1.0.3, + * run the scripted upgrade at 1.0.4, then update the database version to + * 1.0.5 (keeps from skipping the upgrade at 1.0.4) + */ + private function get_upgrade_list() { + $this->get_database_version(); + $dbVersion = $this->databaseVersion; + $newVersion = $this->versionFileVersion; + + $retval = array(); + if(!$this->is_higher_version($dbVersion, $newVersion)) { + $this->error_handler(__METHOD__ .": version (". $newVersion .") isn't higher than (". $dbVersion .")... something is broken"); + } + elseif(is_array($this->config['UPGRADELIST']['MATCHING'])) { + $lastVersion = $dbVersion; + foreach($this->config['UPGRADELIST']['MATCHING'] as $matchVersion=>$data) { + + $matchVersion = preg_replace('/^V/', '', $matchVersion); + if($matchVersion == $data['TARGET_VERSION']) { + $this->error_handler(__METHOD__ .": detected invalid TARGET_VERSION in (". $matchVersion ."): make sure TARGET_VERSION is higher than matching!"); + } + elseif($this->databaseVersion == $matchVersion || $this->is_higher_version($this->databaseVersion, $matchVersion)) { + //the version in MATCHING is equal to or HIGHER than our database version... make sure it is NOT + // higher than the version in our versionFile. + if(!$this->is_higher_version($this->versionFileVersion, $matchVersion)) { + if(!count($retval) && $matchVersion != $this->databaseVersion) { + $retval[$this->databaseVersion] = $matchVersion; + } + //the MATCHING version is NOT higher than the version file's version, looks ok. + $lastVersion = $data['TARGET_VERSION']; + $retval[$matchVersion] = $data['TARGET_VERSION']; + } + else { + $this->do_log(__METHOD__ .": entry in upgrade.xml (". $matchVersion .") is higher than the VERSION file (". $this->versionFileVersion .")", 'warning'); + } + } + else { + $this->do_log(__METHOD__ .": SKIPPING (". $matchVersion .")", 'debug'); + } + } + + if($lastVersion !== $newVersion && (!isset($retval[$lastVersion]) || $retval[$lastVersion] != $newVersion)) { + $retval[$lastVersion] = $newVersion; + } + } + else { + //no intermediary upgrades: just pass back the latest version. + $this->do_log(__METHOD__ .": no intermediary upgrades", 'debug'); + $retval[$dbVersion] = $this->versionFileVersion; + } + + return($retval); + + }//end get_upgrade_list() + //========================================================================= + + + + //========================================================================= + protected function parse_suffix($suffix) { + $retval = NULL; + if(strlen($suffix)) { + //determine what kind it is. + foreach($this->suffixList as $type) { + if(preg_match('/^'. $type .'/', $suffix)) { + $checkThis = preg_replace('/^'. $type .'/', '', $suffix); + if(strlen($checkThis) && is_numeric($checkThis)) { + //oooh... it's something like "BETA3" + $retval = array( + 'type' => $type, + 'number' => $checkThis + ); + } + else { + $this->error_handler(__METHOD__ .": invalid suffix (". $suffix .")"); + } + break; + } + } + } + else { + $this->error_handler(__METHOD__ .": invalid suffix (". $suffix .")"); + } + + return($retval); + }//end parse_suffix() + //========================================================================= + + + + //========================================================================= + private function fix_xml_config($config, $path=null) { + $this->xmlLoops++; + if($this->xmlLoops > 1000) { + $this->error_handler(__METHOD__ .": infinite loop detected..."); + } + + try { + $a2p = new cs_arrayToPath($config); + } + catch(exception $e) { + $this->do_log(__METHOD__ .': encountered exception: '. $e->getMessage()); + $this->error_handler($e->getMessage()); + } + if(!is_array($this->tempXmlConfig)) { + $this->tempXmlConfig = array(); + } + try { + $myA2p = new cs_arrayToPath(&$this->tempXmlConfig); + } + catch(exception $e) { + $this->do_log(__METHOD__ .': encountered exception: '. $e->getMessage()); + $this->error_handler($e->getMessage()); + } + + $myData = $a2p->get_data($path); + + if(is_array($myData)) { + if(isset($myData['type']) && $myData['type'] != 'open') { + if($myData['type'] == 'complete') { + $val = null; + if(isset($myData['value'])) { + $val = $myData['value']; + } + $oldData = $myA2p->get_data(); + $myA2p->set_data($path, $val); + $this->tempXmlConfig = $myA2p->get_data(); + } + else { + $this->error_handler(__METHOD__ .": invalid type (". $myData['type'] .")"); + } + } + else { + foreach($myData as $i=>$d) { + if(!in_array($i, array('type', 'attributes', 'value'))) { + $this->fix_xml_config($config, $path .'/'. $i); + } + } + } + } + else { + $this->error_handler(__METHOD__ .": unable to fix data on path=(". $path .")::: ". $this->gfObj->debug_print($myData,0)); + } + }//end fix_xml_config() + //========================================================================= + + + + //========================================================================= + public function load_table() { + $schemaFileLocation = dirname(__FILE__) .'/schema/schema.sql'; + $schema = file_get_contents($schemaFileLocation); + $schema = str_replace('{tableName}', $this->config['DB_TABLE'], $schema); + $schema = str_replace('{primaryKey}', $this->config['DB_PRIMARYKEY'], $schema); + $this->db->exec($schema); + + $loadTableResult = $this->db->errorMsg(); + if(!strlen($loadTableResult)) { + $loadTableResult = true; + $logRes = 'Successfully loaded'; + $logType = 'initialize'; + + //now set the initial version information... + if(strlen($this->projectName) && strlen($this->versionFileVersion)) { + $this->load_initial_version(); + } + else { + throw new exception(__METHOD__ .": missing projectName (". $this->projectName .") " . + "or versionFileVersion (". $this->versionFileVersion ."), cannot load data"); + } + } + else { + $logRes = 'Failed to load'; + $logType = 'error'; + } + $this->do_log($logRes .' table ('. $this->config['DB_TABLE'] .') into ' . + 'database::: '. $loadTableResult, $logType); + + return($loadTableResult); + }//end load_table() + //========================================================================= + + + + //========================================================================= + public function check_lockfile() { + $status = false; + if(file_exists($this->lockfile)) { + $status = true; + } + + return($status); + }//end check_lockfile() + //========================================================================= + + + + //========================================================================= + /** + * Create a *.lock file that indicates the system is in the process of + * performing an upgrade (safer than always updating the site's configuration + * file). + */ + public function create_lockfile($contents) { + if(!$this->check_lockfile()) { + if($this->fsObj->create_file($this->lockfile)) { + if(!preg_match('/\n$/', $contents)) { + $contents .= "\n"; + } + $writeRes = $this->fsObj->write($contents); + if(is_numeric($writeRes) && $writeRes > 0) { + $this->fsObj->closeFile(); + } + else { + $this->error_handler(__METHOD__ .": failed to write contents (". $contents .") to lockfile"); + } + } + else { + $this->error_handler(__METHOD__ .": failed to create lockfile (". $this->lockfile .")"); + } + } + else { + $this->error_handler(__METHOD__ .": failed to create lockfile, one already exists (". $this->lockfile .")"); + } + }//end create_lockfile() + //========================================================================= + + + + //========================================================================= + /** + * Destroy the *.lock file that indicates an upgrade is underway. + */ + private function remove_lockfile() { + if($this->check_lockfile()) { + if(!$this->fsObj->rm($this->lockfile)) { + $this->error_handler(__METHOD__ .": failed to remove lockfile (". $this->lockfile .")"); + } + } + else { + $this->error_handler(__METHOD__ .": no lockfile (". $this->lockfile .")"); + } + }//end remove_lockfile() + //========================================================================= + + + + //========================================================================= + private function get_full_version_string($versionString) { + if(strlen($versionString)) { + $bits = $this->parse_version_string($versionString); + + $fullVersion = $bits['version_major'] .'.'. $bits['version_minor'] .'.'. + $bits['version_maintenance']; + if(strlen($bits['version_suffix'])) { + $fullVersion .= '-'. $bits['version_suffix']; + } + } + else { + $this->error_handler(__METHOD__ .": no version string given"); + } + + return($fullVersion); + }//end get_full_version_string() + //========================================================================= + + + + //========================================================================= + public function error_handler($details) { + //log the error. + if(!is_object($this->logsObj)) { + throw new exception(__METHOD__ .": error while running an internal upgrade::: ". $details); + } + if($this->internalUpgradeInProgress === false) { + $this->do_log($details, 'exception in code'); + } + + //now throw an exception so other code can catch it. + throw new exception($details); + }//end error_handler() + //========================================================================= + + + + //========================================================================= + public function load_initial_version() { + //if there's an INITIAL_VERSION in the upgrade config file, use that. + $this->read_upgrade_config_file(); + $insertData = array(); + if(isset($this->config['UPGRADELIST']['INITIALVERSION'])) { + $parseThis = $this->config['UPGRADELIST']['INITIALVERSION']; + } + else { + $parseThis = $this->versionFileVersion; + } + $versionInfo = $this->parse_version_string($parseThis); + $insertData = array( + 'project_name' => $this->projectName, + 'version_string' => $versionInfo['version_string'] + ); + + $sql = 'INSERT INTO '. $this->config['DB_TABLE'] . $this->gfObj->string_from_array($insertData, 'insert'); + + if($this->db->run_insert($sql, $this->sequenceName)) { + $loadRes = true; + $this->do_log("Created data for '". $this->projectName ."' with version '". $insertData['version_string'] ."'", 'initialize'); + } + else { + $this->error_handler(__METHOD__ .": failed to load initial version::: ". $e->getMessage()); + } + + return($loadRes); + }//end load_initial_version() + //========================================================================= + + + + //========================================================================= + protected function do_log($message, $type) { + $this->debugLogs[] = array('project'=>$this->projectName,'upgradeFile'=>$this->config['UPGRADE_CONFIG_FILE'],'message'=>$message,'type'=>$type); + if($this->internalUpgradeInProgress === true) { + $this->storedLogs[] = func_get_args(); + } + else { + $this->logsObj->log_by_class($message, $type); + } + }//end do_log() + //========================================================================= + + +}//end upgrade{} + + +?> \ No newline at end of file This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |