952 days continuous production uptime, 40k+ tp/s single node. Original corpo Bitbucket history not included — clean archive commit.
366 lines
10 KiB
PHP
366 lines
10 KiB
PHP
<?php
|
|
|
|
/**
|
|
* This is a wrapper class for the AT daemon. It was plagiarized from:
|
|
*
|
|
* https://github.com/treffynnon/PHP-at-Job-Queue-Wrapper/blob/master/lib/Treffynnon/At/Wrapper.php
|
|
*
|
|
* Because the original (author's) version uses exceptions for error reporting. I've re-tooled the original code,
|
|
* eliminating the exception processing, making the output logging align with Namaste's logging formats, and closing
|
|
* access publicly to all methods except the intended public function.
|
|
*
|
|
* @author treffynnon@php.net Simon Holywell
|
|
* @author mike@givingassistant.org
|
|
* @version 1.0
|
|
*
|
|
*
|
|
* HISTORY:
|
|
* ========
|
|
* 06-07-17 mks port from original source
|
|
* 08-17-20 mks DB-168: code review
|
|
*
|
|
*/
|
|
class gacATWrapper
|
|
{
|
|
protected static string $binary = 'at'; // path to the AT binary
|
|
protected static string $addRegex = '/^job (\d+) at ([\w\d- :]+)$/'; // regexp to fetch the current job listings
|
|
protected static array $addMap = array( // regexp mapping -> descriptive names
|
|
1 => 'job_number',
|
|
2 => 'date',
|
|
);
|
|
// regexp for fetching queue info
|
|
protected static string $queueRegex = '/^(\d+)\s+([\w\d- :]+) (\w) ([\w-]+)$/';
|
|
// another regexp matching for queue data
|
|
protected static array $queueMap = array(
|
|
1 => 'job_number',
|
|
2 => 'date',
|
|
3 => 'queue',
|
|
4 => 'user',
|
|
);
|
|
|
|
protected static string $res = 'CRON: ';
|
|
protected static string $pipeTo = '2>&1'; // redirects STDERR to STDOUT (redundant)
|
|
|
|
protected static array $atSwitches = array( // supported AT options list
|
|
'queue' => '-q',
|
|
'list_queue' => '-l',
|
|
'file' => '-f',
|
|
'remove' => '-d',
|
|
);
|
|
|
|
/**
|
|
* cmd() -- public static function
|
|
*
|
|
* @users self::addCommand
|
|
*
|
|
* @param $command
|
|
* @param $time
|
|
* @param null $queue
|
|
* @return mixed
|
|
*/
|
|
static public function cmd($command, $time, $queue = null)
|
|
{
|
|
return self::addCommand($command, $time, $queue);
|
|
}
|
|
|
|
/**
|
|
* @uses self::addFile
|
|
*
|
|
* @param $file
|
|
* @param $time
|
|
* @param null $queue
|
|
* @return mixed
|
|
*/
|
|
static public function file($file, $time, $queue = null)
|
|
{
|
|
return self::addFile($file, $time, $queue);
|
|
}
|
|
|
|
/**
|
|
* @uses self::listQueue
|
|
*
|
|
* @param null $queue
|
|
* @return mixed
|
|
*/
|
|
static public function lq($queue = null)
|
|
{
|
|
return self::listQueue($queue);
|
|
}
|
|
|
|
|
|
/**
|
|
* Add a job to the `at` queue
|
|
* @param string $command
|
|
* @param string $time see `man at`
|
|
* @param string $queue a-zA-Z see `man at`
|
|
* @return mixed
|
|
*/
|
|
static private function addCommand($command, $time, $queue = null)
|
|
{
|
|
$command = self::escape($command);
|
|
$time = self::escape($time);
|
|
$exec_string = "echo '$command' | " . self::$binary;
|
|
if(null !== $queue) {
|
|
$exec_string .= ' ' . self::$atSwitches['queue'] . " {$queue[0]}";
|
|
}
|
|
$exec_string .= " $time ";
|
|
return self::addJob($exec_string);
|
|
}
|
|
|
|
|
|
/**
|
|
* Add a file job to the `at` queue
|
|
* @param string $file Full path to the file to be executed
|
|
* @param string $time see `man at`
|
|
* @param string $queue a-zA-Z see `man at`
|
|
* @return mixed
|
|
*/
|
|
static private function addFile($file, $time, $queue = null)
|
|
{
|
|
$file = self::escape($file);
|
|
$time = self::escape($time);
|
|
$exec_string = self::$binary . ' ' . self::$atSwitches['file'] . " $file";
|
|
if(null !== $queue) {
|
|
$exec_string .= ' ' . self::$atSwitches['queue'] . " {$queue[0]}";
|
|
}
|
|
$exec_string .= " $time ";
|
|
return self::addJob($exec_string);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return a list of the jobs currently in the queue. If you do not specify
|
|
* a queue to look at then it will return all jobs in all queues.
|
|
* @param string $queue
|
|
* @return array of Job objects
|
|
* @return mixed
|
|
*/
|
|
static private function listQueue($queue = null)
|
|
{
|
|
$exec_string = self::$binary . ' ' . self::$atSwitches['list_queue'];
|
|
if(null !== $queue) {
|
|
$exec_string .= ' ' . self::$atSwitches['queue'] . " {$queue[0]}";
|
|
}
|
|
$result = self::exec($exec_string);
|
|
return self::transform($result, 'queue');
|
|
}
|
|
|
|
/**
|
|
* Remove a job by job number
|
|
* @param int $job_number
|
|
* @return Boolean
|
|
*/
|
|
static public function removeJob($job_number)
|
|
{
|
|
$rc = true;
|
|
if (empty($job_number)) {
|
|
$hdr = sprintf(INFO_LOC, __METHOD__, __LINE__);
|
|
consoleLog(static::$res, CON_ERROR, $hdr . RES_ATW . ERROR_DATA_INPUT_EMPTY . STRING_JOB_NUMBER);
|
|
return(false);
|
|
}
|
|
$job_number = self::escape($job_number);
|
|
// $exec_string = self::$binary . ' ' . self::$atSwitches['remove'] . " $job_number";
|
|
$exec_string = 'atrm ' . $job_number;
|
|
$output = self::exec($exec_string);
|
|
if(count($output)) {
|
|
$rc = false;
|
|
foreach ($output as $errorMessage) {
|
|
$hdr = sprintf(INFO_LOC, __METHOD__, __LINE__);
|
|
consoleLog(static::$res, CON_ERROR, $hdr . $errorMessage);
|
|
}
|
|
echo getDateTime() . CON_ERROR . RES_ATW . $output[0] . PHP_EOL;
|
|
}
|
|
return($rc);
|
|
}
|
|
|
|
|
|
/**
|
|
* Add a job to the at queue and return the
|
|
* @param string $job_exec_string
|
|
* @return mixed
|
|
*/
|
|
static private function addJob($job_exec_string)
|
|
{
|
|
$output = self::exec($job_exec_string);
|
|
return (count($output) == 1) ? $output[0] : $output[1];
|
|
// $job = self::transform($output);
|
|
// if(!count($job)) {
|
|
// $logger = new gacErrorLogger();
|
|
// $logger->warn('failed to add job to the queue. Exec command: ' . $job_exec_string);
|
|
// $logger->__destruct();
|
|
// }
|
|
// return reset($job);
|
|
}
|
|
|
|
|
|
/**
|
|
* Transform the output of `at` into an array of objects
|
|
* @param array $output_array
|
|
* @param string $type Is this an add or list we are transforming?
|
|
* @return array An array of Job objects
|
|
*/
|
|
static private function transform(array $output_array, string $type = 'add'):array
|
|
{
|
|
$jobs = array();
|
|
// Get the appropriate regex class property for the type
|
|
// of `at` switch/command being run at this point in time.
|
|
$regex = $type . 'Regex';
|
|
$regex = self::$$regex;
|
|
$map = $type .'Map';
|
|
$map = self::$$map;
|
|
|
|
foreach($output_array as $line) {
|
|
$matches = array();
|
|
@preg_match($regex, $line, $matches);
|
|
if(count($matches) > count($map)) {
|
|
$jobs[] = self::mapJob($matches, $map);
|
|
}
|
|
}
|
|
return $jobs;
|
|
}
|
|
|
|
|
|
/**
|
|
* Map the details matched with the regex to descriptively named properties
|
|
* in a new Job object
|
|
* @param array $details
|
|
* @param array $map
|
|
* @return Job
|
|
*/
|
|
static private function mapJob($details, $map)
|
|
{
|
|
$Job = new Job();
|
|
foreach($details as $key => $detail) {
|
|
if(isset($map[$key])) {
|
|
$Job->$map[$key] = $detail;
|
|
}
|
|
}
|
|
return $Job;
|
|
}
|
|
|
|
|
|
/**
|
|
* Escape a string that will be passed to exec
|
|
* @param string $string
|
|
* @return string
|
|
*/
|
|
static private function escape($string)
|
|
{
|
|
return escapeshellcmd($string);
|
|
}
|
|
|
|
|
|
/**
|
|
* Run the command via exec() and return each line of the output as an
|
|
* array
|
|
* @param string $string
|
|
* @return array Each line of output is an element in the array
|
|
*/
|
|
static private function exec($string)
|
|
{
|
|
$output = array();
|
|
$string .= ' ' . self::$pipeTo;
|
|
exec($string, $output);
|
|
return $output;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* A simple class for storing a jobs details and some methods for manipulating
|
|
* it. A job model if you will.
|
|
*
|
|
* @author Simon Holywell <treffynnon@php.net>
|
|
* @version 16.11.2010
|
|
*/
|
|
class Job {
|
|
/**
|
|
* Data store for the job details
|
|
* @var array
|
|
*/
|
|
public array $data = array();
|
|
protected string $res = 'CRON: ';
|
|
protected /** @noinspection PhpMissingFieldTypeInspection */ $date;
|
|
|
|
/**
|
|
* Magic method to set a value in the $data
|
|
* property of the class
|
|
* @param string $name
|
|
* @param mixed $value
|
|
*/
|
|
public function __set($name, $value)
|
|
{
|
|
$this->data[$name] = $value;
|
|
}
|
|
|
|
|
|
/**
|
|
* Magic method to get a value in the $data property
|
|
* of the class
|
|
* @param string $name
|
|
* @return mixed
|
|
*/
|
|
public function __get($name)
|
|
{
|
|
if (isset($this->data[$name])) {
|
|
return $this->data[$name];
|
|
}
|
|
$logger = new gacErrorLogger();
|
|
$trace = debug_backtrace();
|
|
$logger->warn("Undefined property via __get(): $name in $trace[0]['file'] . on line $trace[0]['line']");
|
|
$logger->__destruct();
|
|
return(false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Magic method to check for the existence of an
|
|
* index in the $data property of the class
|
|
* @param string $name
|
|
* @return bool
|
|
*/
|
|
public function __isset($name)
|
|
{
|
|
return isset($this->data[$name]);
|
|
}
|
|
|
|
|
|
/**
|
|
* Magic method to unset an index in the $data property
|
|
* of the class
|
|
* @param string $name
|
|
*/
|
|
public function __unset($name)
|
|
{
|
|
unset($this->data[$name]);
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove this job from the queue
|
|
*/
|
|
public function remove() {
|
|
if(isset($this->job_number)) {
|
|
gacATWrapper::removeJob((int)$this->job_number);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a DateTime object for date and time extracted from
|
|
* the output of `at`
|
|
* @example echo $job->date()->format('d-m-Y');
|
|
* @uses DateTime
|
|
* @return DateTime A PHP DateTime object
|
|
*/
|
|
public function date(): ?DateTime
|
|
{
|
|
try {
|
|
return new DateTime($this->date);
|
|
} catch (Exception | TypeError $t) {
|
|
$hdr = sprintf(INFO_LOC, __METHOD__, __LINE__);
|
|
consoleLog($this->res, CON_ERROR, $hdr . $t->getMessage());
|
|
return null;
|
|
}
|
|
}
|
|
} |