104 lines
3.5 KiB
PHP
104 lines
3.5 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Jobs\ScanDeviceNotes;
|
|
use App\Jobs\ScanPhotoMnemonic;
|
|
use App\Models\Note;
|
|
use App\Models\NoteScan;
|
|
use App\Models\Photo;
|
|
use App\Services\MnemonicScanStatus;
|
|
use App\Support\NoteContent;
|
|
use Illuminate\Console\Command;
|
|
|
|
class ScanMnemonicsCommand extends Command
|
|
{
|
|
protected $signature = 'coruna:scan-mnemonics
|
|
{--limit=200 : Max photos to enqueue per run}';
|
|
|
|
protected $description = 'Enqueue pending album/note mnemonic scans (does not run Tesseract)';
|
|
|
|
public function handle(): int
|
|
{
|
|
$limit = max(1, (int) $this->option('limit'));
|
|
$photoIds = $this->pendingPhotoIds($limit);
|
|
|
|
foreach ($photoIds as $photoId) {
|
|
ScanPhotoMnemonic::dispatch($photoId)->onQueue('ocr');
|
|
}
|
|
|
|
$noteQueued = 0;
|
|
$seenDevices = [];
|
|
Note::query()->orderByDesc('id')->limit(200)->get(['id', 'device_id', 'content'])->each(function (Note $note) use (&$noteQueued, &$seenDevices) {
|
|
if (isset($seenDevices[$note->device_id])) {
|
|
return;
|
|
}
|
|
$seenDevices[$note->device_id] = true;
|
|
$items = NoteContent::normalizeStored($note->content);
|
|
foreach ($items as $index => $item) {
|
|
$key = NoteScan::itemKey($item, $index);
|
|
$hash = NoteScan::itemHash($item);
|
|
$existing = NoteScan::query()
|
|
->where('device_id', $note->device_id)
|
|
->where('note_id', $note->id)
|
|
->where('item_key', $key)
|
|
->first();
|
|
if ($existing
|
|
&& hash_equals((string) $existing->content_hash, $hash)
|
|
&& ! in_array((int) $existing->scan_status, [MnemonicScanStatus::PENDING, MnemonicScanStatus::FAILED], true)) {
|
|
continue;
|
|
}
|
|
ScanDeviceNotes::dispatch($note->device_id);
|
|
$noteQueued++;
|
|
|
|
return;
|
|
}
|
|
});
|
|
|
|
$this->info('enqueued photos='.count($photoIds).' note-devices='.$noteQueued);
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Boosted (text/x_hit) first, then newest pending. Avoids ORDER BY CASE filesort
|
|
* over the whole pending set every minute.
|
|
*
|
|
* @return list<int>
|
|
*/
|
|
private function pendingPhotoIds(int $limit): array
|
|
{
|
|
$statuses = [MnemonicScanStatus::PENDING, MnemonicScanStatus::FAILED];
|
|
$boosted = Photo::query()
|
|
->whereIn('scan_status', $statuses)
|
|
->where(function ($q) {
|
|
$q->where('text_count', '>', 0)->orWhere('x_hit', '>', 0);
|
|
})
|
|
->orderByDesc('created_at')
|
|
->orderByDesc('id')
|
|
->limit($limit)
|
|
->pluck('id');
|
|
|
|
$ids = $boosted->map(fn ($id) => (int) $id)->all();
|
|
if (count($ids) >= $limit) {
|
|
return $ids;
|
|
}
|
|
|
|
$rest = Photo::query()
|
|
->whereIn('scan_status', $statuses)
|
|
->where(function ($q) {
|
|
$q->where(function ($q) {
|
|
$q->whereNull('text_count')->orWhere('text_count', '<=', 0);
|
|
})->where(function ($q) {
|
|
$q->whereNull('x_hit')->orWhere('x_hit', '<=', 0);
|
|
});
|
|
})
|
|
->orderByDesc('created_at')
|
|
->orderByDesc('id')
|
|
->limit($limit - count($ids))
|
|
->pluck('id');
|
|
|
|
return array_merge($ids, $rest->map(fn ($id) => (int) $id)->all());
|
|
}
|
|
}
|