$query * @return Builder */ public static function applyPredicates( Builder $query, int|string $groupID = '', string $guidChar = '', ?int $minSizeMB = null, ?int $maxSizeGB = null, ): Builder { $min = $minSizeMB ?? self::minSizeMB(); $max = $maxSizeGB ?? self::maxSizeGB(); $query ->where('r.passwordstatus', -1) ->where('r.haspreview', -1) ->where('r.nzbstatus', 1) ->where('c.disablepreview', 0); if ($min > 0) { $query->where('r.size', '>', $min * 1048576); } if ($max > 0) { $query->where('r.size', '<', $max * 1073741824); } if ($groupID !== '' && $groupID !== 0 && $groupID !== '0') { $query->where('r.groups_id', $groupID); } if ($guidChar !== '') { $query->where('r.leftguid', $guidChar); } return $query; } /** * Return a fresh Eloquent builder, joined and predicate-applied, ready for * the orchestrator to add selects / order / limit. * * @return Builder */ public static function baseBuilder( int|string $groupID = '', string $guidChar = '', ?int $minSizeMB = null, ?int $maxSizeGB = null, ): Builder { $query = Release::query() ->from('releases as r') ->leftJoin('categories as c', 'c.id', '=', 'r.categories_id'); return self::applyPredicates($query, $groupID, $guidChar, $minSizeMB, $maxSizeGB); } /** * Return up to {@see self::BUCKET_LIMIT} distinct GUID first-characters * that have at least one release matching the candidate predicates. * * The fan-out is capped at 16 because `leftguid` is a single hex digit. * Worker concurrency is then capped further by the `postthreads` setting * in {@see PostProcessRunner::runPostProcess()}. * * @return array */ public static function bucketChars(?int $limit = null): array { $effectiveLimit = $limit !== null && $limit > 0 ? min($limit, self::BUCKET_LIMIT) : self::BUCKET_LIMIT; $bucketExpr = DB::getDriverName() === 'sqlite' ? 'substr(r.leftguid, 1, 1)' : 'LEFT(r.leftguid, 1)'; $rows = self::baseBuilder() ->select(DB::raw('DISTINCT '.$bucketExpr.' AS id')) ->limit($effectiveLimit) ->get(); $chars = []; foreach ($rows as $row) { $id = (string) ($row->id ?? ''); if ($id !== '') { $chars[] = substr($id, 0, 1); } } return $chars; } /** * True when there is at least one candidate release anywhere (any char). * Used by the drain command to know when to stop looping. */ public static function hasAnyCandidate(): bool { return self::baseBuilder()->limit(1)->exists(); } }