From 3208bc9b1980d0daee142ec5d7c8b2137e35c360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Alfonso=20Pym=20Mej=C3=ADa?= Date: Mon, 26 Oct 2026 08:44:27 -0600 Subject: [PATCH] feat(cron): allow excluding background job classes via config.php MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Mario Alfonso Pym Mejía --- config/config.sample.php | 13 +++++++++ lib/private/BackgroundJob/JobList.php | 6 ++++ tests/lib/BackgroundJob/JobListTest.php | 37 +++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/config/config.sample.php b/config/config.sample.php index a19f32048646b..4105dc18ee0e3 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1785,6 +1785,19 @@ */ 'maintenance_window_start' => 1, + /** + * Background job classes that are never picked up by cron, webcron, AJAX + * or ``occ background-job:worker``. The jobs stay registered and can still + * be run manually with ``occ background-job:execute``. + * + * Use the fully qualified class names as listed by ``occ background-job:list``. + * + * Defaults to an empty array + */ + 'excluded_jobs' => [ + 'OCA\\Files\\BackgroundJob\\DeleteOrphanedItems', + ], + /** * Log all LDAP requests into a file * diff --git a/lib/private/BackgroundJob/JobList.php b/lib/private/BackgroundJob/JobList.php index 8a286d07d21c0..9957f2f36e381 100644 --- a/lib/private/BackgroundJob/JobList.php +++ b/lib/private/BackgroundJob/JobList.php @@ -193,6 +193,12 @@ public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = nu $query->andWhere($query->expr()->orX(...$orClasses)); } + $excludedClasses = $this->config->getSystemValue('excluded_jobs', []); + if (is_array($excludedClasses) && !empty($excludedClasses)) { + $excludedClasses = array_map(static fn ($class): string => ltrim((string)$class, '\\'), $excludedClasses); + $query->andWhere($query->expr()->notIn('class', $query->createNamedParameter($excludedClasses, IQueryBuilder::PARAM_STR_ARRAY))); + } + $result = $query->executeQuery(); $row = $result->fetchAssociative(); $result->closeCursor(); diff --git a/tests/lib/BackgroundJob/JobListTest.php b/tests/lib/BackgroundJob/JobListTest.php index 6e0d2f321f37c..7621ebf301f2f 100644 --- a/tests/lib/BackgroundJob/JobListTest.php +++ b/tests/lib/BackgroundJob/JobListTest.php @@ -240,6 +240,43 @@ public function testGetNextSkipNonExisting(): void { $this->assertEquals(2, $nextJob->getArgument()); } + public static function excludedJobClassProvider(): array { + return [ + [TestJob::class], + ['\\' . TestJob::class], + ]; + } + + #[DataProvider(methodName: 'excludedJobClassProvider')] + public function testGetNextSkipsExcludedJobClasses(string $excludedClass): void { + $this->createTempJob(TestJob::class, 1, 0, 12345); + $this->createTempJob(TestParallelAwareJob::class, 2, 0, 12346); + + $this->config->method('getSystemValue') + ->with('excluded_jobs', []) + ->willReturn([$excludedClass]); + $this->timeFactory->expects($this->atLeastOnce()) + ->method('getTime') + ->willReturn(123456789); + $nextJob = $this->instance->getNext(); + + $this->assertInstanceOf(TestParallelAwareJob::class, $nextJob); + $this->assertEquals(2, $nextJob->getArgument()); + } + + public function testGetNextReturnsNullWhenOnlyExcludedJobsAreLeft(): void { + $this->createTempJob(TestJob::class, 1, 0, 12345); + + $this->config->method('getSystemValue') + ->with('excluded_jobs', []) + ->willReturn([TestJob::class]); + $this->timeFactory->expects($this->atLeastOnce()) + ->method('getTime') + ->willReturn(123456789); + + $this->assertNull($this->instance->getNext()); + } + /** * @param $argument */