Soshot/app/Soshot/MakeThumb.php

297 lines
11 KiB
PHP

<?php
namespace App\Soshot;
use App\DataBase\DataBase;
use App\Utils\ResizeToDemande;
use HeadlessChromium\Browser;
use HeadlessChromium\BrowserFactory;
use HeadlessChromium\Page;
use HeadlessChromium\Exception\BrowserConnectionFailed;
use HeadlessChromium\Exception\OperationTimedOut;
use Noodlehaus\Config;
if (!function_exists('n_print')) {
function n_print($data, $name = '') {
print_r($data, 1);
}
}
class MakeThumb {
private $queuePath = __DIR__ . '/../../cache/queue/';
private $thumbPath = __DIR__ . '/../../cache/img/';
private $fileSocket = __DIR__ . '/../../cache/tmp/chrome-php-socket';
private $fileList = [];
private $maxGenPerBatch = 2;
private $demandes = [];
private $open = false;
private $conf;
private $db;
private $chromePath = '';
private $tmpDir = '/tmp/chrome_soshot';
/**
* Creates a new instance of the class.
*
* This method loads the default and user configuration files using the `Config`
* class. It sets the maximum number of generations per batch, the Chrome path, and
* the queue path. It then retrieves the list of JSON files in the queue directory
* and decodes their contents to populate the `$demandes` property. Finally, it
* initializes the `DataBase` object and assigns it to the `$db` property.
*
* @return void
*/
function __construct() {
$this->conf = (object)Config::load([__DIR__ . '/../../datas/config.default.json', '?' . __DIR__ . '/../../datas/config.json'])->all();
$this->maxGenPerBatch = $this->conf->maxGenPerBatch;
$this->chromePath = $this->conf->chromePath;
foreach (array_slice(glob($this->queuePath . "*.json"), 0, $this->maxGenPerBatch) as $filename) {
$this->fileList[] = $filename;
}
foreach ($this->fileList as $demande) {
$this->demandes[] = json_decode(file_get_contents($demande));
}
$this->db = new DataBase();
}
/**
* Processes the demands in the queue.
*
* This method iterates over the list of demands in the `$demandes` property.
* For each demand, it sets the parameters for the `DataBase` object, writes the HMAC
* to a temporary file, and checks if the demand is already complete.
*
* If the demand is complete and not a PDF, it resizes the image to the demanded size using the
* `ResizeToDemande` class.
*
* If the demand is not complete, it generates the complete image using the `makeComplete` method.
*
* In either case, it deletes the demand from the queue using the `deleteQueue` method and
* updates the database using the `addUpdate` method.
*
* Finally, it sets the `$open` property to `true` to indicate that the queue is open.
* If the `$open` property is `true`, it generates a complete
* image for all demands in the queue using the `makeComplete` method.
*
* @return void
*/
public function compute() {
foreach ($this->demandes as $demande) {
$this->db->setParams($demande);
file_put_contents('/tmp/soshot_queue', $demande->hmac);
// todo verif permit type
if ($this->testComplete($demande->complete) && $demande->type !== 'pdf') {
if (ResizeToDemande::makeDemande($demande)) {
$this->deleteQueue($demande->hmac);
$this->db->addUpdate(1, $demande->type);
}
} else {
$this->makeComplete($demande);
if ($demande->type !== 'pdf') {
if (!ResizeToDemande::makeDemande($demande)) {
// todo log
}
}
$this->deleteQueue($demande->hmac);
$this->db->addUpdate(1, $demande->type);
$completeDemande = (object)[
'hmac' => $demande->hmac,
'url' => $demande->url,
'type' => 'complete'
];
$complete = new DataBase($completeDemande);
$complete->addUpdate(1, $demande->type);
}
$this->open = true;
if ($this->open === true) {
$this->makeComplete(null, true);
}
}
}
/**
* Deletes a demand from the queue.
*
* This method takes an HMAC as a parameter and deletes the corresponding JSON file
* from the queue directory using the `unlink` function.
*
* @param string $hmac The HMAC of the demand to delete.
*
* @return void
*/
private function deleteQueue(string $hmac) {
if (file_exists($this->queuePath . $hmac . '.json')) {
unlink($this->queuePath . $hmac . '.json');
}
}
/**
* Checks if a file exists.
*
* This method takes a file path as a parameter and checks if the file exists using
* the `file_exists` function. It returns `true` if the file exists, and `false`
* otherwise.
*
* @param string $complete The file path to check.
*
* @return bool `true` if the file exists, `false` otherwise.
*/
private function testComplete(string $complete): bool {
if (file_exists($complete)) {
return true;
}
return false;
}
/**
* Generates a complete image or PDF for a demand.
*
* This method takes a demand object as a parameter and generates a complete image
* or PDF for the demand.
*
* If the `$close` parameter is set to `true`, it closes the browser and deletes the socket file.
* The method first checks if a socket file exists and connects to an existing browser if it does.
*
* If not, it creates a new browser instance using the `BrowserFactory` class.
*
* It then navigates to the URL specified in the demand and takes a screenshot of the page.
*
* If the demand type is 'pdf' or the `alwaysMakePdf` configuration option is set to `true`, it also
* generates a PDF of the page.
*
* Finally, it saves the screenshot and PDF to the appropriate file paths and
* updates the database using the `addUpdate` method.
*
* If an `OperationTimedOut` exception is thrown, it logs the error, closes the browser,
* deletes the socket file, copies an error image to the file path, and updates the
* database with an error status.
*
* @param object|null $demande The demand object containing the URL, HMAC, file path, and type of the demand.
* @param bool $close Whether to close the browser and delete the socket file after
* generating the complete image or PDF. Defaults to `false`.
*
* @return void
*/
private function makeComplete(object|null $demande, bool $close = false) {
if (file_exists($this->fileSocket)) {
$socket = \file_get_contents($this->fileSocket);
try {
$browser = BrowserFactory::connectToBrowser($socket);
} catch (BrowserConnectionFailed $e) {
$browser = $this->openBrowser();
}
} else {
$factory = new BrowserFactory($this->chromePath);
$browser = $factory->createBrowser([
'userAgent' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
'keepAlive' => true,
'headless' => true,
'windowSize' => [1920, 1080],
'userDataDir' => $this->tmpDir,
'customFlags' => [
'--disable-dev-shm-usage',
'--disable-gpu'
],
'noSandbox' => true,
//'connectionDelay' => 0.8, // add 0.8 second of delay between each instruction sent to chrome,
//'debugLogger' => 'php://stdout',
]);
\file_put_contents($this->fileSocket, $browser->getSocketUri(), LOCK_EX);
}
if ($close === true) {
$socket = \file_get_contents($this->fileSocket);
$browser = BrowserFactory::connectToBrowser($socket);
$browser->close();
unlink($this->fileSocket);
return true;
}
$dir = $this->thumbPath . substr($demande->hmac, 0, 4) . '/';
if (!is_dir($dir)) {
mkdir($dir);
}
try {
$page = $browser->createPage();
$page->navigate($demande->url)->waitForNavigation(Page::LOAD, 15000);
sleep(4);
$page->screenshot([
'captureBeyondViewport' => true,
'clip' => $page->getFullPageClip(),
'format' => $this->conf->fileFormat,
])->saveToFile($demande->complete);
if ($demande->type === 'pdf' || $this->conf->alwaysMakePdf === true) {
if ($this->conf->alwaysMakePdf === true) {
$pdfFile = str_replace($demande->type, 'pdf', $demande->filePath);
$pdfFile = str_replace($this->conf->fileFormat, 'pdf', $pdfFile);
} else {
$pdfFile = $demande->filePath;
}
$page->pdf([
'printBackground' => true,
'displayHeaderFooter' => true,
'paperWidth' => 8.268,
'paperHeight' => 11.693,
'scale' => 1
])->saveToFile($pdfFile);
if ($this->conf->alwaysMakePdf === true) {
$this->db->addUpdate(1, 'pdf');
}
}
$this->db->addUpdate(1, 'complete');
} catch (OperationTimedOut $e) {
// todo log
$socket = \file_get_contents($this->fileSocket);
$browser = BrowserFactory::connectToBrowser($socket);
$browser->close();
unlink($this->fileSocket);
copy(__DIR__ . '/../../src/images/error_thumb.png', $demande->filePath);
$this->db->addUpdate(2, $demande->type);
}
}
/**
* Creates and opens a new browser instance.
*
* This method creates a new browser instance using the `BrowserFactory` class with
* the specified configuration options. It then saves the socket URI to a file and
* returns the browser instance.
*
* @return Browser The created browser instance.
*/
private function openBrowser(): Browser {
$factory = new BrowserFactory($this->chromePath);
// Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
$browser = $factory->createBrowser([
'userAgent' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
'keepAlive' => true,
'headless' => true,
'windowSize' => [1920, 1080],
'userDataDir' => $this->tmpDir,
'customFlags' => [
'--disable-dev-shm-usage',
'--disable-gpu'
],
'noSandbox' => true,
]);
\file_put_contents($this->fileSocket, $browser->getSocketUri(), LOCK_EX);
return $browser;
}
}