fix CS
[phancap.git] / src / phancap / Adapter / Cutycapt.php
1 <?php
2 /**
3  * Part of phancap
4  *
5  * PHP version 5
6  *
7  * @category  Tools
8  * @package   Adapter_Cutycapt
9  * @author    Christian Weiske <cweiske@cweiske.de>
10  * @copyright 2014 Christian Weiske
11  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
12  * @link      http://cweiske.de/phancap.htm
13  */
14 namespace phancap;
15
16 /**
17  * Screenshot rendering using the "cutycapt" command line tool.
18  *
19  * @category  Tools
20  * @package   Adapter_Cutycapt
21  * @author    Christian Weiske <cweiske@cweiske.de>
22  * @copyright 2014 Christian Weiske
23  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
24  * @version   Release: @package_version@
25  * @link      http://cweiske.de/phancap.htm
26  */
27 class Adapter_Cutycapt
28 {
29     /**
30      * Lock file handle
31      * @var resourece
32      */
33     protected $lockHdl;
34
35     /**
36      * Lock file path
37      * @var string
38      */
39     protected $lockFile = null;
40
41     /**
42      * Check if all dependencies are available.
43      *
44      * @return mixed TRUE if all is fine, array with error messages otherwise
45      */
46     public function isAvailable()
47     {
48         $old = error_reporting(error_reporting() & ~E_STRICT);
49         $arErrors = array();
50         if (\System::which('xvfb-run') === false) {
51             $arErrors[] = '"xvfb-run" is not installed';
52         }
53         if (\System::which('cutycapt') === false) {
54             $arErrors[] = '"cutycapt" is not installed';
55         }
56         if (\System::which('convert') === false) {
57             $arErrors[] = '"convert" (imagemagick) is not installed';
58         }
59
60         error_reporting($old);
61         if (count($arErrors)) {
62             return $arErrors;
63         }
64
65         return true;
66     }
67
68     /**
69      * Render a website screenshot
70      *
71      * @param Image   $img     Image configuration
72      * @param Options $options Screenshot configuration
73      *
74      * @return void
75      * @throws \Exception When something fails
76      */
77     public function render(Image $img, Options $options)
78     {
79         $format = $options->values['sformat'];
80         if ($format == 'jpg') {
81             $format = 'jpeg';
82         }
83
84         $serverNumber = $this->getServerNumber($options);
85         $tmpPath = $img->getPath() . '-tmp';
86         $cmd = 'cutycapt'
87             . ' --url=' . escapeshellarg($options->values['url'])
88             . ' --out-format=' . escapeshellarg($format)
89             . ' --out=' . escapeshellarg($tmpPath)
90             . ' --max-wait=10000'
91             . ' --min-width=' . $options->values['bwidth'];
92         if ($options->values['bheight'] !== null) {
93             $cmd .= ' --min-height=' . $options->values['bheight'];
94         }
95
96         $xvfbcmd = 'xvfb-run'
97             . ' -e /dev/stdout'
98             . ' --server-args="-screen 0, 1024x768x24"'
99             . ' --server-num=' . $serverNumber;
100         Executor::run($xvfbcmd . ' ' . $cmd);
101
102         $this->resize($tmpPath, $img, $options);
103     }
104
105     /**
106      * Get a free X server number.
107      *
108      * Each xvfb-run process needs its own free server number.
109      * Needed for multiple parallel requests.
110      *
111      * @return integer Server number
112      */
113     protected function getServerNumber()
114     {
115         //clean stale lock files
116         $this->cleanup();
117
118         $num = 100;
119         $bFound = false;
120         do {
121             ++$num;
122             $f = $this->config->cacheDir . 'tmp-curlycapt-server-' . $num . '.lock';
123             $this->lockHdl = fopen($f, 'w');
124             if (flock($this->lockHdl, LOCK_EX | LOCK_NB)) {
125                 $this->lockFile = $f;
126                 $bFound = true;
127                 break;
128             } else {
129                 fclose($this->lockHdl);
130             }
131         } while ($num < 200);
132
133         if (!$bFound) {
134             throw new \Exception('Too many requests running');
135         }
136
137         $this->lockFile = $f;
138         return $num;
139     }
140
141     /**
142      * Unlock lock file and clean up old lock files
143      *
144      * @return void
145      */
146     public function cleanup()
147     {
148         if ($this->lockFile !== null && $this->lockHdl) {
149             flock($this->lockHdl, LOCK_UN);
150             unlink($this->lockFile);
151         }
152
153         $lockFiles = glob(
154             $this->config->cacheDir . 'tmp-curlycapt-server-*.lock'
155         );
156
157         $now = time();
158         foreach ($lockFiles as $file) {
159             if ($now - filemtime($file) > 120) {
160                 //delete stale lock file; probably something crashed.
161                 unlink($file);
162             }
163         }
164     }
165
166     /**
167      * Convert an image to the given size.
168      *
169      * Target file is the path of $img.
170      *
171      * @param string  $tmpPath Path of image to be scaled.
172      * @param Image   $img     Image configuration
173      * @param Options $options Screenshot configuration
174      *
175      * @return void
176      */
177     protected function resize($tmpPath, Image $img, Options $options)
178     {
179         if ($options->values['sformat'] == 'pdf') {
180             //nothing to resize.
181             rename($tmpPath, $img->getPath());
182             return;
183         }
184
185         $crop = '';
186         if ($options->values['smode'] == 'screen') {
187             $crop = ' -crop ' . $options->values['swidth']
188                 . 'x' . $options->values['sheight']
189                 . '+0x0';
190         }
191
192         $convertcmd = 'convert'
193             . ' ' . escapeshellarg($tmpPath)
194             . ' -resize ' . $options->values['swidth']
195             . $crop
196             . ' ' . escapeshellarg($img->getPath());
197         Executor::run($convertcmd);
198         //var_dump($convertcmd);die();
199         unlink($tmpPath);
200     }
201
202     /**
203      * Set phancap configuration
204      *
205      * @param Config $config Phancap configuration
206      *
207      * @return void
208      */
209     public function setConfig(Config $config)
210     {
211         $this->config = $config;
212     }
213 }
214 ?>