plugins/generic/acron/AcronPlugin.inc.php

Go to the documentation of this file.
00001 <?php
00002 
00015 //$Id$
00016 
00017 import('classes.plugins.GenericPlugin');
00018 
00019 // TODO: Rather than parsing the crontab on each request (which is slow and
00020 // dumb), decide when earliest possible run for each scheduled conference is, and store that
00021 // timestamp for quick reference along with the other entry parameters.
00022 
00023 // Alternately, store a PluginSetting containing the last time Acron actually
00024 // considered scheduled tasks, and limit the run frequency to (e.g.) 30 minutes.
00025 
00026 // TODO: Error handling. If a scheduled task encounters an error...?
00027 
00028 class AcronPlugin extends GenericPlugin {
00029 
00030    function isSitePlugin() {
00031       // This is a site-wide plugin.
00032       return true;
00033    }
00034 
00035    function register($category, $path) {
00036       if (!Config::getVar('general', 'installed')) return false;
00037       if (parent::register($category, $path)) {
00038 
00039          $this->addLocaleData();
00040          $this->parseCrontab();
00041 
00042          HookRegistry::register('LoadHandler',array(&$this, 'callback'));
00043 
00044          return true;
00045       }
00046       return false;
00047    }
00048 
00049    function callback($hookName, $args) {
00050       $isEnabled = $this->getSetting(0, 0, 'enabled');
00051       if($isEnabled) {
00052          $taskDao =& DAORegistry::getDao('ScheduledTaskDAO');
00053 
00054          // Grab the scheduled scheduled conference tree
00055          $scheduledTasks = $this->getSetting(0, 0, 'crontab');
00056          if(!$scheduledTasks) {
00057             $this->parseCrontab();
00058             $scheduledTasks = $this->getSetting(0, 0, 'crontab');
00059          }
00060 
00061          $tasks = $this->getSetting(0, 0, 'crontab');
00062 
00063          foreach($tasks as $task) {
00064 
00065             $lastRuntime = $taskDao->getLastRunTime($task['className']);
00066 
00067             if (isset($task['frequency'])) {
00068                $canExecute = $this->checkFrequency($task['className'], $task['frequency'], $lastRuntime);
00069             } else {
00070                // WARNING: tasks without 'frequency' entries will run ON EVERY REQUEST.
00071                // This can incur a serious performance hit.
00072                $canExecute = true;
00073             }
00074 
00075             if ($canExecute) {
00076                // Strip off the package name(s) to get the base class name
00077                $className = $task['className'];
00078 
00079                $pos = strrpos($className, '.');
00080                if ($pos === false) {
00081                   $baseClassName = $className;
00082                } else {
00083                   $baseClassName = substr($className, $pos+1);
00084                }
00085 
00086                // There's a race here. Several requests may come in closely spaced.
00087                // Each may decide it's time to run scheduled tasks, and more than one
00088                // can happily go ahead and do it before the "last run" time is updated.
00089 
00090                // By updating the last run time as soon as feasible, we can minimize
00091                // the race window. TODO: there ought to be a safer way of doing this.
00092 
00093                $taskDao->updateLastRunTime($className, $lastRuntime);
00094 
00095                // Load and execute the task
00096                import($className);
00097                $task = new $baseClassName($args);
00098                $task->execute();
00099             }
00100          }
00101       }
00102 
00103       return false;
00104    }
00105 
00106    /*
00107     * parseCrontab: reload the scheduled tasks XML.
00108     */
00109    function parseCrontab() {
00110       $xmlParser = new XMLParser();
00111 
00112       // TODO: make this a plugin setting, rather than assuming.
00113       $tree = $xmlParser->parse(Config::getVar('general', 'registry_dir') . '/scheduledTasks.xml');
00114 
00115       if (!$tree) {
00116          $xmlParser->destroy();
00117 
00118          // TODO: graceful error handling
00119          fatalError('Error parsing scheduled tasks XML.');
00120       }
00121 
00122       $tasks = array();
00123 
00124       foreach ($tree->getChildren() as $task) {
00125          $frequency = $task->getChildByName('frequency');
00126 
00127          $args = array();
00128          $index = 0;
00129          while(($arg = $task->getChildByName('arg', $index)) != null) {
00130             array_push($args, $arg->getValue());
00131             $index++;
00132          }
00133 
00134          $tasks[] = array(
00135             'className' => $task->getAttribute('class'),
00136             'frequency' => $frequency ? $frequency->getAttributes() : null,
00137             'args' => $args
00138          );
00139       }
00140 
00141       $xmlParser->destroy();
00142 
00143       // Store the object.
00144       $this->updateSetting(0, 0, 'crontab', $tasks, 'object');    
00145    }
00146 
00154    function checkFrequency($className, $frequency, $lastRunTime) {
00155       $isValid = true;
00156 
00157       // Check day of week
00158       if (isset($frequency['dayofweek'])) {
00159          $isValid = $this->isInRange($frequency['dayofweek'], (int)date('w'), $lastRunTime, 'day', strtotime('-1 week'));
00160       }
00161 
00162       if ($isValid) {
00163          // Check month
00164          if (isset($frequency['month'])) {
00165             $isValid = $this->isInRange($frequency['month'], (int)date('n'), $lastRunTime, 'month', strtotime('-1 year'));
00166          }
00167       }
00168 
00169       if ($isValid) {
00170          // Check day
00171          if (isset($frequency['day'])) {
00172             $isValid = $this->isInRange($frequency['day'], (int)date('j'), $lastRunTime, 'day', strtotime('-1 month'));
00173          }
00174       }
00175 
00176       if ($isValid) {
00177          // Check hour
00178          if (isset($frequency['hour'])) {
00179             $isValid = $this->isInRange($frequency['hour'], (int)date('G'), $lastRunTime, 'hour', strtotime('-1 day'));
00180          }
00181       }
00182 
00183       if ($isValid) {
00184          // Check minute
00185          if (isset($frequency['minute'])) {
00186             $isValid = $this->isInRange($frequency['minute'], (int)date('i'), $lastRunTime, 'min', strtotime('-1 hour'));
00187          }
00188       }
00189 
00190       return $isValid;
00191    }
00192 
00202    function isInRange($rangeStr, $currentValue, $lastTimestamp, $timeCompareStr, $cutoffTimestamp) {
00203       $isValid = false;
00204       $rangeArray = explode(',', $rangeStr);
00205 
00206       if ($cutoffTimestamp > $lastTimestamp) {
00207          // Execute immediately if the cutoff time period has past since the task was last run
00208          $isValid = true;
00209       }
00210 
00211       for ($i = 0, $count = count($rangeArray); !$isValid && ($i < $count); $i++) {
00212          if ($rangeArray[$i] == '*') {
00213             // Is wildcard
00214             $isValid = true;
00215 
00216          } if (is_numeric($rangeArray[$i])) {
00217             // Is just a value
00218             $isValid = ($currentValue == (int)$rangeArray[$i]);
00219 
00220          } else if (preg_match('/^(\d*)\-(\d*)$/', $rangeArray[$i], $matches)) {
00221             // Is a range
00222             $isValid = $this->isInNumericRange($currentValue, (int)$matches[1], (int)$matches[2]);
00223 
00224          } else if (preg_match('/^(.+)\/(\d+)$/', $rangeArray[$i], $matches)) {
00225             // Is a range with a skip factor
00226             $skipRangeStr = $matches[1];
00227             $skipFactor = (int)$matches[2];
00228 
00229             if ($skipRangeStr == '*') {
00230                $isValid = true;
00231 
00232             } else if (preg_match('/^(\d*)\-(\d*)$/', $skipRangeStr, $matches)) {
00233                $isValid = $this->isInNumericRange($currentValue, (int)$matches[1], (int)$matches[2]);
00234             }
00235 
00236             if ($isValid) {
00237                // Check against skip factor
00238                $isValid = (strtotime("-$skipFactor $timeCompareStr") > $lastTimestamp);
00239             }
00240          }
00241       }
00242 
00243       return $isValid;
00244    }
00245 
00253    function isInNumericRange($value, $min, $max) {
00254       return ($value >= $min && $value <= $max);
00255    }
00256 
00257    function getName() {
00258       return 'AcronPlugin';
00259    }
00260 
00261    function getDisplayName() {
00262       return __('plugins.generic.acron.name');
00263    }
00264 
00265    function getDescription() {
00266       return __('plugins.generic.acron.description');
00267    }
00268 
00269    function getManagementVerbs() {
00270       $isEnabled = $this->getSetting(0, 0, 'enabled');
00271 
00272       $verbs = array();
00273       $verbs[] = array(
00274          ($isEnabled?'disable':'enable'),
00275          __($isEnabled?'manager.plugins.disable':'manager.plugins.enable')
00276       );
00277       $verbs[] = array(
00278          'reload', __('plugins.generic.acron.reload')
00279       );
00280       return $verbs;
00281    }
00282 
00283    /*
00284     * Execute a management verb on this plugin
00285     * @param $verb string
00286     * @param $args array
00287     * @param $message string Location for the plugin to put a result msg
00288     * @return boolean
00289     */
00290    function manage($verb, $args, &$message) {
00291       switch ($verb) {
00292          case 'enable':
00293             $this->updateSetting(0, 0, 'enabled', true);
00294             $message = __('plugins.generic.acron.enabled');
00295             break;
00296          case 'disable':
00297             $this->updateSetting(0, 0, 'enabled', false);
00298             $message = __('plugins.generic.acron.disabled');
00299             break;
00300          case 'reload':
00301             $this->parseCrontab();
00302       }
00303       return false;
00304    }
00305 }
00306 ?>

Generated on 25 Jul 2013 for Open Conference Systems by  doxygen 1.4.7