HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/app/Model/JobMonitor.php
<?php

namespace App\Model;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Bus\Batch;
use Illuminate\Support\Facades\Bus;
use App\Library\Traits\HasUid;
use App\Library\Lockable;
use Exception;
use Illuminate\Support\Facades\Log;

class JobMonitor extends Model
{
    use HasFactory;
    use HasUid;

    public const STATUS_QUEUED    = 'queued';
    public const STATUS_RUNNING   = 'running';
    public const STATUS_DONE      = 'done';
    public const STATUS_FAILED    = 'failed';
    public const STATUS_CANCELLED = 'cancelled';

    public static function makeInstance($subject, $jobType)
    {
        $monitor = new self();
        $monitor->status = self::STATUS_QUEUED;
        $monitor->subject_name = get_class($subject);
        $monitor->subject_id = $subject->id;

        // A job TYPE is helpful when we need to retrieve a list of jobs of a given type.
        // For example, get all verification jobs of a list
        $monitor->job_type = $jobType;

        // Return
        return $monitor;
    }

    public function scopeByJobType($query, $jobType)
    {
        return $query->where('job_type', $jobType);
    }

    public function getBatch()
    {
        if (is_null($this->batch_id)) {
            return;
        }

        return Bus::findBatch($this->batch_id);
    }

    public function job()
    {
        return $this->belongsTo(Job::class);
    }

    public function getJob()
    {
        return $this->job;
    }

    public function hasJob()
    {
        return ! is_null($this->job_id);
    }

    public function hasBatch()
    {
        return ! is_null($this->batch_id);
    }

    public function setFailed($exception)
    {
        $this->status = self::STATUS_FAILED;
        $errorMsg = "Error executing job. ".$exception->getMessage();
        $this->error = $errorMsg;
        $this->save();

        $trace = method_exists($exception, 'getTraceAsString') ? $exception->getTraceAsString() : '';
        $traceLines = empty($trace) ? [] : preg_split("/(\r\n|\n|\r)/", $trace);
        $traceTop = implode("\n", array_slice($traceLines, 0, 20));

        Log::error(sprintf(
            "LOGGED BY JOB MONITOR. %s\nException: %s\nTop 20 trace lines:\n%s",
            $errorMsg,
            is_object($exception) ? get_class($exception) : gettype($exception),
            $traceTop
        ));
    }

    public function setRunning()
    {
        $this->status = self::STATUS_RUNNING;
        $this->save();
    }

    public function setDone()
    {
        $this->status = self::STATUS_DONE;
        $this->save();
    }

    public function setQueued()
    {
        $this->status = self::STATUS_QUEUED;
        $this->save();
    }

    public function scopeActive($query)
    {
        return $query->whereIn('status', [self::STATUS_RUNNING, self::STATUS_QUEUED]);
    }

    public function getJsonData()
    {
        // Also convert null or empty string to an empty array ([])
        return json_decode($this->data, true) ?: [];
    }

    public function setJsonData($data)
    {
        $this->data = json_encode($data);
        $this->save();
    }

    public function updateJsonData($data)
    {
        $json = $this->getJsonData();
        $json = array_merge($json, $data);
        $this->setJsonData($json);
    }

    public function cancelWithoutDeleteBatch()
    {
        $this->cancelBatch();
    }

    /**
     * Soft cancel — stops the queue job/batch but keeps the monitor row so
     * audit history is preserved. API DELETE uses this variant. UI wizard's
     * cancel button still uses the legacy cancel() (hard delete) so existing
     * UX doesn't change.
     */
    public function cancelAndRetain(): void
    {
        try {
            $this->cancelJob();
            $this->cancelBatch();
        } catch (\Exception $ex) {
            // Same swallow as legacy cancel() — serializable closure quirks.
        }
        $this->status = self::STATUS_CANCELLED;
        $this->save();
    }

    public function cancel()
    {
        try {
            $this->cancelJob(); // if any
            $this->cancelBatch(); // if any
        } catch (\Exception $ex) {
            // In case of any issue like:
            // "Your serialized closure might have been modified or it's unsafe to be unserialized"
            // occuring when executing Bus::findBatch($id)
            // Laravel\\SerializableClosure\\SerializableClosure->__unserialize()
        } finally {
            // For now, do not store cancelled job monitor
            // So, just delete the record
            $this->delete();
        }

    }

    private function cancelJob()
    {
        // Get the job record in the `jobs` database table
        $job = $this->getJob();

        // Remove it from queue, if any
        if (!is_null($job)) {
            $job->delete();
        }
    }

    private function cancelBatch()
    {
        // Then get the batch
        // This is not the batch record in job_batches model
        // So we can just cancel it to have its remaining jobs perish!
        // It will be pruned with queue:prune-batches command
        $batch = $this->getBatch();
        if (!is_null($batch)) {
            $batch->cancel();
        }
    }

    public function isDone()
    {
        return $this->status == self::STATUS_DONE;
    }

    public function isFailed()
    {
        return $this->status == self::STATUS_FAILED;
    }
}