0012024: remember popup window size in client state
[tine20] / tine20 / Tinebase / AsyncJob.php
1 <?php
2 /**
3  * Tine 2.0
4  *
5  * @package     Tinebase
6  * @subpackage  AsyncJob
7  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
8  * @author      Philipp Schüle <p.schuele@metaways.de>
9  * @copyright   Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
10  * 
11  */
12
13 /**
14  * controller for async event management
15  *
16  * @package     Tinebase
17  * @subpackage  AsyncJob
18  */
19 class Tinebase_AsyncJob
20 {
21     /**
22      * @var Tinebase_Backend_Sql
23      */
24     protected $_backend;
25     
26     /**
27      * default seconds till job is declared 'failed'
28      *
29      */    
30     const SECONDS_TILL_FAILURE = 300;
31     
32     /**
33      * holds the instance of the singleton
34      *
35      * @var Tinebase_AsyncJob
36      */
37     private static $instance = NULL;
38     
39     /**
40      * the constructor
41      *
42      */
43     private function __construct()
44     {
45         $this->_backend = new Tinebase_Backend_Sql(array(
46             'modelName' => 'Tinebase_Model_AsyncJob', 
47             'tableName' => 'async_job',
48         ));
49     }
50     
51     /**
52      * the singleton pattern
53      *
54      * @return Tinebase_AsyncJob
55      */
56     public static function getInstance() 
57     {
58         if (self::$instance === NULL) {
59             self::$instance = new Tinebase_AsyncJob();
60         }
61         return self::$instance;
62     }
63     
64     /**************************** public functions *********************************/
65     
66     /**
67      * check if job is running and returns next sequence / FALSE if a job is running atm
68      *
69      * @param string $_name
70      * @return boolean|integer
71      */
72     public function getNextSequence($_name)
73     {
74         $lastJob = $this->getLastJob($_name);
75         if ($lastJob) {
76             if ($lastJob->status === Tinebase_Model_AsyncJob::STATUS_RUNNING) {
77                 if (Tinebase_DateTime::now()->isLater($lastJob->end_time)) {
78                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__
79                         . ' Old ' . $_name . ' job is running too long. Finishing it now.');
80                     $this->finishJob($lastJob, Tinebase_Model_AsyncJob::STATUS_FAILURE);
81                 } else {
82                     if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
83                         . ' Current job runs till ' . $lastJob->end_time->toString());
84                 }
85                 $result = FALSE;
86             } else {
87                 $result = $lastJob->seq + 1;
88             }
89         } else {
90             // begin new sequence
91             $result = 1;
92         }
93         
94         return $result;
95     }
96     
97     /**
98      * get last job of this name
99      * 
100      * @param string $name
101      * @return Tinebase_Model_AsyncJob
102      */
103     public function getLastJob($name)
104     {
105         $filter = new Tinebase_Model_AsyncJobFilter(array(array(
106             'field'     => 'name',
107             'operator'  => 'equals',
108             'value'     => $name
109         )));
110         $pagination = new Tinebase_Model_Pagination(array(
111             'sort'        => 'seq',
112             'dir'         => 'DESC',
113             'limit'       => 1,
114         ));
115         $jobs = $this->_backend->search($filter, $pagination);
116         $lastJob = $jobs->getFirstRecord();
117         return $lastJob;
118     }
119
120     /**
121      * start new job / check fencing
122      *
123      * @param string $_name
124      * @param int $timeout
125      * @return NULL|Tinebase_Model_AsyncJob
126      */
127     public function startJob($_name, $_timeout = self::SECONDS_TILL_FAILURE)
128     {
129         $result = NULL;
130         
131         $nextSequence = $this->getNextSequence($_name);
132         if ($nextSequence === FALSE) {
133             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
134                 . ' Job ' . $_name . ' is already running. Skipping ...');
135             return $result;
136         }
137         
138         try {
139             $result = $this->_createNewJob($_name, $nextSequence, $_timeout);
140         } catch (Zend_Db_Statement_Exception $zdse) {
141             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
142                 . ' Could not start job ' . $_name . ': ' . $zdse);
143         }
144         
145         return $result;
146     }
147     
148     /**
149      * create new job
150      * 
151      * @param string $_name
152      * @param integer $_sequence
153      * @param int $timeout
154      * @return Tinebase_Model_AsyncJob
155      */
156     protected function _createNewJob($_name, $_sequence, $_timeout)
157     {
158         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ 
159             . ' Creating new Job ' . $_name);
160         
161         $date = new Tinebase_DateTime();
162         $date->addSecond($_timeout);
163         
164         $job = new Tinebase_Model_AsyncJob(array(
165             'name'              => $_name,
166             'start_time'        => new Tinebase_DateTime(),
167             'end_time'          => $date,
168             'status'            => Tinebase_Model_AsyncJob::STATUS_RUNNING,
169             'seq'               => $_sequence
170         ));
171         
172         return $this->_backend->create($job);
173     }
174
175     /**
176      * only keep the last 60 jobs and purge all other
177      * 
178      * @param Tinebase_Model_AsyncJob $job
179      */
180     protected function _purgeOldJobs(Tinebase_Model_AsyncJob $job)
181     {
182         $deleteBefore = $job->seq - 60;
183         
184         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__\r
185             . ' Purging old Jobs before sequence: ' . $deleteBefore);\r
186         
187         // avoid overloading by deleting old jobs in batches only
188         $idsToDelete = $this->_backend->search(new Tinebase_Model_AsyncJobFilter(array(
189             array(
190                 'field'    => 'seq',
191                 'operator' => 'less',
192                 'value'    => $deleteBefore
193             )
194         )), new Tinebase_Model_Pagination(array('limit' => 10000)), true);
195         
196         $this->_backend->delete($idsToDelete);\r
197     }
198     
199     /**
200      * finish job
201      *
202      * @param Tinebase_Model_AsyncJob $_asyncJob
203      * @param string $_status
204      * @param string $_message
205      * @return Tinebase_Model_AsyncJob
206      */
207     public function finishJob(Tinebase_Model_AsyncJob $_asyncJob, $_status = Tinebase_Model_AsyncJob::STATUS_SUCCESS, $_message = NULL)
208     {
209         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
210             . ' Finishing job ' . $_asyncJob->name . ' with status ' . $_status);
211         
212         $this->_purgeOldJobs($_asyncJob);
213         
214         $_asyncJob->end_time = Tinebase_DateTime::now();
215         $_asyncJob->status = $_status;
216         if ($_message !== NULL) {
217             $_asyncJob->message = $_message;
218         }
219         
220         $result = $this->_backend->update($_asyncJob);
221         
222         return $result;
223     }
224 }