support concurrent requests with cutycapt
[phancap.git] / src / phancap / Adapter / Cutycapt.php
1 <?php
2 namespace phancap;
3
4 class Adapter_Cutycapt
5 {
6     protected $lockHdl;
7     protected $lockFile = null;
8
9     public function isAvailable()
10     {
11         //FIXME: setup check for xvfbrun, cutycapt, convert
12     }
13
14     public function render(Image $img, Options $options)
15     {
16         $format = $options->values['sformat'];
17         if ($format == 'jpg') {
18             $format = 'jpeg';
19         }
20
21         $serverNumber = $this->getServerNumber($options);
22         $tmpPath = $img->getPath() . '-tmp';
23         $cmd = 'cutycapt'
24             . ' --url=' . escapeshellarg($options->values['url'])
25             . ' --out-format=' . escapeshellarg($format)
26             . ' --out=' . escapeshellarg($tmpPath)
27             . ' --max-wait=10000'
28             . ' --min-width=' . $options->values['bwidth'];
29         if ($options->values['bheight'] !== null) {
30             $cmd .= ' --min-height=' . $options->values['bheight'];
31         }
32
33         $xvfbcmd = 'xvfb-run'
34             . ' -e /dev/stdout'
35             . ' --server-args="-screen 0, 1024x768x24"'
36             . ' --server-num=' . $serverNumber;
37         Executor::run($xvfbcmd . ' ' . $cmd);
38
39         $this->resize($tmpPath, $img, $options);
40     }
41
42     protected function getServerNumber()
43     {
44         //clean stale lock files
45         $this->cleanup();
46
47         $num = 100;
48         $bFound = false;
49         do {
50             ++$num;
51             $f = $this->config->cacheDir . 'tmp-curlycapt-server-' . $num . '.lock';
52             $this->lockHdl = fopen($f, 'w');
53             if (flock($this->lockHdl, LOCK_EX | LOCK_NB)) {
54                 $this->lockFile = $f;
55                 $bFound = true;
56                 break;
57             } else {
58                 fclose($this->lockHdl);
59             }
60         } while ($num < 200);
61
62         if (!$bFound) {
63             throw new \Exception('Too many requests running');
64         }
65
66         $this->lockFile = $f;
67         return $num;
68     }
69
70     public function cleanup()
71     {
72         if ($this->lockFile !== null && $this->lockHdl) {
73             flock($this->lockHdl, LOCK_UN);
74             unlink($this->lockFile);
75         }
76
77         $lockFiles = glob(
78             $this->config->cacheDir . 'tmp-curlycapt-server-*.lock'
79         );
80
81         $now = time();
82         foreach ($lockFiles as $file) {
83             if ($now - filemtime($file) > 120) {
84                 //delete stale lock file; probably something crashed.
85                 unlink($file);
86             }
87         }
88     }
89
90     protected function resize($tmpPath, $img, $options)
91     {
92         if ($options->values['sformat'] == 'pdf') {
93             //nothing to resize.
94             rename($tmpPath, $img->getPath());
95             return;
96         }
97
98         $crop = '';
99         if ($options->values['smode'] == 'screen') {
100             $crop = ' -crop ' . $options->values['swidth']
101                 . 'x' . $options->values['sheight']
102                 . '+0x0';
103         }
104
105         $convertcmd = 'convert'
106             . ' ' . escapeshellarg($tmpPath)
107             . ' -resize ' . $options->values['swidth']
108             . $crop
109             . ' ' . escapeshellarg($img->getPath());
110         Executor::run($convertcmd);
111         //var_dump($convertcmd);die();
112         unlink($tmpPath);
113     }
114
115     public function setConfig(Config $config)
116     {
117         $this->config = $config;
118     }
119 }
120 ?>