* @license PHP License * @package WB * @subpackage base */ WBClass::load('WBDaemon' , 'WBClock' , 'WBFile'); /** * Site screen shot server * * @version 0.2.0 * @package WB * @subpackage base */ class WBDaemon_SiteScreenShot extends WBDaemon { /** * Environment variables to configure the server * @var int */ protected $env = array( 'DISPLAY' => ':101' ); const XSERVER = '/usr/bin/Xvfb'; const MOZILLA = '/usr/bin/firefox'; protected $maxWindow = 2; /** * Table access * @var WBDatasource_Table */ protected $table; /** * file * @var WBVFS_File */ protected $file; /** * dictionary * @var WBDictionary_URL */ protected $dict; /** * screeshot file * @var WBFile */ protected $shot; protected $pids = array( 'x' => 0, 'mozilla' => 0 ); protected $progress = array(); /** * total count of screenshots * * @var int */ protected $count = 0; /** * 2nd constructor * */ protected function init() { $this->startX(); $this->startMozilla(); $this->table = WBClass::create('WBDatasource_Table'); $this->file = WBClass::create('WBVFS_File'); $this->dict = WBClass::create('WBDictionary_URL'); $this->shot = WBClass::create('WBFile'); $this->shot->mkdir('var/tmp'); $this->maxWindow = WBParam::get('wb/siteshot/window/max', $this->maxWindow); return true; } /** * main loop to run daemon process * */ protected function process() { if (($this->count % 50) == 0) { $this->snapshotInProgress(true); $this->hangUp(); } $untilDone = false; $limit = $this->maxWindow - count($this->progress); if ($limit <= 0) { $untilDone = true; $limit = $this->maxWindow; } $this->snapshotInProgress($untilDone); $options = array( 'limit' => $limit ); $ids = $this->table->getIds('urlscreenshotqueue', null, null, $options); if (count($ids) == 0) { echo "queue is empty\n"; sleep(5); return; } foreach ($ids as $id) { if (count($this->progress) >= $this->maxWindow) { break; } $this->dict->load($id); if (!$this->dict->getId()) { $this->table->delete('urlscreenshotqueue', $id); continue; } $this->openurl(); $this->table->delete('urlscreenshotqueue', $id); } } /** * open ULR in browser * * */ protected function openurl() { // get list of browser windows $win = sprintf('DISPLAY=%s xdotool search --onlyvisible -title iceweasel' , $this->env['DISPLAY']); $before = $this->exec($win); // open url in new browser window $moz = sprintf('DISPLAY=%s %s -remote "openurl(%s, new-window)"' , $this->env['DISPLAY'] , self::MOZILLA , $this->dict->getWord()); $this->exec($moz); sleep(1); // extract id of new browser window $after = $this->exec($win); $new = array_values(array_diff($after, $before)); // schedule taking snapshot in 10 seconds $this->shot->tempnam('shot'); $p = array( 'count' => ++$this->count, 'id' => $this->dict->getId(), 'win' => $new[0], 'time' => time() + 10, 'tmp' => $this->shot->realpath(), 'url' => $this->dict->getWord() ); $this->progress[] = $p; } /** * take snapshot of opened URLs * * Walk through list of opened URLs and take snapshots. Usually taking * snapshot is scheduled 10 seconds after URL is opened. Therefore, this * method may return even if not all snapshots are taken. Use parameter * $untilDone to avoid this behaviour * * @param bool $untilDone do until all snapshots are take */ protected function snapshotInProgress($untilDone = false) { if (empty($this->progress)) { return; } echo "snapshot " . count($this->progress) . "\n"; $now = time(); $tmp = array(); while (count($this->progress)) { $p = array_shift($this->progress); if ($now < $p['time']) { $tmp[] = $p; continue; } $this->snapshot($p); } $this->progress = $tmp; if (!$untilDone) { return; } while (!empty($this->progress)) { sleep(2); echo "snapshot until done " . count($this->progress) . "\n"; $this->snapshotInProgress(); } } /** * take snapshot * * * @param array $p */ protected function snapshot($p) { echo 'snapshot ' . print_r($p, true) . "\n"; // get list of browser windows $win = sprintf('DISPLAY=%s xdotool search --onlyvisible -title iceweasel' , $this->env['DISPLAY']); $list = $this->exec($win); print_r($list); // move $cmd = sprintf('DISPLAY=%s xdotool windowsize %s 1280 1280' , $this->env['DISPLAY'] , $p['win']); $this->exec($cmd); // resize $cmd = sprintf('DISPLAY=%s xdotool windowmove %s 0 0' , $this->env['DISPLAY'] , $p['win']); $this->exec($cmd); // raise window $cmd = sprintf('DISPLAY=%s xdotool windowraise %s' , $this->env['DISPLAY'] , $p['win']); $this->exec($cmd); // try to get proper screenshot for ($i = 0; $i < 5; ++$i) { sleep(1); // screenshot $cmd = sprintf('xwd -display %s -id %s -silent -out %s.xwd' , $this->env['DISPLAY'] , $p['win'] , $p['tmp']); $this->exec($cmd, true); // screenshot to jpeg $cmd = sprintf('mogrify -format jpeg %s.xwd' , $p['tmp']); $this->exec($cmd, true); if (file_exists($p['tmp'] . '.jpeg')) { break; } } // close window $cmd = sprintf('DISPLAY=%s xdotool key "ctrl+w"' , $this->env['DISPLAY']); $this->exec($cmd); unlink($p['tmp']); unlink($p['tmp'] . '.xwd'); chmod($p['tmp'] . '.jpeg', 0666); } /** * run command * * Auxilliary method to call external programmes * * @param striong $cmd * @return array stdout */ protected function exec($cmd) { $out = array(); echo "$cmd \n"; exec($cmd, $out, $ret); if ($ret) { if ($tolerant) { return $out; } echo "$ret -> $cmd \n"; echo '
' . var_export($out, true) . "
\n"; exit(1); } return $out; } /** * stop daemon */ protected function halt() { $this->snapshotInProgress(true); $this->stopMozilla(); $this->stopX(); return true; } /** * shutdown */ protected function shutdown() { $this->snapshotInProgress(true); $this->stopMozilla(); $this->stopX(); return true; } /** * hangup * * restart everything */ protected function hangUp() { $this->snapshotInProgress(true); $this->stopMozilla(); $this->stopX(); sleep(2); $this->startX(); $this->startMozilla(); return true; } /** * start virtual frame buffer X server * * */ protected function startX() { $args = array( $this->env['DISPLAY'], '-screen', '0', '1280x1280x24' ); $this->forkProg('x', self::XSERVER, $args); } /** * stop virtual frame buffer X server * */ protected function stopX() { $this->killProg('x'); } /** * start mozilla browser * */ protected function startMozilla() { $args = array( 'about:' ); $this->forkProg('mozilla', self::MOZILLA, $args); } /** * stop mozilla browser * */ protected function stopMozilla() { $this->killProg('mozilla'); } /** * fork programme * * Fork process and execute binary with arguments and current * environment. * * @param string $name internal name to store pid * @param string $bin programme to call * @param array $args command line arguments */ protected function forkProg($name, $bin, $args) { $this->killProg($name); $pid = pcntl_fork(); if ($pid == -1) { echo "Failed to fork process for $name\n"; $this->running = false; return; } if ($pid) { // remember child's pid $this->pids[$name] = $pid; // wait for programme to settle sleep(5); return; } pcntl_exec($bin, $args, $this->env); exit(0); } /** * kill forked programme * * Kill process and wait for exit status. * * @param string $name internal name to store pid */ protected function killProg($name) { if (!$this->pids[$name]) { return; } posix_kill($this->pids[$name], SIGTERM); $status = null; pcntl_waitpid($this->pids[$name], $status ); $this->pids[$name] = 0; } }?>