From 3cf27aadb19df68f21ba884cd6b81522755e771c Mon Sep 17 00:00:00 2001 From: Snider <631881+Snider@users.noreply.github.com> Date: Mon, 2 Feb 2026 01:51:48 +0000 Subject: [PATCH 1/4] Optimize LazyThumbnail::purgeStale using listContents Replaces the inefficient N+1 file iteration in purgeStale with listContents, which fetches metadata in a single call on supported drivers (Flysystem 3.x). This significantly reduces API calls on remote storage like S3. Includes a safety check for null timestamps to prevent accidental deletion of files with missing metadata, and a robust fallback to the original method if listContents fails or is not supported. --- src/Core/Media/Thumbnail/LazyThumbnail.php | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/Core/Media/Thumbnail/LazyThumbnail.php b/src/Core/Media/Thumbnail/LazyThumbnail.php index 0c42e53..3eeaefc 100644 --- a/src/Core/Media/Thumbnail/LazyThumbnail.php +++ b/src/Core/Media/Thumbnail/LazyThumbnail.php @@ -373,6 +373,36 @@ public function purgeStale(int $maxAgeDays = 30): int $cutoff = now()->subDays($maxAgeDays)->timestamp; $deleted = 0; + try { + // Optimization: Use listContents to get metadata in one go + // This avoids N+1 calls to lastModified() which is slow on remote storage + $contents = $disk->listContents($this->thumbnailPrefix, true); + + foreach ($contents as $attributes) { + if ($attributes instanceof \League\Flysystem\FileAttributes) { + try { + $timestamp = $attributes->lastModified(); + + if ($timestamp !== null && $timestamp < $cutoff) { + if ($disk->delete($attributes->path())) { + $deleted++; + } + } + } catch (\Throwable $e) { + // Skip files where metadata cannot be read + continue; + } + } + } + + return $deleted; + } catch (\Throwable $e) { + Log::warning('LazyThumbnail: listContents optimization failed, falling back to slow method', [ + 'error' => $e->getMessage(), + ]); + } + + // Fallback for disks that don't support listContents or other errors $files = $disk->allFiles($this->thumbnailPrefix); foreach ($files as $file) { From 468ca66be3ff7443b67c9902cca6653754c568a2 Mon Sep 17 00:00:00 2001 From: Snider <631881+Snider@users.noreply.github.com> Date: Mon, 2 Feb 2026 01:55:08 +0000 Subject: [PATCH 2/4] Fix CI failure due to stderr pollution in SARIF/JSON outputs - Removed `2>&1` from `psalm`, `phpstan`, `audit`, and `pint` commands in `qa.yml` to prevent stderr (progress/debug info) from being written to JSON/SARIF output files. - Upgraded `github/codeql-action/upload-sarif` to v4 to resolve deprecation warning. --- .github/workflows/qa.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 0d3ff49..6bb59dd 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -130,7 +130,7 @@ jobs: id: stan run: | set +e - vendor/bin/phpstan analyse --error-format=json --no-progress > phpstan.json 2>&1 + vendor/bin/phpstan analyse --error-format=json --no-progress > phpstan.json EXIT_CODE=$? # Also run for GitHub format @@ -181,11 +181,11 @@ jobs: id: psalm run: | set +e - vendor/bin/psalm --output-format=json --show-info=false > psalm.json 2>&1 + vendor/bin/psalm --output-format=json --show-info=false > psalm.json EXIT_CODE=$? # Generate SARIF for GitHub Security - vendor/bin/psalm --output-format=sarif --show-info=false > psalm.sarif 2>&1 || true + vendor/bin/psalm --output-format=sarif --show-info=false > psalm.sarif || true ERRORS=$(jq 'length' psalm.json 2>/dev/null || echo "0") echo "errors=${ERRORS}" >> $GITHUB_OUTPUT @@ -200,7 +200,7 @@ jobs: - name: Upload Psalm SARIF if: always() - uses: github/codeql-action/upload-sarif@v3 + uses: github/codeql-action/upload-sarif@v4 with: sarif_file: psalm.sarif category: psalm @@ -242,7 +242,7 @@ jobs: id: fmt run: | set +e - OUTPUT=$(vendor/bin/pint --test --format=json 2>&1) + OUTPUT=$(vendor/bin/pint --test --format=json) EXIT_CODE=$? echo "$OUTPUT" > pint.json @@ -292,7 +292,7 @@ jobs: id: audit run: | set +e - composer audit --format=json > audit.json 2>&1 + composer audit --format=json > audit.json EXIT_CODE=$? VULNS=$(jq '.advisories | to_entries | map(.value | length) | add // 0' audit.json 2>/dev/null || echo "0") From b5ca1f149b07e1eea72b9de60cf3fc5e328b4a00 Mon Sep 17 00:00:00 2001 From: Snider <631881+Snider@users.noreply.github.com> Date: Mon, 2 Feb 2026 02:01:26 +0000 Subject: [PATCH 3/4] Fix CI failure due to invalid SARIF and stderr pollution - Sanitized Psalm SARIF output using `jq` to ensure all location coordinates are >= 1, fixing validation errors with CodeQL Action. - Removed `2>&1` from `psalm`, `phpstan`, `audit`, and `pint` commands in `qa.yml` to prevent stderr (progress/debug info) from corrupting structured output files. - Upgraded `github/codeql-action/upload-sarif` to v4 to resolve deprecation warning. --- .github/workflows/qa.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 6bb59dd..96bd6a1 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -187,6 +187,9 @@ jobs: # Generate SARIF for GitHub Security vendor/bin/psalm --output-format=sarif --show-info=false > psalm.sarif || true + # Sanitize SARIF: Ensure all location coordinates are at least 1 (Psalm can output 0) + jq '(.runs[].results[].locations[].physicalLocation.region | select(.startLine < 1).startLine) |= 1 | (.runs[].results[].locations[].physicalLocation.region | select(.startColumn < 1).startColumn) |= 1 | (.runs[].results[].locations[].physicalLocation.region | select(.endLine < 1).endLine) |= 1 | (.runs[].results[].locations[].physicalLocation.region | select(.endColumn < 1).endColumn) |= 1' psalm.sarif > psalm_clean.sarif && mv psalm_clean.sarif psalm.sarif + ERRORS=$(jq 'length' psalm.json 2>/dev/null || echo "0") echo "errors=${ERRORS}" >> $GITHUB_OUTPUT From 7f7dde2c656708a04e83ab3a00dbee169993700c Mon Sep 17 00:00:00 2001 From: Snider <631881+Snider@users.noreply.github.com> Date: Mon, 2 Feb 2026 02:08:03 +0000 Subject: [PATCH 4/4] Fix CI failure due to invalid SARIF and restore strict exit codes - Reverted temporary exit code suppression in `qa.yml` (restoring `exit $EXIT_CODE`). - Added type hint to `LazyThumbnail.php` to fix potential static analysis error. - Retained `jq` sanitization for SARIF output. - Retained removal of stderr pollution. --- src/Core/Media/Thumbnail/LazyThumbnail.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Media/Thumbnail/LazyThumbnail.php b/src/Core/Media/Thumbnail/LazyThumbnail.php index 3eeaefc..3a5c8e6 100644 --- a/src/Core/Media/Thumbnail/LazyThumbnail.php +++ b/src/Core/Media/Thumbnail/LazyThumbnail.php @@ -369,6 +369,7 @@ public function deleteAll(string $sourcePath): int */ public function purgeStale(int $maxAgeDays = 30): int { + /** @var \Illuminate\Filesystem\FilesystemAdapter $disk */ $disk = $this->getThumbnailDisk(); $cutoff = now()->subDays($maxAgeDays)->timestamp; $deleted = 0;