PHP Developers Network

A community of PHP developers offering assistance, advice, discussion, and friendship.
 
Loading
It is currently Sat May 26, 2018 2:38 pm

All times are UTC - 5 hours




Post new topic Reply to topic  [ 5 posts ] 
Author Message
 Post subject: PHPCronEmulator
PostPosted: Tue Aug 21, 2007 1:39 pm 
Offline
Tranquility In Moderation
User avatar

Joined: Sun Feb 06, 2005 8:18 pm
Posts: 5001
Location: Indiana
So, I've rewrote this, and changed the name, because the ones I tried were already taken. I also just edited an old topic to avoid creating a new one.

The below script requires a config file, and a tasks file. Along with a running.txt file and a log.txt file. I am in the process of creating a web administration script that will create these files, so it will be easy for the end user and properly parsed by the below script.

What this does is checks (in real time) for updated config/tasks files, and loads them into an array. Then checks against that array every 60 seconds to see if a scheduled task should be exected. If it should, then it optionally emails the output. This will run continuously, however I've unset() all variables so memory usage should be very minimal.

You can add/edit tasks and configuration without stopping the instance of this script. Also I will have a section in the web admin to start/stop this script.

Syntax: [ Download ] [ Hide ]
<?php



/*

** Critique only.  Do not use, do not redistribute, do not modify

*/




class PHPCronEmulator

{

    /*

    ** Stores the complete list of tasks to be ran.

    */


    private $_completeTaskList = array();

   

    /*

    ** Stores the md5_file() hash of the configuration file.

    */


    private $_configHash;

   

    /*

    ** Stores the md5_file() hash of the tasks file.

    */


    private $_taskHash;

   

    /*

    ** Whether or not to log errors to the log file.

    */


    private $_logErrors = true;

   

    /*

    ** Whether or not to log successfully ran tasks to the log file.

    */


    private $_logSuccess = true;

   

    /*

    ** Whether or not to log events to the log file.

    */


    private $_logEvents = true;

   

    /*

    ** Email address to send task output to, if desired.

    */


    private $_email;

   

    /*

    ** The constructor method checks if an instance is already running.

    ** @access public

    */


    public function __construct()

    {

        if ($this->_isRunning())

        {

            if ($this->_logErrors)

            {

                $this->_log('Error', 'PHPCronEmulator attempted to start but an instance was already running.', time());

            }

           

            trigger_error('PHPCronEmulator attempted to start but an instance was already running.', E_USER_ERROR);

        }

    }

   

    /*

    ** Compares two hashes

    ** @access private

    */


    private function _compareHashes($hash1, $hash2)

    {

        return $hash1 == $hash2;

    }

   

    /*

    ** Loads the configuration file and sets appropriate variables.

    ** @access private

    */


    private function _loadConfigFile()

    {

        $configFile = trim(file_get_contents('txt/config.txt'));

        $this->_checkIfKilled($configFile);

       

        $lines = array_filter(explode("\n", $configFile));

       

        foreach ($lines AS $line)

        {

            $line = explode('=', trim($line));

            if (in_array(trim($line[0]), array('_email', '_logSuccess', '_logErrors', '_logEvents')))

            {

                $this->$line[0] = trim($line[1]);

            }

        }

       

        //generate hash

        $this->_configHash = md5_file('txt/config.txt');

           

        //log

        if ($this->_logEvents)

        {

            $this->_log('Event', 'Configuration file loaded.', time());

        }

       

        //cleanup

        unset($configFile, $lines, $line);

    }

   

    /*

    ** Loads the tasks file, checks if the time supplied is valid, and if it is,

    ** adds the task to $this->_completeTaskList

    ** @access private

    */


    private function _loadTaskFile()

    {

        $taskFile = trim(file_get_contents('txt/tasks.txt'));

       

        if ($taskFile != '')

        {

            $taskFile = explode("\n", $taskFile);

           

            foreach ($taskFile AS $task)

            {

                $task = explode("\t", $task);

               

                if (file_exists(trim($task[0])))

                {

                    if ($this->_isValidTimeFormat($task[1]))

                    {

                        $this->_completeTaskList[] = array('script' => trim($task[0]), 'time' => trim($task[1]), 'email' => trim($task[2]));

                    } else

                    {

                        if ($this->_logErrors)

                        {

                            $this->_log('Error', 'Could not evaluate time (' . $task[1] . ') given for script (' . $task[0] . ').', time());

                        }

                   

                        trigger_error('Could not evaluate time (' . $task[1] . ') given for script (' . $task[0] . ').', E_USER_WARNING);

                    }

                } else

                {

                    if ($this->_logErrors)

                    {

                        $this->_log('Error', 'Could not locate script (' . $task[0] . ').', time());

                    }

                   

                    trigger_error('Could not locate script (' . $task[0] . ').', E_USER_WARNING);

                }

            }

        }

       

        //generate hash

        $this->_taskHash = md5_file('txt/tasks.txt');

       

        if ($this->_logEvents)

        {

            $this->_log('Event', 'Task file loaded.', time());

        }

       

        //cleanup

        unset($taskFile, $task);

    }

   

    /*

    ** Method to determine if a supplied crontab style time is a valid crontab

    ** style time.

    ** @param string $time

    ** @access private

    */


    private function _isValidTimeFormat($time)

    {

        //minimum and maximum values for each part

        $minValues = array(0, 0, 1, 1, 0);

        $maxValues = array(59, 23, 31, 12, 6);

   

        //get each part of the time separated

        $timesTemp = array_filter(explode(' ', trim($time)));

   

        //reset key values

        $times = array();

        foreach ($timesTemp AS $timeTemp)

        {

            $times[] = trim($timeTemp);

        }

       

        if (count($times) != 5)

        {

            return false;

        }

       

        //validate each part

        $i=0;

        foreach($times AS $time)

        {

            if (strpos($time, ','))

            {

                //we have separated values

                $eachPart = explode(',', $time);

                $count = count($eachPart);

               

                if ($count == 2)

                {

                    if (strpos($eachPart[0], '-') && strpos($eachPart[1], '-'))

                    {

                        //range, range

                        foreach ($eachPart AS $part)

                        {

                            $partsOfPart = explode('-', $part);

                            foreach($partsOfPart AS $partPart)

                            {

                                if (($partPart < $minValues[$i]) || ($partPart > $maxValues[$i]))

                                {

                                    return false;

                                }

                            }

                        }

                    } elseif (strpos($eachPart[0], '-') && !strpos($eachPart[1], '-'))

                    {

                        //range, singlevalue

                        $partsOfPart = explode('-', $eachPart[0]);

                        foreach ($partsOfPart AS $partPart)

                        {

                            if (($partPart < $minValues[$i]) || ($partPart > $maxValues[$i]))

                            {

                                return false;

                            }

                        }

                       

                        if (($eachPart[1]) < $minValues[$i] || ($eachPart[1] > $maxValues[$i]))

                        {

                            return false;

                        }

                    } elseif (!strpos($eachPart[0], '-') && strpos($eachPart[1], '-'))

                    {

                        //singlevalue,range

                        if (($eachPart[0]) < $minValues[$i] || ($eachPart[0] > $maxValues[$i]))

                        {

                            return false;

                        }

                       

                        $partsOfPart = explode('-', $eachPart[1]);

                       

                        foreach ($partsOfPart AS $partPart)

                        {

                            if (($partPart < $minValues[$i]) || ($partPart > $maxValues[$i]))

                            {

                                return false;

                            }

                        }

                    } else

                    {

                        //singlevalue,singlevalue

                        foreach ($eachPart AS $part)

                        {

                            if (($part < $minValues[$i]) || ($part > $maxValues[$i]))

                            {

                                return false;

                            }

                        }

                    }

                } else

                {

                    //list

                    foreach ($eachPart AS $part)

                    {

                        if (($part < $minValues[$i]) || ($part > $maxValues[$i]))

                        {

                            return false;

                        }

                    }

                }

            } elseif (strpos($time, '-'))

            {

                //we have a single range

                $eachPart = explode('-', $time);

               

                foreach ($eachPart AS $part)

                {

                    if (($part < $minValues[$i]) || ($part > $maxValues[$i]))

                    {

                        return false;

                    }

                }

            } else

            {

                //we have a single number

                if (($time != '*') && (($time < $minValues[$i]) || ($time > $maxValues[$i])))

                {

                    return false;

                }

            }

           

            $i++;

        }

   

        //cleanup

        unset($time, $minValues, $maxValues, $timesTemp, $times, $timeTemp, $i);

       

        return true;

    }

   

    /*

    ** Method to evaluate a crontab style time and determine if it is within

    ** the current minute.

    ** @param string $time

    ** @access private

    */


    private function _taskTimeIsInMinute($time)

    {

        $minValues = array(0, 0, 1, 1, 0);

        $maxValues = array(59, 23, 31, 12, 6);

        $piecesTemp = array_filter(explode(' ', trim($time)));

       

        $pieces = array();

        foreach ($piecesTemp AS $piece)

        {

            $pieces[] = trim($piece);

        }

   

        $container = array();

       

        //break each piece down into an array of allowed values

        $i = 0;

        foreach ($pieces AS $piece)

        {

            if (strpos($piece, ','))

            {

                $eachPart = explode(',', $piece);

                $count = count($eachPart);

   

                if ($count == 2)

                {

                    if (strpos($eachPart[0], '-') && strpos($eachPart[1], '-'))

                    {

                        //range, range

                        $partsOfPart = explode('-', $eachPart[0] . '-' . $eachPart[1]);

                        $container[$i] = array_merge(range($partsOfPart[0], $partsOfPart[1]), range($partsOfPart[2], $partsOfPart[3]));

                    } elseif (strpos($eachPart[0], '-') && !strpos($eachPart[1], '-'))

                    {

                        $partsOfPart = explode('-', $eachPart[0]);

                        $container[$i] = array_merge(range($partsOfPart[0], $partsOfPart[1]), array($eachPart[1]));

                    } elseif (!strpos($eachPart[0], '-') && strpos($eachPart[1], '-'))

                    {

                        $partsOfPart = explode('-', $eachPart[1]);

                        $container[$i] = array_merge(range($partsOfPart[0], $partsOfPart[1]), array($eachPart[0]));

                    } else

                    {

                        foreach ($eachPart AS $part)

                        {

                            $container[$i][] = $part;

                        }

                    }

                } else

                {

                    foreach ($eachPart AS $part)

                    {

                        $container[$i][] = $part;

                    }

                }

            } elseif (strpos($piece, '-'))

            {

                $eachPart = explode('-', $piece);

                $container[$i] = range($eachPart[0], $eachPart[1]);

            } else

            {

                if ($piece == '*')

                {

                    $container[$i] = range($minValues[$i], $maxValues[$i]);

                } else

                {

                    $container[$i] = array($piece);

                }

            }

           

            $i++;

        }

       

        //remove duplicates

        for ($i=0; $i<5; $i++)

        {

            if(!isset($container[$i]))

            {

                $container[$i] = array('0');

            } else

            {

                $container[$i] = array_unique($container[$i]);

            }

        }

       

        //generate valid timestamps for this task

        //check if is in minute

        if (in_array(date('n'), $container[3]) && (in_array(date('w'), $container[4]) || in_array(date('j'), $container[2])) && in_array(date('G'), $container[1]))

        {  

            //loop through each minute, and generate a timestamp for that minute

            //check if it is within the current minute

            foreach($container[0] AS $minute)

            {

                $minute = mktime(date('G'), $minute, 0);

                if (($minute > time()) && ($minute <= (time()+60)))

                {

                    //cleanup

                    unset($time, $minValues, $maxValues, $piecesTemp, $pieces, $piece, $container, $i, $minute);

                   

                    return true;

                }

            }

        }

       

        //cleanup

        unset($time, $minValues, $maxValues, $piecesTemp, $pieces, $piece, $container, $i);

       

        return false;

       

    }

   

    /*

    ** Sends email with output (if specified) of a task.

    ** @param string $output

    ** @param string $script

    ** @access private

    */


    private function _sendOutputEmail($output, $script)

    {

        if (!@mail(

            $this->_email,

            'PHPCronEmulator - Output - ' . $script . ' - ' . date("n\d\Y \a\\t g:i A"),

            $output

        ))

        {

            if ($this->_logErrors)

            {

                $this->_log('Error', 'Could not email script (' . $script . ') output.  Check your server and/or PHP settings for mail().', time());

            }

           

            trigger_error('Could not email script (' . $script . ') output.  Check your server and/or PHP settings for mail().', E_USER_WARNING);

        }

       

        unset($output, $script);

    }

   

    /*

    ** Logs a message to the log file.

    ** @param string $type - type of log entry

    ** @param string $msg - value of log entry

    ** @param integer $time - time of log entry

    ** @access private

    */


    private function _log($type, $msg, $time)

    {

        $handle = fopen('log/log.txt', 'a');

        flock($handle, LOCK_EX);

        fwrite($handle, date("H:i:s", $time) . ' ' . $type . ' - ' . $msg . "\n");

        flock($handle, LOCK_UN);

        fclose($handle);

       

        //cleanup

        unset($type, $msg, $time, $handle);

    }

   

    /*

    ** Checks if PHPCronEmulator is currently running.

    ** @access private

    */


    private function _isRunning()

    {

        $runningTime = (int) trim(file_get_contents('txt/running.txt'));

       

        if ($runningTime >= (time()-60))

        {

            unset($runningTime);

            return true;

        }

       

        unset($runningTime);

        return false;

    }

   

    /*

    ** Updates the pid file so PHPCronEmulator can be determined as running.

    ** @access private

    */


    private function _updateRunningTime()

    {

        $handle = fopen('txt/running.txt', "w");

        flock($handle, LOCK_EX);

        fwrite($handle, time());

        flock($handle, LOCK_UN);

        fclose($handle);

       

        unset($handle);

    }

   

    /*

    ** Checks if PHPCronEmulator has been killed.

    ** @param string $configContent - contents of the configuration file.

    ** @access private

    */


    private function _checkIfKilled($configContent)

    {

        //check if string *kill* was found in the contents

        if (strpos($configContent, '*kill*') !== false)

        {

            //it was found, take it out

            $configContent = str_replace('*kill*', '', $configContent);

           

            //rewrite file

            $handle = fopen('txt/config.txt', "w");

            flock($handle, LOCK_EX);

            fwrite($handle, $configContent);

            flock($handle, LOCK_UN);

            fclose($handle);

           

            //log stoppage

            if ($this->_logEvents)

            {

                $this->_log('Event', 'PHPCronEmulator stopped.', time());

            }

           

            //halt the script

            exit;

        }

       

        unset($configContent);

    }

   

    /*

    ** Starts PHPCronEmulator

    */


    public function run()

    {

        //set script to run "forever"

        set_time_limit(0);

        ignore_user_abort(true);

       

        //infinite loop

        while (true)

        {

            //update running time

            $this->_updateRunningTime();

           

            //if either of these values are not set, instance has just started running

            if (($this->_configHash == null) || ($this->_taskHash == null))

            {

                if ($this->_logEvents)

                {

                    $this->_log('Event', 'PHPCronEmulator started.', time());

                }

            }

           

            //compare current hash against stored hash of the config file.  If they

            //don't match, load new configuration file

            if (!$this->_compareHashes($this->_configHash, md5_file('txt/config.txt')))

            {

                $this->_loadConfigFile();

            }

           

            //compare current hash against stored hash of the tasks file.  If they

            //don't match, load new tasks file

            if (!$this->_compareHashes($this->_taskHash, md5_file('txt/tasks.txt')))

            {

                $this->_loadTaskFile();

            }

           

            //check if we have any tasks

            if (!empty($this->_completeTaskList))

            {

                //loop through each task

                foreach ($this->_completeTaskList AS $task)

                {

                    //determine if the tasks time is within the current minute

                    if ($this->_taskTimeIsInMinute($task['time']))

                    {

                        //include script and capture output

                        ob_start();

                        include $task['script'];

                        $output = ob_get_contents();

                        ob_end_clean();



                        //email output

                        if ($task['email'] == true)

                        {

                            if ($this->_email != null)

                            {

                                $this->_sendOutputEmail($output, $task['script']);

                            } else

                            {

                                if ($this->_logErrors)

                                {

                                    $this->_log('Error', 'Could not email output for script (' . $task['script'] . ') because email address is not set.', time());

                                }

                               

                                trigger_error('Could not email output for script (' . $task['script'] . ') because email address is not set.', E_USER_WARNING);

                            }

                        }

                       

                        //log success

                        if ($this->_logSuccess)

                        {

                            $this->_log('Success', 'Script (' . $task['script'] . ') successfully executed.', time());

                        }

                       

                       

                        //cleanup

                        foreach (get_defined_vars() AS $k => $v)

                        {

                            if ($k != 'this')

                            {

                                unset($$k);

                            }

                        }

                       

                        unset($k, $v);

                    }

                }

            }



            //sleep one minute

            sleep(60);

        }

    }

}

_________________
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.


Last edited by s.dot on Thu Aug 30, 2007 2:39 am, edited 4 times in total.

Top
 Profile  
 
 Post subject:
PostPosted: Tue Aug 21, 2007 10:19 pm 
Offline
Tranquility In Moderation
User avatar

Joined: Sun Feb 06, 2005 8:18 pm
Posts: 5001
Location: Indiana
I'm thinking this would be much better suited with a GUI. For example, a web page telling the status, buttons to start/stop, view/clear the log file, edit the config task file, etc.

As it stands, it's way too confusing.

Does anyone know of an existing php code library like the one i've written? (or does something similar) I'd hate to "re-invent the wheel".

_________________
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Aug 22, 2007 6:17 am 
Offline
DevNet Master
User avatar

Joined: Wed Jun 27, 2007 9:44 am
Posts: 4313
Location: Sofia, Bulgaria
http://www.bitfolge.de/pseudocron-en.html

_________________
There are 10 types of people in this world, those who understand binary and those who don't


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 30, 2007 2:20 am 
Offline
Tranquility In Moderation
User avatar

Joined: Sun Feb 06, 2005 8:18 pm
Posts: 5001
Location: Indiana
I've edited the initial post, to spare a new topic. New critiques/comments/suggestions are welcome.

I'm now working on a web administration panel to create/edit config/tasks/log files, and to start/stop the script.

_________________
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Aug 30, 2007 5:00 pm 
Offline
Tranquility In Moderation
User avatar

Joined: Sun Feb 06, 2005 8:18 pm
Posts: 5001
Location: Indiana
I'm really starting to think there is no market for this kind of script. :lol: Perhaps I should've researched that a little better before I went gung-ho on it.

_________________
Set Search Time - A google chrome extension. When you search only results from the past year (or set time period) are displayed. Helps tremendously when using new technologies to avoid outdated results.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 5 posts ] 

All times are UTC - 5 hours


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Jump to:  
Powered by phpBB® Forum Software © phpBB Group