2022-09-01 06:13:05 -04:00
< ? php
declare ( strict_types = 1 );
/**
2024-05-24 13:43:47 -04:00
* SPDX - FileCopyrightText : 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX - License - Identifier : AGPL - 3.0 - or - later
2022-09-01 06:13:05 -04:00
*/
namespace OC\Core\Command\Background ;
use OC\Core\Command\InterruptedException ;
2024-04-09 05:12:48 -04:00
use OCP\BackgroundJob\IJobList ;
2026-01-26 16:09:40 -05:00
use OCP\Files\ISetupManager ;
2023-12-20 08:29:44 -05:00
use OCP\ITempManager ;
2024-04-09 05:12:48 -04:00
use Psr\Log\LoggerInterface ;
2022-09-01 06:13:05 -04:00
use Symfony\Component\Console\Input\InputArgument ;
use Symfony\Component\Console\Input\InputInterface ;
use Symfony\Component\Console\Input\InputOption ;
use Symfony\Component\Console\Output\OutputInterface ;
class JobWorker extends JobBase {
2024-04-09 05:12:48 -04:00
public function __construct (
protected IJobList $jobList ,
protected LoggerInterface $logger ,
private ITempManager $tempManager ,
2026-01-26 16:09:40 -05:00
private ISetupManager $setupManager ,
2024-04-09 05:12:48 -04:00
) {
parent :: __construct ( $jobList , $logger );
}
2022-09-01 06:13:05 -04:00
protected function configure () : void {
parent :: configure ();
$this
-> setName ( 'background-job:worker' )
-> setDescription ( 'Run a background job worker' )
-> addArgument (
2024-04-08 11:25:51 -04:00
'job-classes' ,
2024-04-11 04:55:39 -04:00
InputArgument :: OPTIONAL | InputArgument :: IS_ARRAY ,
'The classes of the jobs to look for in the database'
2022-09-01 06:13:05 -04:00
)
-> addOption (
'once' ,
null ,
InputOption :: VALUE_NONE ,
'Only execute the worker once (as a regular cron execution would do it)'
)
-> addOption (
'interval' ,
'i' ,
InputOption :: VALUE_OPTIONAL ,
'Interval in seconds in which the worker should repeat already processed jobs (set to 0 for no repeat)' ,
2025-06-30 05:35:14 -04:00
1
2022-09-01 06:13:05 -04:00
)
2024-08-16 07:23:10 -04:00
-> addOption (
'stop_after' ,
't' ,
InputOption :: VALUE_OPTIONAL ,
'Duration after which the worker should stop and exit. The worker won\'t kill a potential running job, it will exit after this job has finished running (supported values are: "30" or "30s" for 30 seconds, "10m" for 10 minutes and "2h" for 2 hours)'
)
2022-09-01 06:13:05 -04:00
;
}
protected function execute ( InputInterface $input , OutputInterface $output ) : int {
2024-08-16 07:23:10 -04:00
$startTime = time ();
$stopAfterOptionValue = $input -> getOption ( 'stop_after' );
$stopAfterSeconds = $stopAfterOptionValue === null
? null
: $this -> parseStopAfter ( $stopAfterOptionValue );
if ( $stopAfterSeconds !== null ) {
$output -> writeln ( '<info>Background job worker will stop after ' . $stopAfterSeconds . ' seconds</info>' );
}
2024-04-11 04:55:39 -04:00
$jobClasses = $input -> getArgument ( 'job-classes' );
$jobClasses = empty ( $jobClasses ) ? null : $jobClasses ;
2024-04-08 11:25:51 -04:00
if ( $jobClasses !== null ) {
2024-04-11 04:55:39 -04:00
// at least one class is invalid
2024-04-08 11:25:51 -04:00
foreach ( $jobClasses as $jobClass ) {
if ( ! class_exists ( $jobClass )) {
$output -> writeln ( '<error>Invalid job class: ' . $jobClass . '</error>' );
return 1 ;
}
}
2022-09-01 06:13:05 -04:00
}
while ( true ) {
2024-08-16 07:23:10 -04:00
// Stop if we exceeded stop_after value
if ( $stopAfterSeconds !== null && ( $startTime + $stopAfterSeconds ) < time ()) {
$output -> writeln ( 'stop_after time has been exceeded, exiting...' , OutputInterface :: VERBOSITY_VERBOSE );
break ;
}
2022-09-01 06:13:05 -04:00
// Handle canceling of the process
try {
$this -> abortIfInterrupted ();
} catch ( InterruptedException $e ) {
$output -> writeln ( '<info>Background job worker stopped</info>' );
break ;
}
$this -> printSummary ( $input , $output );
usleep ( 50000 );
2024-04-08 11:25:51 -04:00
$job = $this -> jobList -> getNext ( false , $jobClasses );
2022-09-01 06:13:05 -04:00
if ( ! $job ) {
2023-12-20 08:16:16 -05:00
if ( $input -> getOption ( 'once' ) === true ) {
2024-04-11 04:55:39 -04:00
if ( $jobClasses === null ) {
2024-04-09 05:12:48 -04:00
$output -> writeln ( 'No job is currently queued' , OutputInterface :: VERBOSITY_VERBOSE );
} else {
2024-04-11 04:55:39 -04:00
$output -> writeln ( 'No job of classes [' . implode ( ', ' , $jobClasses ) . '] is currently queued' , OutputInterface :: VERBOSITY_VERBOSE );
2024-04-09 05:12:48 -04:00
}
2024-04-08 07:21:32 -04:00
$output -> writeln ( 'Exiting...' , OutputInterface :: VERBOSITY_VERBOSE );
2022-09-01 06:13:05 -04:00
break ;
}
2024-04-08 07:21:32 -04:00
$output -> writeln ( 'Waiting for new jobs to be queued' , OutputInterface :: VERBOSITY_VERBOSE );
2025-06-30 05:35:14 -04:00
if (( int ) $input -> getOption ( 'interval' ) === 0 ) {
break ;
}
2022-09-01 06:13:05 -04:00
// Re-check interval for new jobs
2025-06-30 05:35:14 -04:00
sleep (( int ) $input -> getOption ( 'interval' ));
2022-09-01 06:13:05 -04:00
continue ;
}
2024-04-08 07:21:32 -04:00
$output -> writeln ( 'Running job ' . get_class ( $job ) . ' with ID ' . $job -> getId ());
2022-09-01 06:13:05 -04:00
if ( $output -> isVerbose ()) {
$this -> printJobInfo ( $job -> getId (), $job , $output );
}
2024-09-18 18:37:55 -04:00
$job -> start ( $this -> jobList );
2024-04-08 07:21:32 -04:00
$output -> writeln ( 'Job ' . $job -> getId () . ' has finished' , OutputInterface :: VERBOSITY_VERBOSE );
2022-09-01 06:13:05 -04:00
// clean up after unclean jobs
2024-04-09 05:12:48 -04:00
$this -> setupManager -> tearDown ();
$this -> tempManager -> clean ();
2022-09-01 06:13:05 -04:00
$this -> jobList -> setLastJob ( $job );
$this -> jobList -> unlockJob ( $job );
if ( $input -> getOption ( 'once' ) === true ) {
break ;
}
}
return 0 ;
}
private function printSummary ( InputInterface $input , OutputInterface $output ) : void {
if ( ! $output -> isVeryVerbose ()) {
return ;
}
2024-04-08 07:21:32 -04:00
$output -> writeln ( '<comment>Summary</comment>' );
2022-09-01 06:13:05 -04:00
$counts = [];
foreach ( $this -> jobList -> countByClass () as $row ) {
$counts [] = $row ;
}
$this -> writeTableInOutputFormat ( $input , $output , $counts );
}
2024-08-16 07:23:10 -04:00
private function parseStopAfter ( string $value ) : ? int {
if ( is_numeric ( $value )) {
2024-08-23 09:10:27 -04:00
return ( int ) $value ;
2024-08-16 07:23:10 -04:00
}
if ( preg_match ( " /^( \ d+)s $ /i " , $value , $matches )) {
2024-08-23 09:10:27 -04:00
return ( int ) $matches [ 0 ];
2024-08-16 07:23:10 -04:00
}
if ( preg_match ( " /^( \ d+)m $ /i " , $value , $matches )) {
2024-08-23 09:10:27 -04:00
return 60 * (( int ) $matches [ 0 ]);
2024-08-16 07:23:10 -04:00
}
if ( preg_match ( " /^( \ d+)h $ /i " , $value , $matches )) {
2024-08-23 09:10:27 -04:00
return 60 * 60 * (( int ) $matches [ 0 ]);
2024-08-16 07:23:10 -04:00
}
return null ;
}
2022-09-01 06:13:05 -04:00
}