X-Git-Url: https://git.cyclocoop.org/?a=blobdiff_plain;f=maintenance%2FMaintenance.php;h=cec12709625a482ff973a32cafbd103b52023573;hb=56dbeaa4482ff031f8d270f65c66462375e4a1df;hp=d048b7b20c8e48892c6a660b70332b02f7288d22;hpb=9f5d06527c649911ba4dcbe1c42df728d76faabe;p=lhc%2Fweb%2Fwiklou.git diff --git a/maintenance/Maintenance.php b/maintenance/Maintenance.php index d048b7b20c..cec1270962 100644 --- a/maintenance/Maintenance.php +++ b/maintenance/Maintenance.php @@ -20,26 +20,21 @@ * @defgroup Maintenance Maintenance */ +/** + * @defgroup MaintenanceArchive Maintenance archives + * @ingroup Maintenance + */ + // Define this so scripts can easily find doMaintenance.php -define( 'DO_MAINTENANCE', dirname( __FILE__ ) . '/doMaintenance.php' ); +define( 'RUN_MAINTENANCE_IF_MAIN', dirname( __FILE__ ) . '/doMaintenance.php' ); +define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless + $maintClass = false; // Make sure we're on PHP5 or better -if ( version_compare( PHP_VERSION, '5.1.0' ) < 0 ) { - die ( "Sorry! This version of MediaWiki requires PHP 5.1.x; you are running " . - PHP_VERSION . ".\n\n" . - "If you are sure you already have PHP 5.1.x or higher installed, it may be\n" . - "installed in a different path from PHP " . PHP_VERSION . ". Check with your system\n" . - "administrator.\n" ); -} - -// Wrapper for posix_isatty() -if ( !function_exists( 'posix_isatty' ) ) { - # We default as considering stdin a tty (for nice readline methods) - # but treating stout as not a tty to avoid color codes - function posix_isatty( $fd ) { - return !$fd; - } +if ( !function_exists( 'version_compare' ) || version_compare( PHP_VERSION, '5.3.2' ) < 0 ) { + require_once( dirname( __FILE__ ) . '/../includes/PHPVersionError.php' ); + wfPHPVersionError( 'cli' ); } /** @@ -68,6 +63,9 @@ abstract class Maintenance { // This is the desired params protected $mParams = array(); + // Array of mapping short parameters to long ones + protected $mShortParamsMap = array(); + // Array of desired args protected $mArgList = array(); @@ -90,10 +88,25 @@ abstract class Maintenance { // Have we already loaded our user input? protected $mInputLoaded = false; - // Batch size. If a script supports this, they should set - // a default with setBatchSize() + /** + * Batch size. If a script supports this, they should set + * a default with setBatchSize() + * + * @var int + */ protected $mBatchSize = null; + // Generic options added by addDefaultParams() + private $mGenericParameters = array(); + // Generic options which might or not be supported by the script + private $mDependantParameters = array(); + + /** + * Used by getDD() / setDB() + * @var DatabaseBase + */ + private $mDb = null; + /** * List of all the core maintenance scripts. This is added * to scripts added by extensions in $wgMaintenanceScripts @@ -116,6 +129,31 @@ abstract class Maintenance { register_shutdown_function( array( $this, 'outputChanneled' ), false ); } + /** + * Should we execute the maintenance script, or just allow it to be included + * as a standalone class? It checks that the call stack only includes this + * function and "requires" (meaning was called from the file scope) + * + * @return Boolean + */ + public static function shouldExecute() { + $bt = debug_backtrace(); + $count = count( $bt ); + if ( $count < 2 ) { + return false; // sanity + } + if ( $bt[0]['class'] !== 'Maintenance' || $bt[0]['function'] !== 'shouldExecute' ) { + return false; // last call should be to this function + } + $includeFuncs = array( 'require_once', 'require', 'include', 'include_once' ); + for( $i=1; $i < $count; $i++ ) { + if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) { + return false; // previous calls should all be "requires" + } + } + return true; + } + /** * Do the actual work. All child classes will need to implement this */ @@ -129,9 +167,13 @@ abstract class Maintenance { * @param $description String: the description of the param to show on --help * @param $required Boolean: is the param required? * @param $withArg Boolean: is an argument required with this option? + * @param $shortName String: character to use as short name */ - protected function addOption( $name, $description, $required = false, $withArg = false ) { - $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg ); + protected function addOption( $name, $description, $required = false, $withArg = false, $shortName = false ) { + $this->mParams[$name] = array( 'desc' => $description, 'require' => $required, 'withArg' => $withArg, 'shortName' => $shortName ); + if ( $shortName !== false ) { + $this->mShortParamsMap[$shortName] = $name; + } } /** @@ -214,6 +256,20 @@ abstract class Maintenance { */ protected function setBatchSize( $s = 0 ) { $this->mBatchSize = $s; + + // If we support $mBatchSize, show the option. + // Used to be in addDefaultParams, but in order for that to + // work, subclasses would have to call this function in the constructor + // before they called parent::__construct which is just weird + // (and really wasn't done). + if ( $this->mBatchSize ) { + $this->addOption( 'batch-size', 'Run this many operations ' . + 'per batch, default: ' . $this->mBatchSize, false, true ); + if ( isset( $this->mParams['batch-size'] ) ) { + // This seems a little ugly... + $this->mDependantParameters['batch-size'] = $this->mParams['batch-size']; + } + } } /** @@ -244,6 +300,9 @@ abstract class Maintenance { return rtrim( $input ); } + /** + * @return bool + */ public function isQuiet() { return $this->mQuiet; } @@ -261,12 +320,8 @@ abstract class Maintenance { } if ( $channel === null ) { $this->cleanupChanneled(); - - $f = fopen( 'php://stdout', 'w' ); - fwrite( $f, $out ); - fclose( $f ); - } - else { + print( $out ); + } else { $out = preg_replace( '/\n\z/', '', $out ); $this->outputChanneled( $out, $channel ); } @@ -276,19 +331,18 @@ abstract class Maintenance { * Throw an error to the user. Doesn't respect --quiet, so don't use * this for non-error output * @param $err String: the error to display - * @param $die Boolean: If true, go ahead and die out. + * @param $die Int: if > 0, go ahead and die out using this int as the code */ - protected function error( $err, $die = false ) { + protected function error( $err, $die = 0 ) { $this->outputChanneled( false ); if ( php_sapi_name() == 'cli' ) { fwrite( STDERR, $err . "\n" ); } else { - $f = fopen( 'php://stderr', 'w' ); - fwrite( $f, $err . "\n" ); - fclose( $f ); + print $err; } - if ( $die ) { - die(); + $die = intval( $die ); + if ( $die > 0 ) { + die( $die ); } } @@ -300,9 +354,7 @@ abstract class Maintenance { */ public function cleanupChanneled() { if ( !$this->atLineStart ) { - $handle = fopen( 'php://stdout', 'w' ); - fwrite( $handle, "\n" ); - fclose( $handle ); + print "\n"; $this->atLineStart = true; } } @@ -312,7 +364,7 @@ abstract class Maintenance { * same channel are concatenated, but any intervening messages in another * channel start a new line. * @param $msg String: the message without trailing newline - * @param $channel Channel identifier or null for no + * @param $channel string Channel identifier or null for no * channel. Channel comparison uses ===. */ public function outputChanneled( $msg, $channel = null ) { @@ -321,25 +373,20 @@ abstract class Maintenance { return; } - $handle = fopen( 'php://stdout', 'w' ); - // End the current line if necessary if ( !$this->atLineStart && $channel !== $this->lastChannel ) { - fwrite( $handle, "\n" ); + print "\n"; } - fwrite( $handle, $msg ); + print $msg; $this->atLineStart = false; if ( $channel === null ) { // For unchanneled messages, output trailing newline immediately - fwrite( $handle, "\n" ); + print "\n"; $this->atLineStart = true; } $this->lastChannel = $channel; - - // Cleanup handle - fclose( $handle ); } /** @@ -360,8 +407,11 @@ abstract class Maintenance { * Add the default parameters to the scripts */ protected function addDefaultParams() { - $this->addOption( 'help', 'Display this help message' ); - $this->addOption( 'quiet', 'Whether to supress non-error output' ); + + # Generic (non script dependant) options: + + $this->addOption( 'help', 'Display this help message', false, false, 'h' ); + $this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' ); $this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true ); $this->addOption( 'wiki', 'For specifying the wiki ID', false, true ); $this->addOption( 'globals', 'Output globals at the end of processing for debugging' ); @@ -369,16 +419,21 @@ abstract class Maintenance { $this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " . "http://en.wikipedia.org. This is sometimes necessary because " . "server name detection may fail in command line scripts.", false, true ); + + # Save generic options to display them separately in help + $this->mGenericParameters = $this->mParams ; + + # Script dependant options: + // If we support a DB, show the options if ( $this->getDbType() > 0 ) { $this->addOption( 'dbuser', 'The DB user to use for this script', false, true ); $this->addOption( 'dbpass', 'The password to use for this script', false, true ); } - // If we support $mBatchSize, show the option - if ( $this->mBatchSize ) { - $this->addOption( 'batch-size', 'Run this many operations ' . - 'per batch, default: ' . $this->mBatchSize, false, true ); - } + + # Save additional script dependant options to display + # them separately in help + $this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters ); } /** @@ -389,32 +444,25 @@ abstract class Maintenance { * @return Maintenance child */ public function runChild( $maintClass, $classFile = null ) { - // If we haven't already specified, kill setup procedures - // for child scripts, we've already got a sane environment - self::disableSetup(); - // Make sure the class is loaded first - if ( !class_exists( $maintClass ) ) { + if ( !MWInit::classExists( $maintClass ) ) { if ( $classFile ) { require_once( $classFile ); } - if ( !class_exists( $maintClass ) ) { + if ( !MWInit::classExists( $maintClass ) ) { $this->error( "Cannot spawn child: $maintClass" ); } } + /** + * @var $child Maintenance + */ $child = new $maintClass(); $child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs ); - return $child; - } - - /** - * Disable Setup.php mostly - */ - protected static function disableSetup() { - if ( !defined( 'MW_NO_SETUP' ) ) { - define( 'MW_NO_SETUP', true ); + if ( !is_null( $this->mDb ) ) { + $child->setDB( $this->mDb ); } + return $child; } /** @@ -429,7 +477,7 @@ abstract class Maintenance { } # Make sure we can handle script parameters - if ( !ini_get( 'register_argc_argv' ) ) { + if ( !function_exists( 'hphp_thread_set_warmup_enabled' ) && !ini_get( 'register_argc_argv' ) ) { $this->error( 'Cannot get command line arguments, register_argc_argv is set to false', true ); } @@ -479,6 +527,7 @@ abstract class Maintenance { * to allow sysadmins to explicitly set one if they'd prefer to override * defaults (or for people using Suhosin which yells at you for trying * to disable the limits) + * @return string */ public function memoryLimit() { $limit = $this->getOption( 'memory-limit', 'max' ); @@ -559,10 +608,14 @@ abstract class Maintenance { } elseif ( substr( $arg, 0, 2 ) == '--' ) { # Long options $option = substr( $arg, 2 ); + if ( array_key_exists( $option, $options ) ) { + $this->error( "\nERROR: $option parameter given twice\n" ); + $this->maybeHelp( true ); + } if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) { $param = next( $argv ); if ( $param === false ) { - $this->error( "\nERROR: $option needs a value after it\n" ); + $this->error( "\nERROR: $option parameter needs a value after it\n" ); $this->maybeHelp( true ); } $options[$option] = $param; @@ -580,10 +633,17 @@ abstract class Maintenance { # Short options for ( $p = 1; $p < strlen( $arg ); $p++ ) { $option = $arg { $p } ; + if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) { + $option = $this->mShortParamsMap[$option]; + } + if ( array_key_exists( $option, $options ) ) { + $this->error( "\nERROR: $option parameter given twice\n" ); + $this->maybeHelp( true ); + } if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) { $param = next( $argv ); if ( $param === false ) { - $this->error( "\nERROR: $option needs a value after it\n" ); + $this->error( "\nERROR: $option parameter needs a value after it\n" ); $this->maybeHelp( true ); } $options[$option] = $param; @@ -641,7 +701,7 @@ abstract class Maintenance { $this->mQuiet = true; } if ( $this->hasOption( 'batch-size' ) ) { - $this->mBatchSize = $this->getOption( 'batch-size' ); + $this->mBatchSize = intval( $this->getOption( 'batch-size' ) ); } } @@ -687,22 +747,75 @@ abstract class Maintenance { } $this->output( "$output\n\n" ); - // Parameters description - foreach ( $this->mParams as $par => $info ) { + # TODO abstract some repetitive code below + + // Generic parameters + $this->output( "Generic maintenance parameters:\n" ); + foreach ( $this->mGenericParameters as $par => $info ) { + if ( $info['shortName'] !== false ) { + $par .= " (-{$info['shortName']})"; + } $this->output( wordwrap( "$tab--$par: " . $info['desc'], $descWidth, "\n$tab$tab" ) . "\n" ); } + $this->output( "\n" ); - // Arguments description - foreach ( $this->mArgList as $info ) { - $openChar = $info['require'] ? '<' : '['; - $closeChar = $info['require'] ? '>' : ']'; - $this->output( - wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " . - $info['desc'], $descWidth, "\n$tab$tab" ) . "\n" - ); + $scriptDependantParams = $this->mDependantParameters; + if( count($scriptDependantParams) > 0 ) { + $this->output( "Script dependant parameters:\n" ); + // Parameters description + foreach ( $scriptDependantParams as $par => $info ) { + if ( $info['shortName'] !== false ) { + $par .= " (-{$info['shortName']})"; + } + $this->output( + wordwrap( "$tab--$par: " . $info['desc'], $descWidth, + "\n$tab$tab" ) . "\n" + ); + } + $this->output( "\n" ); + } + + + // Script specific parameters not defined on construction by + // Maintenance::addDefaultParams() + $scriptSpecificParams = array_diff_key( + # all script parameters: + $this->mParams, + # remove the Maintenance default parameters: + $this->mGenericParameters, + $this->mDependantParameters + ); + if( count($scriptSpecificParams) > 0 ) { + $this->output( "Script specific parameters:\n" ); + // Parameters description + foreach ( $scriptSpecificParams as $par => $info ) { + if ( $info['shortName'] !== false ) { + $par .= " (-{$info['shortName']})"; + } + $this->output( + wordwrap( "$tab--$par: " . $info['desc'], $descWidth, + "\n$tab$tab" ) . "\n" + ); + } + $this->output( "\n" ); + } + + // Print arguments + if( count( $this->mArgList ) > 0 ) { + $this->output( "Arguments:\n" ); + // Arguments description + foreach ( $this->mArgList as $info ) { + $openChar = $info['require'] ? '<' : '['; + $closeChar = $info['require'] ? '>' : ']'; + $this->output( + wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " . + $info['desc'], $descWidth, "\n$tab$tab" ) . "\n" + ); + } + $this->output( "\n" ); } die( 1 ); @@ -713,7 +826,7 @@ abstract class Maintenance { */ public function finalSetup() { global $wgCommandLineMode, $wgShowSQLErrors, $wgServer; - global $wgProfiling, $wgDBadminuser, $wgDBadminpassword; + global $wgDBadminuser, $wgDBadminpassword; global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf; # Turn off output buffering again, it might have been turned on in the settings files @@ -741,6 +854,9 @@ abstract class Maintenance { $wgDBpassword = $wgDBadminpassword; if ( $wgDBservers ) { + /** + * @var $wgDBservers array + */ foreach ( $wgDBservers as $i => $server ) { $wgDBservers[$i]['user'] = $wgDBuser; $wgDBservers[$i]['password'] = $wgDBpassword; @@ -758,8 +874,6 @@ abstract class Maintenance { $wgShowSQLErrors = true; @set_time_limit( 0 ); $this->adjustMemoryLimit(); - - $wgProfiling = false; // only for Profiler.php mode; avoids OOM errors } /** @@ -781,66 +895,16 @@ abstract class Maintenance { } } - /** - * Do setup specific to WMF - */ - public function loadWikimediaSettings() { - global $IP, $wgNoDBParam, $wgUseNormalUser, $wgConf, $site, $lang; - - if ( empty( $wgNoDBParam ) ) { - # Check if we were passed a db name - if ( isset( $this->mOptions['wiki'] ) ) { - $db = $this->mOptions['wiki']; - } else { - $db = array_shift( $this->mArgs ); - } - list( $site, $lang ) = $wgConf->siteFromDB( $db ); - - # If not, work out the language and site the old way - if ( is_null( $site ) || is_null( $lang ) ) { - if ( !$db ) { - $lang = 'aa'; - } else { - $lang = $db; - } - if ( isset( $this->mArgs[0] ) ) { - $site = array_shift( $this->mArgs ); - } else { - $site = 'wikipedia'; - } - } - } else { - $lang = 'aa'; - $site = 'wikipedia'; - } - - # This is for the IRC scripts, which now run as the apache user - # The apache user doesn't have access to the wikiadmin_pass command - if ( $_ENV['USER'] == 'apache' ) { - # if ( posix_geteuid() == 48 ) { - $wgUseNormalUser = true; - } - - putenv( 'wikilang=' . $lang ); - - ini_set( 'include_path', ".:$IP:$IP/includes:$IP/languages:$IP/maintenance" ); - - if ( $lang == 'test' && $site == 'wikipedia' ) { - define( 'TESTWIKI', 1 ); - } - } - /** * Generic setup for most installs. Returns the location of LocalSettings * @return String */ public function loadSettings() { - global $wgWikiFarm, $wgCommandLineMode, $IP; + global $wgCommandLineMode, $IP; - $wgWikiFarm = false; if ( isset( $this->mOptions['conf'] ) ) { $settingsFile = $this->mOptions['conf']; - } else if ( defined("MW_CONFIG_FILE") ) { + } elseif ( defined("MW_CONFIG_FILE") ) { $settingsFile = MW_CONFIG_FILE; } else { $settingsFile = "$IP/LocalSettings.php"; @@ -870,8 +934,8 @@ abstract class Maintenance { */ public function purgeRedundantText( $delete = true ) { # Data should come off the master, wrapped in a transaction - $dbw = wfGetDB( DB_MASTER ); - $dbw->begin(); + $dbw = $this->getDB( DB_MASTER ); + $dbw->begin( __METHOD__ ); $tbl_arc = $dbw->tableName( 'archive' ); $tbl_rev = $dbw->tableName( 'revision' ); @@ -916,11 +980,12 @@ abstract class Maintenance { } # Done - $dbw->commit(); + $dbw->commit( __METHOD__ ); } /** * Get the maintenance directory. + * @return string */ protected function getDir() { return dirname( __FILE__ ); @@ -943,10 +1008,8 @@ abstract class Maintenance { */ protected static function getCoreScripts() { if ( !self::$mCoreScripts ) { - self::disableSetup(); $paths = array( dirname( __FILE__ ), - dirname( __FILE__ ) . '/gearman', dirname( __FILE__ ) . '/language', dirname( __FILE__ ) . '/storage', ); @@ -974,9 +1037,33 @@ abstract class Maintenance { return self::$mCoreScripts; } + /** + * Returns a database to be used by current maintenance script. It can be set by setDB(). + * If not set, wfGetDB() will be used. + * This function has the same parameters as wfGetDB() + * + * @return DatabaseBase + */ + protected function &getDB( $db, $groups = array(), $wiki = false ) { + if ( is_null( $this->mDb ) ) { + return wfGetDB( $db, $groups, $wiki ); + } else { + return $this->mDb; + } + } + + /** + * Sets database object to be returned by getDB(). + * + * @param $db DatabaseBase: Database object to be used + */ + public function setDB( &$db ) { + $this->mDb = $db; + } + /** * Lock the search index - * @param &$db Database object + * @param &$db DatabaseBase object */ private function lockSearchindex( &$db ) { $write = array( 'searchindex' ); @@ -986,7 +1073,7 @@ abstract class Maintenance { /** * Unlock the tables - * @param &$db Database object + * @param &$db DatabaseBase object */ private function unlockSearchindex( &$db ) { $db->unlockTables( __CLASS__ . '::' . __METHOD__ ); @@ -995,7 +1082,7 @@ abstract class Maintenance { /** * Unlock and lock again * Since the lock is low-priority, queued reads will be able to complete - * @param &$db Database object + * @param &$db DatabaseBase object */ private function relockSearchindex( &$db ) { $this->unlockSearchindex( $db ); @@ -1006,7 +1093,7 @@ abstract class Maintenance { * Perform a search index update with locking * @param $maxLockTime Integer: the maximum time to keep the search index locked. * @param $callback callback String: the function that will update the function. - * @param $dbw Database object + * @param $dbw DatabaseBase object * @param $results */ public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) { @@ -1043,8 +1130,9 @@ abstract class Maintenance { /** * Update the searchindex table for a given pageid - * @param $dbw Database: a database write handle + * @param $dbw DatabaseBase a database write handle * @param $pageId Integer: the page ID to update. + * @return null|string */ public function updateSearchIndexForPage( $dbw, $pageId ) { // Get current revision @@ -1062,6 +1150,22 @@ abstract class Maintenance { return $title; } + /** + * Wrapper for posix_isatty() + * We default as considering stdin a tty (for nice readline methods) + * but treating stout as not a tty to avoid color codes + * + * @param $fd int File descriptor + * @return bool + */ + public static function posix_isatty( $fd ) { + if ( !MWInit::functionExists( 'posix_isatty' ) ) { + return !$fd; + } else { + return posix_isatty( $fd ); + } + } + /** * Prompt the console for input * @param $prompt String what to begin the line with, like '> ' @@ -1070,7 +1174,7 @@ abstract class Maintenance { public static function readconsole( $prompt = '> ' ) { static $isatty = null; if ( is_null( $isatty ) ) { - $isatty = posix_isatty( 0 /*STDIN*/ ); + $isatty = self::posix_isatty( 0 /*STDIN*/ ); } if ( $isatty && function_exists( 'readline' ) ) { @@ -1126,6 +1230,9 @@ abstract class Maintenance { } } +/** + * Fake maintenance wrapper, mostly used for the web installer/updater + */ class FakeMaintenance extends Maintenance { protected $mSelf = "FakeMaintenanceScript"; public function execute() { @@ -1133,3 +1240,70 @@ class FakeMaintenance extends Maintenance { } } +/** + * Class for scripts that perform database maintenance and want to log the + * update in `updatelog` so we can later skip it + */ +abstract class LoggedUpdateMaintenance extends Maintenance { + public function __construct() { + parent::__construct(); + $this->addOption( 'force', 'Run the update even if it was completed already' ); + $this->setBatchSize( 200 ); + } + + public function execute() { + $db = $this->getDB( DB_MASTER ); + $key = $this->getUpdateKey(); + + if ( !$this->hasOption( 'force' ) && + $db->selectRow( 'updatelog', '1', array( 'ul_key' => $key ), __METHOD__ ) ) + { + $this->output( "..." . $this->updateSkippedMessage() . "\n" ); + return true; + } + + if ( !$this->doDBUpdates() ) { + return false; + } + + if ( + $db->insert( 'updatelog', array( 'ul_key' => $key ), __METHOD__, 'IGNORE' ) ) + { + return true; + } else { + $this->output( $this->updatelogFailedMessage() . "\n" ); + return false; + } + } + + /** + * Message to show that the update was done already and was just skipped + * @return String + */ + protected function updateSkippedMessage() { + $key = $this->getUpdateKey(); + return "Update '{$key}' already logged as completed."; + } + + /** + * Message to show the the update log was unable to log the completion of this update + * @return String + */ + protected function updatelogFailedMessage() { + $key = $this->getUpdateKey(); + return "Unable to log update '{$key}' as completed."; + } + + /** + * Do the actual work. All child classes will need to implement this. + * Return true to log the update as done or false (usually on failure). + * @return Bool + */ + abstract protected function doDBUpdates(); + + /** + * Get the update key name to go in the update log table + * @return String + */ + abstract protected function getUpdateKey(); +}