Files
ggdeals/scripts/scheduler_loop.php
deepseek fff7a4b9b9 GG.deals clone: CodeIgniter 4 + MongoDB game price tracker
- Full gg.deals-style UI: deal feed with filters, game pages with price
  history charts, rankings, vouchers, news, prepaids, subscriptions,
  bundles, franchises, wishlist, API docs
- Real data crawlers:
  * gg:crawl (Steam Store API + GOG Catalog + Epic free games)
  * gg:catalog (252K game slugs from gg.deals sitemaps)
  * gg:ggdeals (per-game prices via FlareSolverr session, JSON-LD parsing)
  * gg:seed / gg:crawl-covers (demo seed + Steam CDN cover art)
- 24/7 self-updating scheduler (scripts/scheduler_loop.php, lockfile-protected)
- Tailwind v4 dark/light theme, Alpine.js, Chart.js
2026-08-23 07:06:02 +00:00

92 lines
2.8 KiB
PHP

#!/usr/bin/env php
<?php
declare(strict_types=1);
/**
* Crash-safe recurring crawler scheduler with a single-instance lockfile.
*
* Loop:
* 1. Acquire lockfile (fail fast if another instance is running)
* 2. Refresh Steam + GOG + Epic prices (gg:crawl)
* 3. Finalize aggregates
* 4. Expand catalog: crawl next batch of N games from gg.deals via FlareSolverr
* 5. Release lockfile, sleep interval
*
* Usage: php scripts/scheduler_loop.php [interval_minutes] [batch_size]
* Example: php scripts/scheduler_loop.php 60 200
*/
$intervalMin = isset($argv[1]) ? max(5, (int) $argv[1]) : 60;
$batchSize = isset($argv[2]) ? max(10, (int) $argv[2]) : 150;
$root = dirname(__DIR__);
$lockFile = $root . '/writable/logs/scheduler.lock';
$logFile = $root . '/writable/logs/scheduler.log';
function logMsg(string $file, string $msg): void
{
$line = '[' . date('Y-m-d H:i:s') . '] ' . $msg . PHP_EOL;
file_put_contents($file, $line, FILE_APPEND);
echo $line;
}
function acquireLock(string $lockFile): bool
{
if (file_exists($lockFile)) {
$pid = (int) trim((string) file_get_contents($lockFile));
if ($pid > 0 && posix_kill($pid, 0)) {
return false; // another instance alive
}
@unlink($lockFile); // stale lock
}
file_put_contents($lockFile, (string) getmypid());
return true;
}
function releaseLock(string $lockFile): void
{
@unlink($lockFile);
}
function run(string $root, string $cmd, string $logFile, string $tag): int
{
logMsg($logFile, ">>> [$tag] $cmd");
$output = [];
$code = 0;
exec('cd ' . escapeshellarg($root) . ' && ' . $cmd . ' 2>&1', $output, $code);
foreach (array_slice($output, -6) as $line) {
logMsg($logFile, " $line");
}
logMsg($logFile, "<<< [$tag] exit $code");
return $code;
}
logMsg($logFile, "Scheduler loop started — interval {$intervalMin}min, batch {$batchSize}");
while (true) {
if (! acquireLock($lockFile)) {
logMsg($logFile, 'Another crawler instance is running — exiting this loop.');
exit(1);
}
try {
// 1. refresh prices from direct sources
run($root, 'php spark gg:crawl steam', $logFile, 'steam');
run($root, 'php spark gg:crawl gog', $logFile, 'gog');
run($root, 'php spark gg:crawl epic', $logFile, 'epic');
run($root, 'php spark gg:crawl finalize', $logFile, 'finalize');
// 2. expand catalog via FlareSolverr session
run($root, "php spark gg:ggdeals --batch={$batchSize}", $logFile, 'ggdeals-expand');
} catch (\Throwable $e) {
logMsg($logFile, 'ERROR in loop: ' . $e->getMessage());
} finally {
releaseLock($lockFile);
}
logMsg($logFile, "Sleeping {$intervalMin}min until next cycle…");
sleep($intervalMin * 60);
}