Your IP : 216.73.217.78


Current Path : /home/seto/indexator.pm/
Upload File :
Current File : /home/seto/indexator.pm/worker.php

#!/usr/bin/env php
<?php

declare(strict_types=1);

if (php_sapi_name() !== 'cli') {
    exit('Run this script from the command line only.' . PHP_EOL);
}

require __DIR__ . '/bootstrap.php';

use App\Core\Database;
use App\Projects\ProjectRepository;
use App\Queue\JobQueue;
use App\Search\CheckService;
use App\Search\DataForSeoProvider;
use App\Search\XmlRiverProvider;

// ── Worker lock: prevent duplicate workers ───────────────────────────────────
$lockFile = __DIR__ . '/worker.lock';
$lockFp   = fopen($lockFile, 'c');

if (!$lockFp || !flock($lockFp, LOCK_EX | LOCK_NB)) {
    echo '[' . date('Y-m-d H:i:s') . '] Another worker is running. Exiting.' . PHP_EOL;
    if ($lockFp) fclose($lockFp);
    exit(0);
}

// ── Setup ────────────────────────────────────────────────────────────────────
$config   = $GLOBALS['config'];
$db       = Database::getInstance($config['DB_PATH']);
$queue    = new JobQueue($db);
$projects = new ProjectRepository($db);

$providerName = $config['SEARCH_PROVIDER'] ?? 'dataforseo';
$provider = match ($providerName) {
    'xmlriver' => new XmlRiverProvider(
        $config['XMLRIVER_USER'] ?? '',
        $config['XMLRIVER_KEY']  ?? '',
    ),
    default => new DataForSeoProvider(
        $config['DATAFORSEO_LOGIN']    ?? '',
        $config['DATAFORSEO_PASSWORD'] ?? '',
    ),
};

$checker     = new CheckService($db, $queue, $projects, $provider);
$batchSize   = (int) ($config['BATCH_SIZE'] ?? 10);
$concurrency = (int) ($config['CONCURRENT_REQUESTS'] ?? $batchSize);
$total       = 0;

// ── Reset stuck in_progress jobs (older than 60s) ───────────────────────────
$resetCount = $db->execute(
    "UPDATE jobs SET status = 'pending', started_at = NULL
     WHERE status = 'in_progress' AND started_at < datetime('now', '-60 seconds')"
);
if ($resetCount > 0) {
    echo '[' . date('Y-m-d H:i:s') . '] Reset ' . $resetCount . ' stuck job(s).' . PHP_EOL;
}

// ── Fix projects that are 100% done but still checking/active ────────────────
$doneAtStart = $db->fetchAll(
    "SELECT id FROM projects
     WHERE status IN ('checking', 'active')
       AND id NOT IN (SELECT DISTINCT project_id FROM project_urls WHERE status IN ('new', 'pending'))
       AND id IN (SELECT DISTINCT project_id FROM project_urls)",
    []
);
echo '[' . date('Y-m-d H:i:s') . '] Startup sweep: ' . count($doneAtStart) . ' project(s) done.' . PHP_EOL;
foreach ($doneAtStart as $row) {
    echo '  Saving check history for project #' . $row['id'] . PHP_EOL;
    $projects->saveCheckHistory((int)$row['id'], $providerName);
}
$db->execute(
    "UPDATE projects SET status = 'done', updated_at = datetime('now')
     WHERE status IN ('checking', 'active')
       AND id NOT IN (
           SELECT DISTINCT project_id FROM project_urls WHERE status IN ('new', 'pending')
       )
       AND id IN (
           SELECT DISTINCT project_id FROM project_urls
       )"
);

echo '[' . date('Y-m-d H:i:s') . '] Worker started (concurrency=' . $concurrency . ').' . PHP_EOL;

// ── Main loop ────────────────────────────────────────────────────────────────
$processedProjectIds = [];
while (true) {
    $jobs = $queue->fetchBatch($batchSize);

    if (empty($jobs)) {
        break;
    }

    foreach ($jobs as $job) {
        $processedProjectIds[(int)$job['project_id']] = true;
    }

    $urls = array_map(fn($j) => $j['url'], $jobs);
    echo '  Batch of ' . count($jobs) . ': ' . implode(', ', array_slice($urls, 0, 3))
        . (count($jobs) > 3 ? ' ...' : '') . PHP_EOL;

    $checker->processBatch($jobs, $concurrency);
    $total += count($jobs);
}

// Save check history for every project this worker actually processed
foreach (array_keys($processedProjectIds) as $pid) {
    echo '  Saving check history for project #' . $pid . PHP_EOL;
    try {
        $projects->saveCheckHistory($pid, $providerName);
    } catch (\Throwable $e) {
        echo '  ERROR saving history for #' . $pid . ': ' . $e->getMessage() . PHP_EOL;
    }
}

// ── Final: save check history, then mark projects done ───────────────────────
$justFinished = $db->fetchAll(
    "SELECT id FROM projects
     WHERE status IN ('checking', 'active')
       AND id NOT IN (
           SELECT DISTINCT project_id FROM project_urls WHERE status IN ('new', 'pending')
       )
       AND id IN (SELECT DISTINCT project_id FROM project_urls)",
    []
);
echo '[' . date('Y-m-d H:i:s') . '] Final sweep: ' . count($justFinished) . ' project(s) done.' . PHP_EOL;
foreach ($justFinished as $row) {
    echo '  Saving check history for project #' . $row['id'] . PHP_EOL;
    try {
        $projects->saveCheckHistory((int)$row['id'], $providerName);
    } catch (\Throwable $e) {
        echo '  ERROR: ' . $e->getMessage() . PHP_EOL;
    }
}

$db->execute(
    "UPDATE projects SET status = 'done', updated_at = datetime('now')
     WHERE status IN ('checking', 'active')
       AND id NOT IN (
           SELECT DISTINCT project_id FROM project_urls WHERE status IN ('new', 'pending')
       )
       AND id IN (
           SELECT DISTINCT project_id FROM project_urls
       )"
);

echo '[' . date('Y-m-d H:i:s') . '] Done. Processed ' . $total . ' job(s).' . PHP_EOL;

// ── Release lock ─────────────────────────────────────────────────────────────
flock($lockFp, LOCK_UN);
fclose($lockFp);