use absolute path for calling tine20.php in multiproc command
[tine20] / tine20 / Calendar / Frontend / CalDAV / Cli.php
1 <?php
2 /**
3  * Tine 2.0
4  * @package     Calendar
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @author      Paul Mehrer <p.mehrer@metaways.de>
7  * @author      Philipp Schüle <p.schuele@metaways.de>
8  * @copyright   Copyright (c) 2014 Metaways Infosystems GmbH (http://www.metaways.de)
9  */
10
11 /**
12  * CalDAV import helper functions for CLI
13  *
14  * @package     Calendar
15  */
16 class Calendar_Frontend_CalDAV_Cli
17 {
18     /**
19      * caldav users / single user data
20      * 
21      * @var array
22      */
23     protected $_users = array();
24     
25     /**
26      * @var Calendar_Import_CalDav_Client
27      */
28     protected $_caldavClient = null;
29     
30     /**
31      * CLI opts
32      * 
33      * @var Zend_Console_Getopt
34      */
35     protected $_opts;
36     
37     /**
38      * parsed CLI arguments
39      * 
40      * @var array
41      */
42     protected $_args;
43     
44     protected $_numberOfImportRuns = 2;
45     protected $_caldavClientClass = 'Calendar_Import_CalDav_Client';
46     protected $_appName = 'Calendar';
47     
48     /**
49      * the constructor
50      */
51     public function __construct(Zend_Console_Getopt $opts, $args)
52     {
53         $this->_opts = $opts;
54         $this->_args = $args;
55         
56         if (Tinebase_Core::isLogLevel(Zend_Log::INFO))
57             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' Init caldav cli helper with params: ' . print_r($args, true));
58         
59         $this->_readCalDavUserFile($args['caldavuserfile'], isset($args['line']) ? $args['line'] : 0);
60         
61         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG))
62              Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' User(s): '
63                 . (isset($this->_users['username']) ? $this->_users['username'] : count($this->_users)));
64         
65         $caldavClientOptions = array(
66             'baseUri' => $args['url']
67         );
68         if (isset($this->_users['username'])) {
69             $caldavClientOptions = array_merge($caldavClientOptions, array(
70                 'userName' => $this->_users['username'],
71                 'password' => $this->_users['password'],
72             ));
73         }
74         $this->_caldavClient = new $this->_caldavClientClass($caldavClientOptions, 'MacOSX');
75         $this->_caldavClient->setVerifyPeer(false);
76     }
77     
78     /**
79      * import all calendars
80      */
81     public function importAllCalendars()
82     {
83         $this->_caldavClient->importAllCalendarsForUsers($this->_users);
84     }
85     
86     /**
87      * import all data for users
88      */
89     public function importAllCalendarDataForUsers()
90     {
91         $this->_caldavClient->importAllCalendarDataForUsers($this->_users);
92     }
93
94     /**
95      * import all data for single user
96      */
97     public function importAllCalendarData()
98     {
99         $this->_caldavClient->importAllCalendarData($this->_args['run'] == 1 ? true : false);
100     }
101     
102     /**
103      * update all data for users
104      */
105     public function updateAllCalendarDataForUsers()
106     {
107         $this->_caldavClient->updateAllCalendarDataForUsers($this->_users);
108     }
109     
110     /**
111      * update all data for single user
112      */
113     public function updateAllCalendarData()
114     {
115         $this->_caldavClient->updateAllCalendarData($this->_args['run'] == 1 ? true : false);
116     }
117     
118     /**
119      * run import/update with multiple processes
120      * 
121      * @param string $mode
122      * @throws Exception
123      */
124     public function runImportUpdateMultiproc($mode)
125     {
126         $numProc = intval($this->_args['numProc']);
127         $this->_validateNumProc($numProc);
128         
129         if (empty($this->_opts->passwordfile)) {
130             throw new Exception('Passwordfile required for this method');
131         }
132         
133         if ($mode === 'import' && (empty($this->_args['dataonly']) || $this->_args['dataonly'] == false)) {
134             // first import the calendars, serial sadly
135             $this->importAllCalendars();
136         }
137         
138         $cliParams = '--username ' . $this->_opts->username . ' --passwordfile ' . $this->_opts->passwordfile
139         . ' --method ' . $this->_appName . '.' . $mode . 'CalDavDataForUser'
140                 . ' url=' .  $this->_args['url'] . ' caldavuserfile=' . $this->_args['caldavuserfile'];
141         
142         for ($run = 1; $run <= $this->_numberOfImportRuns; ++$run) {
143             $this->_runMultiProcessImportUpdate($numProc, $cliParams, $run);
144         }
145     }
146     
147     /**
148      * validate num procs
149      * 
150      * @param string $numProc
151      * @throws Exception
152      */
153     protected function _validateNumProc($numProc)
154     {
155         if ($numProc < 1) {
156             throw new Exception('numProc: ' . $numProc . ' needs to be at least 1');
157         }
158         if ($numProc > 32) {
159             throw new Exception('numProc: ' . $numProc . ' needs to be lower than 33');
160         }
161     }
162     
163     /**
164      * run multi process command
165      * 
166      * do multiprocess part, no system resources may be used as of here, 
167      * like file handles, db resources... see pcntl_fork man page!
168      * 
169      * @todo add pids to processes array to allow better control 
170      * @todo generalize and move to Tinebase_CalDAV_Cli_Abstract
171      * 
172      * @param integer $numProc
173      * @param string $cliParams
174      * @param integer $run
175      */
176     protected function _runMultiProcessImportUpdate($numProc, $cliParams, $run = 1)
177     {
178         // $processes = array();
179         $processes = 0;
180         $line = 0;
181         foreach ($this->_users as $user => $pwd)
182         {
183             ++$line;
184             //if (count($processes) >= $numProc) {
185             if ($processes >= $numProc) {
186                 $pid = pcntl_wait($status);
187     
188                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
189                         . ' pid: ' . $pid);
190     
191                 // debug
192                 //                     echo '1' . (int) pcntl_wexitstatus($status);
193                 //                     echo '2' . (int) pcntl_wifexited($status);
194                 //                     echo '3' . (int) pcntl_wifsignaled($status);
195                 //                     echo '4' . (int) pcntl_wifstopped($status);
196                 //                     echo '5' . (int) pcntl_wstopsig($status);
197                 //                     echo '6' . (int) pcntl_wtermsig($status);
198     
199                 if (pcntl_wifexited($status) === false ) {
200                     exit('pcntl_wait return value was not found in process list: ' . $pid . ' status: ' . $status . PHP_EOL);
201                 }
202                 
203                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
204                     . ' Child exited');
205                 
206                 // unset($processes[$pid]);
207                 --$processes;
208             }
209     
210             $pid = pcntl_fork();
211             if ($pid == -1) {
212                 exit('could not fork' . PHP_EOL);
213             } else if ($pid) {
214                 // we are the parent
215                 // $processes[$pid] = true;
216                 ++$processes;
217             } else {
218                 // we are the child
219                 $config = ($this->_opts->config) ? '--config=' . $this->_opts->config . ' ' : '';
220                 $command = dirname(dirname(dirname(dirname(__FILE__)))) . '/tine20.php ' . $config . $cliParams . ' run=' . $run . ' line=' . $line;
221                 
222                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
223                      . ' Spawning new child with command: ' . $command);
224                 
225                 exec($command);
226                 exit();
227             }
228         }
229         // wait for childs to finish
230         // while (count($processes) > 0) {
231         while ($processes > 0) {
232             $pid = pcntl_wait($status);
233             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . ' ' . __LINE__
234                     . ' pid: ' . $pid);
235     
236             if (pcntl_wifexited($status) === false) {
237                 exit('pcntl_wait return value was not found in process list: ' . $pid . ' status: ' . $status . PHP_EOL);
238             }
239             // unset($processes[$pid]);
240             --$processes;
241         }
242     }
243     
244     /**
245      * read caldav user credentials file
246      * 
247      * - file should have the following format (CSV):
248      * USERNAME1;PASSWORD1
249      * USERNAME2;PASSWORD2
250      * 
251      * @param string $file
252      * @throws Exception
253      */
254     protected function _readCalDavUserFile($file, $line = 0)
255     {
256         if (!($fh = fopen($file, 'r'))) {
257             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Couldn\'t open file: '.$file);
258             throw new Exception('Couldn\'t open file: '.$file);
259         }
260         $this->_users = array();
261         $i = 0;
262         while ($row = fgetcsv($fh, 2048, ';'))
263         {
264             if ($line > 0 && ++$i == $line) {
265                 // only fetch single user
266                 $this->_users = array('username' => $row[0], 'password' => $row[1]);
267                 return;
268             }
269             $this->_users[$row[0]] = $row[1];
270         }
271         if (count($this->_users) < 1) {
272             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' No users found in: '.$file);
273             throw new Exception('No users found in: '.$file);
274         }
275         if ($line > 0) {
276             Tinebase_Core::getLogger()->err(__METHOD__ . '::' . __LINE__ . ' Line: '.$line. ' out of bounds');
277             throw new Exception('No user found, line: '.$line. ' out of bounds');
278         }
279     }
280 }