Completed
Push — master ( c0a409...2c6d3d )
by
unknown
31:40
created
lib/public/BackgroundJob/Job.php 1 patch
Indentation   +78 added lines, -78 removed lines patch added patch discarded remove patch
@@ -22,82 +22,82 @@
 block discarded – undo
22 22
  * @since 33.0.0 removed deprecated `execute()` method
23 23
  */
24 24
 abstract class Job implements IJob, IParallelAwareJob {
25
-	protected string $id = '0';
26
-	protected int $lastRun = 0;
27
-	protected mixed $argument = null;
28
-	protected bool $allowParallelRuns = true;
29
-
30
-	/**
31
-	 * @since 15.0.0
32
-	 */
33
-	public function __construct(
34
-		protected ITimeFactory $time,
35
-	) {
36
-	}
37
-
38
-	#[Override]
39
-	public function start(IJobList $jobList): void {
40
-		$jobList->setLastRun($this);
41
-		$logger = \OCP\Server::get(LoggerInterface::class);
42
-
43
-		try {
44
-			$jobDetails = get_class($this) . ' (id: ' . $this->getId() . ', arguments: ' . json_encode($this->getArgument()) . ')';
45
-			$jobStartTime = $this->time->getTime();
46
-			$logger->debug('Starting job ' . $jobDetails, ['app' => 'cron']);
47
-			$this->run($this->argument);
48
-			$timeTaken = $this->time->getTime() - $jobStartTime;
49
-
50
-			$logger->debug('Finished job ' . $jobDetails . ' in ' . $timeTaken . ' seconds', ['app' => 'cron']);
51
-			$jobList->setExecutionTime($this, $timeTaken);
52
-		} catch (\Throwable $e) {
53
-			if ($logger) {
54
-				$logger->error('Error while running background job ' . $jobDetails, [
55
-					'app' => 'core',
56
-					'exception' => $e,
57
-				]);
58
-			}
59
-		}
60
-	}
61
-
62
-	#[Override]
63
-	final public function setId(string $id): void {
64
-		$this->id = $id;
65
-	}
66
-
67
-	#[Override]
68
-	final public function setLastRun(int $lastRun): void {
69
-		$this->lastRun = $lastRun;
70
-	}
71
-
72
-	#[Override]
73
-	public function setArgument(mixed $argument): void {
74
-		$this->argument = $argument;
75
-	}
76
-
77
-	#[Override]
78
-	final public function getId(): string {
79
-		return $this->id;
80
-	}
81
-
82
-	#[Override]
83
-	final public function getLastRun(): int {
84
-		return $this->lastRun;
85
-	}
86
-
87
-	#[Override]
88
-	public function getArgument(): mixed {
89
-		return $this->argument;
90
-	}
91
-
92
-	#[Override]
93
-	public function setAllowParallelRuns(bool $allow): void {
94
-		$this->allowParallelRuns = $allow;
95
-	}
96
-
97
-	#[Override]
98
-	public function getAllowParallelRuns(): bool {
99
-		return $this->allowParallelRuns;
100
-	}
101
-
102
-	abstract protected function run($argument);
25
+    protected string $id = '0';
26
+    protected int $lastRun = 0;
27
+    protected mixed $argument = null;
28
+    protected bool $allowParallelRuns = true;
29
+
30
+    /**
31
+     * @since 15.0.0
32
+     */
33
+    public function __construct(
34
+        protected ITimeFactory $time,
35
+    ) {
36
+    }
37
+
38
+    #[Override]
39
+    public function start(IJobList $jobList): void {
40
+        $jobList->setLastRun($this);
41
+        $logger = \OCP\Server::get(LoggerInterface::class);
42
+
43
+        try {
44
+            $jobDetails = get_class($this) . ' (id: ' . $this->getId() . ', arguments: ' . json_encode($this->getArgument()) . ')';
45
+            $jobStartTime = $this->time->getTime();
46
+            $logger->debug('Starting job ' . $jobDetails, ['app' => 'cron']);
47
+            $this->run($this->argument);
48
+            $timeTaken = $this->time->getTime() - $jobStartTime;
49
+
50
+            $logger->debug('Finished job ' . $jobDetails . ' in ' . $timeTaken . ' seconds', ['app' => 'cron']);
51
+            $jobList->setExecutionTime($this, $timeTaken);
52
+        } catch (\Throwable $e) {
53
+            if ($logger) {
54
+                $logger->error('Error while running background job ' . $jobDetails, [
55
+                    'app' => 'core',
56
+                    'exception' => $e,
57
+                ]);
58
+            }
59
+        }
60
+    }
61
+
62
+    #[Override]
63
+    final public function setId(string $id): void {
64
+        $this->id = $id;
65
+    }
66
+
67
+    #[Override]
68
+    final public function setLastRun(int $lastRun): void {
69
+        $this->lastRun = $lastRun;
70
+    }
71
+
72
+    #[Override]
73
+    public function setArgument(mixed $argument): void {
74
+        $this->argument = $argument;
75
+    }
76
+
77
+    #[Override]
78
+    final public function getId(): string {
79
+        return $this->id;
80
+    }
81
+
82
+    #[Override]
83
+    final public function getLastRun(): int {
84
+        return $this->lastRun;
85
+    }
86
+
87
+    #[Override]
88
+    public function getArgument(): mixed {
89
+        return $this->argument;
90
+    }
91
+
92
+    #[Override]
93
+    public function setAllowParallelRuns(bool $allow): void {
94
+        $this->allowParallelRuns = $allow;
95
+    }
96
+
97
+    #[Override]
98
+    public function getAllowParallelRuns(): bool {
99
+        return $this->allowParallelRuns;
100
+    }
101
+
102
+    abstract protected function run($argument);
103 103
 }
Please login to merge, or discard this patch.
lib/public/BackgroundJob/IJob.php 1 patch
Indentation   +56 added lines, -56 removed lines patch added patch discarded remove patch
@@ -18,67 +18,67 @@
 block discarded – undo
18 18
  * @since 33.0.0 removed deprecated `execute()` method
19 19
  */
20 20
 interface IJob {
21
-	/**
22
-	 * @since 24.0.0
23
-	 */
24
-	public const TIME_INSENSITIVE = 0;
25
-	/**
26
-	 * @since 24.0.0
27
-	 */
28
-	public const TIME_SENSITIVE = 1;
21
+    /**
22
+     * @since 24.0.0
23
+     */
24
+    public const TIME_INSENSITIVE = 0;
25
+    /**
26
+     * @since 24.0.0
27
+     */
28
+    public const TIME_SENSITIVE = 1;
29 29
 
30
-	/**
31
-	 * Start the background job with the registered argument
32
-	 *
33
-	 * This methods will take care of running the background job, of initializing
34
-	 * the state and cleaning up the job list after running the job.
35
-	 *
36
-	 * For common background job scenario, you will want to use TimedJob or QueuedJob
37
-	 * instead of overwriting this method.
38
-	 *
39
-	 * @param IJobList $jobList The job list that manages the state of this job
40
-	 * @since 25.0.0
41
-	 */
42
-	public function start(IJobList $jobList): void;
30
+    /**
31
+     * Start the background job with the registered argument
32
+     *
33
+     * This methods will take care of running the background job, of initializing
34
+     * the state and cleaning up the job list after running the job.
35
+     *
36
+     * For common background job scenario, you will want to use TimedJob or QueuedJob
37
+     * instead of overwriting this method.
38
+     *
39
+     * @param IJobList $jobList The job list that manages the state of this job
40
+     * @since 25.0.0
41
+     */
42
+    public function start(IJobList $jobList): void;
43 43
 
44
-	/**
45
-	 * @since 7.0.0
46
-	 * @since 33.0.0 Parameter $id changed from int to string
47
-	 */
48
-	public function setId(string $id): void;
44
+    /**
45
+     * @since 7.0.0
46
+     * @since 33.0.0 Parameter $id changed from int to string
47
+     */
48
+    public function setId(string $id): void;
49 49
 
50
-	/**
51
-	 * @since 7.0.0
52
-	 */
53
-	public function setLastRun(int $lastRun): void;
50
+    /**
51
+     * @since 7.0.0
52
+     */
53
+    public function setLastRun(int $lastRun): void;
54 54
 
55
-	/**
56
-	 * @param mixed $argument
57
-	 * @since 7.0.0
58
-	 */
59
-	public function setArgument(mixed $argument): void;
55
+    /**
56
+     * @param mixed $argument
57
+     * @since 7.0.0
58
+     */
59
+    public function setArgument(mixed $argument): void;
60 60
 
61
-	/**
62
-	 * Get the id of the background job
63
-	 * This id is determined by the job list when a job is added to the list
64
-	 *
65
-	 * @since 7.0.0
66
-	 * @since 33.0.0 The return type changed from int to string
67
-	 */
68
-	public function getId(): string;
61
+    /**
62
+     * Get the id of the background job
63
+     * This id is determined by the job list when a job is added to the list
64
+     *
65
+     * @since 7.0.0
66
+     * @since 33.0.0 The return type changed from int to string
67
+     */
68
+    public function getId(): string;
69 69
 
70
-	/**
71
-	 * Get the last time this job was run as unix timestamp
72
-	 *
73
-	 * @since 7.0.0
74
-	 */
75
-	public function getLastRun(): int;
70
+    /**
71
+     * Get the last time this job was run as unix timestamp
72
+     *
73
+     * @since 7.0.0
74
+     */
75
+    public function getLastRun(): int;
76 76
 
77
-	/**
78
-	 * Get the argument associated with the background job
79
-	 * This is the argument that will be passed to the background job
80
-	 *
81
-	 * @since 7.0.0
82
-	 */
83
-	public function getArgument(): mixed;
77
+    /**
78
+     * Get the argument associated with the background job
79
+     * This is the argument that will be passed to the background job
80
+     *
81
+     * @since 7.0.0
82
+     */
83
+    public function getArgument(): mixed;
84 84
 }
Please login to merge, or discard this patch.
lib/public/BackgroundJob/IJobList.php 1 patch
Indentation   +141 added lines, -141 removed lines patch added patch discarded remove patch
@@ -32,145 +32,145 @@
 block discarded – undo
32 32
  */
33 33
 #[Consumable(since: '7.0.0')]
34 34
 interface IJobList {
35
-	/**
36
-	 * Add a job to the list
37
-	 *
38
-	 * @param IJob|class-string<IJob> $job
39
-	 * @param mixed $argument The argument to be passed to $job->run() when the job is executed
40
-	 * @since 7.0.0
41
-	 */
42
-	public function add(IJob|string $job, mixed $argument = null): void;
43
-
44
-	/**
45
-	 * Add a job to the list but only run it after the given timestamp
46
-	 *
47
-	 * For cron background jobs this means the job will likely run shortly after the timestamp
48
-	 * has been reached. For ajax background jobs the job might only run when users are active
49
-	 * on the instance again.
50
-	 *
51
-	 * @param class-string<IJob> $job
52
-	 * @param mixed $argument The serializable argument to be passed to $job->run() when the job is executed
53
-	 * @since 28.0.0
54
-	 */
55
-	public function scheduleAfter(string $job, int $runAfter, mixed $argument = null): void;
56
-
57
-	/**
58
-	 * Remove a job from the list
59
-	 *
60
-	 * @param IJob|class-string<IJob> $job
61
-	 * @param mixed $argument
62
-	 * @since 7.0.0
63
-	 */
64
-	public function remove(IJob|string $job, mixed $argument = null): void;
65
-
66
-	/**
67
-	 * Remove a job from the list by id
68
-	 *
69
-	 * @since 30.0.0
70
-	 * @since 33.0.0 Parameter $id changed from int to string
71
-	 */
72
-	public function removeById(string $id): void;
73
-
74
-	/**
75
-	 * check if a job is in the list
76
-	 *
77
-	 * @param IJob|class-string<IJob> $job
78
-	 * @param mixed $argument
79
-	 * @since 7.0.0
80
-	 */
81
-	public function has(IJob|string $job, mixed $argument): bool;
82
-
83
-	/**
84
-	 * Get jobs matching the search
85
-	 *
86
-	 * @param IJob|class-string<IJob>|null $job
87
-	 * @return array<IJob>
88
-	 * @since 25.0.0
89
-	 * @deprecated 26.0.0 Use getJobsIterator instead to avoid duplicated job objects
90
-	 */
91
-	public function getJobs(IJob|string|null $job, ?int $limit, int $offset): array;
92
-
93
-	/**
94
-	 * Get jobs matching the search
95
-	 *
96
-	 * @param IJob|class-string<IJob>|null $job
97
-	 * @return iterable<IJob>
98
-	 * @since 26.0.0
99
-	 */
100
-	public function getJobsIterator(IJob|string|null $job, ?int $limit, int $offset): iterable;
101
-
102
-	/**
103
-	 * Get the next job in the list
104
-	 *
105
-	 * @param bool $onlyTimeSensitive Whether we get only time sensitive jobs or not
106
-	 * @param class-string<IJob>[]|null $jobClasses List of job classes to restrict which next job we get
107
-	 * @return ?IJob the next job to run. Beware that this object may be a singleton and may be modified by the next call to buildJob.
108
-	 * @since 7.0.0 - In 24.0.0 parameter $onlyTimeSensitive got added; In 30.0.0 parameter $jobClasses got added
109
-	 */
110
-	public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob;
111
-
112
-	/**
113
-	 * @since 7.0.0
114
-	 * @since 33.0.0 Parameter $id changed from int to string
115
-	 */
116
-	public function getById(string $id): ?IJob;
117
-
118
-	/**
119
-	 * @since 23.0.0
120
-	 * @since 33.0.0 Parameter $id changed from int to string
121
-	 */
122
-	public function getDetailsById(string $id): ?array;
123
-
124
-	/**
125
-	 * set the job that was last ran to the current time
126
-	 *
127
-	 * @since 7.0.0
128
-	 */
129
-	public function setLastJob(IJob $job): void;
130
-
131
-	/**
132
-	 * Remove the reservation for a job
133
-	 *
134
-	 * @since 9.1.0
135
-	 */
136
-	public function unlockJob(IJob $job): void;
137
-
138
-	/**
139
-	 * set the lastRun of $job to now
140
-	 *
141
-	 * @since 7.0.0
142
-	 */
143
-	public function setLastRun(IJob $job): void;
144
-
145
-	/**
146
-	 * set the run duration of $job
147
-	 *
148
-	 * @since 12.0.0
149
-	 */
150
-	public function setExecutionTime(IJob $job, int $timeTaken): void;
151
-
152
-	/**
153
-	 * Reset the $job so it executes on the next trigger
154
-	 *
155
-	 * @since 23.0.0
156
-	 */
157
-	public function resetBackgroundJob(IJob $job): void;
158
-
159
-	/**
160
-	 * Checks whether a job of the passed class was reserved to run
161
-	 * in the last 6h
162
-	 *
163
-	 * @param class-string<IJob>|null $className
164
-	 * @return bool
165
-	 * @since 27.0.0
166
-	 */
167
-	public function hasReservedJob(?string $className): bool;
168
-
169
-	/**
170
-	 * Returns a count of jobs per Job class
171
-	 *
172
-	 * @return list<array{class:class-string<IJob>, count:int}>
173
-	 * @since 30.0.0
174
-	 */
175
-	public function countByClass(): array;
35
+    /**
36
+     * Add a job to the list
37
+     *
38
+     * @param IJob|class-string<IJob> $job
39
+     * @param mixed $argument The argument to be passed to $job->run() when the job is executed
40
+     * @since 7.0.0
41
+     */
42
+    public function add(IJob|string $job, mixed $argument = null): void;
43
+
44
+    /**
45
+     * Add a job to the list but only run it after the given timestamp
46
+     *
47
+     * For cron background jobs this means the job will likely run shortly after the timestamp
48
+     * has been reached. For ajax background jobs the job might only run when users are active
49
+     * on the instance again.
50
+     *
51
+     * @param class-string<IJob> $job
52
+     * @param mixed $argument The serializable argument to be passed to $job->run() when the job is executed
53
+     * @since 28.0.0
54
+     */
55
+    public function scheduleAfter(string $job, int $runAfter, mixed $argument = null): void;
56
+
57
+    /**
58
+     * Remove a job from the list
59
+     *
60
+     * @param IJob|class-string<IJob> $job
61
+     * @param mixed $argument
62
+     * @since 7.0.0
63
+     */
64
+    public function remove(IJob|string $job, mixed $argument = null): void;
65
+
66
+    /**
67
+     * Remove a job from the list by id
68
+     *
69
+     * @since 30.0.0
70
+     * @since 33.0.0 Parameter $id changed from int to string
71
+     */
72
+    public function removeById(string $id): void;
73
+
74
+    /**
75
+     * check if a job is in the list
76
+     *
77
+     * @param IJob|class-string<IJob> $job
78
+     * @param mixed $argument
79
+     * @since 7.0.0
80
+     */
81
+    public function has(IJob|string $job, mixed $argument): bool;
82
+
83
+    /**
84
+     * Get jobs matching the search
85
+     *
86
+     * @param IJob|class-string<IJob>|null $job
87
+     * @return array<IJob>
88
+     * @since 25.0.0
89
+     * @deprecated 26.0.0 Use getJobsIterator instead to avoid duplicated job objects
90
+     */
91
+    public function getJobs(IJob|string|null $job, ?int $limit, int $offset): array;
92
+
93
+    /**
94
+     * Get jobs matching the search
95
+     *
96
+     * @param IJob|class-string<IJob>|null $job
97
+     * @return iterable<IJob>
98
+     * @since 26.0.0
99
+     */
100
+    public function getJobsIterator(IJob|string|null $job, ?int $limit, int $offset): iterable;
101
+
102
+    /**
103
+     * Get the next job in the list
104
+     *
105
+     * @param bool $onlyTimeSensitive Whether we get only time sensitive jobs or not
106
+     * @param class-string<IJob>[]|null $jobClasses List of job classes to restrict which next job we get
107
+     * @return ?IJob the next job to run. Beware that this object may be a singleton and may be modified by the next call to buildJob.
108
+     * @since 7.0.0 - In 24.0.0 parameter $onlyTimeSensitive got added; In 30.0.0 parameter $jobClasses got added
109
+     */
110
+    public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob;
111
+
112
+    /**
113
+     * @since 7.0.0
114
+     * @since 33.0.0 Parameter $id changed from int to string
115
+     */
116
+    public function getById(string $id): ?IJob;
117
+
118
+    /**
119
+     * @since 23.0.0
120
+     * @since 33.0.0 Parameter $id changed from int to string
121
+     */
122
+    public function getDetailsById(string $id): ?array;
123
+
124
+    /**
125
+     * set the job that was last ran to the current time
126
+     *
127
+     * @since 7.0.0
128
+     */
129
+    public function setLastJob(IJob $job): void;
130
+
131
+    /**
132
+     * Remove the reservation for a job
133
+     *
134
+     * @since 9.1.0
135
+     */
136
+    public function unlockJob(IJob $job): void;
137
+
138
+    /**
139
+     * set the lastRun of $job to now
140
+     *
141
+     * @since 7.0.0
142
+     */
143
+    public function setLastRun(IJob $job): void;
144
+
145
+    /**
146
+     * set the run duration of $job
147
+     *
148
+     * @since 12.0.0
149
+     */
150
+    public function setExecutionTime(IJob $job, int $timeTaken): void;
151
+
152
+    /**
153
+     * Reset the $job so it executes on the next trigger
154
+     *
155
+     * @since 23.0.0
156
+     */
157
+    public function resetBackgroundJob(IJob $job): void;
158
+
159
+    /**
160
+     * Checks whether a job of the passed class was reserved to run
161
+     * in the last 6h
162
+     *
163
+     * @param class-string<IJob>|null $className
164
+     * @return bool
165
+     * @since 27.0.0
166
+     */
167
+    public function hasReservedJob(?string $className): bool;
168
+
169
+    /**
170
+     * Returns a count of jobs per Job class
171
+     *
172
+     * @return list<array{class:class-string<IJob>, count:int}>
173
+     * @since 30.0.0
174
+     */
175
+    public function countByClass(): array;
176 176
 }
Please login to merge, or discard this patch.
lib/private/BackgroundJob/JobList.php 1 patch
Indentation   +410 added lines, -410 removed lines patch added patch discarded remove patch
@@ -26,414 +26,414 @@
 block discarded – undo
26 26
 use function strlen;
27 27
 
28 28
 class JobList implements IJobList {
29
-	/** @var array<string, string> */
30
-	protected array $alreadyVisitedParallelBlocked = [];
31
-
32
-	public function __construct(
33
-		protected readonly IDBConnection $connection,
34
-		protected readonly IConfig $config,
35
-		protected readonly ITimeFactory $timeFactory,
36
-		protected readonly LoggerInterface $logger,
37
-		protected readonly IGenerator $generator,
38
-	) {
39
-	}
40
-
41
-	#[Override]
42
-	public function add(IJob|string $job, mixed $argument = null, ?int $firstCheck = null): void {
43
-		if ($firstCheck === null) {
44
-			$firstCheck = $this->timeFactory->getTime();
45
-		}
46
-
47
-		$class = ($job instanceof IJob) ? get_class($job) : $job;
48
-
49
-		$argumentJson = json_encode($argument);
50
-		if (strlen($argumentJson) > 4000) {
51
-			throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
52
-		}
53
-
54
-		$query = $this->connection->getQueryBuilder();
55
-		if (!$this->has($job, $argument)) {
56
-			$query->insert('jobs')
57
-				->values([
58
-					'id' => $query->createNamedParameter($this->generator->nextId(), IQueryBuilder::PARAM_INT),
59
-					'class' => $query->createNamedParameter($class),
60
-					'argument' => $query->createNamedParameter($argumentJson),
61
-					'argument_hash' => $query->createNamedParameter(hash('sha256', $argumentJson)),
62
-					'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
63
-					'last_checked' => $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT),
64
-				]);
65
-		} else {
66
-			$query->update('jobs')
67
-				->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
68
-				->set('last_checked', $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT))
69
-				->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
70
-				->where($query->expr()->eq('class', $query->createNamedParameter($class)))
71
-				->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(hash('sha256', $argumentJson))));
72
-		}
73
-		$query->executeStatement();
74
-	}
75
-
76
-	public function scheduleAfter(string $job, int $runAfter, mixed $argument = null): void {
77
-		$this->add($job, $argument, $runAfter);
78
-	}
79
-
80
-	#[Override]
81
-	public function remove(IJob|string $job, mixed $argument = null): void {
82
-		$class = ($job instanceof IJob) ? get_class($job) : $job;
83
-
84
-		$query = $this->connection->getQueryBuilder();
85
-		$query->delete('jobs')
86
-			->where($query->expr()->eq('class', $query->createNamedParameter($class)));
87
-		if (!is_null($argument)) {
88
-			$argumentJson = json_encode($argument);
89
-			$query->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(hash('sha256', $argumentJson))));
90
-		}
91
-
92
-		// Add galera safe delete chunking if using mysql
93
-		// Stops us hitting wsrep_max_ws_rows when large row counts are deleted
94
-		if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_MYSQL) {
95
-			// Then use chunked delete
96
-			$max = IQueryBuilder::MAX_ROW_DELETION;
97
-
98
-			$query->setMaxResults($max);
99
-
100
-			do {
101
-				$deleted = $query->executeStatement();
102
-			} while ($deleted === $max);
103
-		} else {
104
-			// Dont use chunked delete - let the DB handle the large row count natively
105
-			$query->executeStatement();
106
-		}
107
-	}
108
-
109
-	#[Override]
110
-	public function removeById(string $id): void {
111
-		$query = $this->connection->getQueryBuilder();
112
-		$query->delete('jobs')
113
-			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
114
-		$query->executeStatement();
115
-	}
116
-
117
-	#[Override]
118
-	public function has(IJob|string $job, mixed $argument): bool {
119
-		$class = ($job instanceof IJob) ? get_class($job) : $job;
120
-		$argument = json_encode($argument);
121
-
122
-		$query = $this->connection->getQueryBuilder();
123
-		$query->select('id')
124
-			->from('jobs')
125
-			->where($query->expr()->eq('class', $query->createNamedParameter($class)))
126
-			->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(hash('sha256', $argument))))
127
-			->setMaxResults(1);
128
-
129
-		$result = $query->executeQuery();
130
-		$row = $result->fetch();
131
-		$result->closeCursor();
132
-
133
-		return (bool)$row;
134
-	}
135
-
136
-	#[Override]
137
-	public function getJobs(IJob|string|null $job, ?int $limit, int $offset): array {
138
-		$iterable = $this->getJobsIterator($job, $limit, $offset);
139
-		return (is_array($iterable))
140
-			? $iterable
141
-			: iterator_to_array($iterable);
142
-	}
143
-
144
-	#[Override]
145
-	public function getJobsIterator(IJob|string|null $job, ?int $limit, int $offset): iterable {
146
-		$query = $this->connection->getQueryBuilder();
147
-		$query->select('*')
148
-			->from('jobs')
149
-			->setMaxResults($limit)
150
-			->setFirstResult($offset);
151
-
152
-		if ($job !== null) {
153
-			$class = ($job instanceof IJob) ? get_class($job) : $job;
154
-			$query->where($query->expr()->eq('class', $query->createNamedParameter($class)));
155
-		}
156
-
157
-		$result = $query->executeQuery();
158
-
159
-		while ($row = $result->fetch()) {
160
-			$job = $this->buildJob($row);
161
-			if ($job) {
162
-				yield $job;
163
-			}
164
-		}
165
-		$result->closeCursor();
166
-	}
167
-
168
-	#[Override]
169
-	public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob {
170
-		$query = $this->connection->getQueryBuilder();
171
-		$query->select('*')
172
-			->from('jobs')
173
-			->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
174
-			->andWhere($query->expr()->lte('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
175
-			->orderBy('last_checked', 'ASC')
176
-			->setMaxResults(1);
177
-
178
-		if ($onlyTimeSensitive) {
179
-			$query->andWhere($query->expr()->eq('time_sensitive', $query->createNamedParameter(IJob::TIME_SENSITIVE, IQueryBuilder::PARAM_INT)));
180
-		}
181
-
182
-		if (!empty($jobClasses)) {
183
-			$orClasses = [];
184
-			foreach ($jobClasses as $jobClass) {
185
-				$orClasses[] = $query->expr()->eq('class', $query->createNamedParameter($jobClass, IQueryBuilder::PARAM_STR));
186
-			}
187
-			$query->andWhere($query->expr()->orX(...$orClasses));
188
-		}
189
-
190
-		$result = $query->executeQuery();
191
-		$row = $result->fetch();
192
-		$result->closeCursor();
193
-
194
-		if ($row) {
195
-			$job = $this->buildJob($row);
196
-
197
-			if ($job instanceof IParallelAwareJob && !$job->getAllowParallelRuns() && $this->hasReservedJob(get_class($job))) {
198
-				if (!isset($this->alreadyVisitedParallelBlocked[get_class($job)])) {
199
-					$this->alreadyVisitedParallelBlocked[get_class($job)] = $job->getId();
200
-				} elseif ($this->alreadyVisitedParallelBlocked[get_class($job)] === $job->getId()) {
201
-					$this->logger->info('Skipped through all jobs and revisited a IParallelAwareJob blocked job again, giving up.', ['app' => 'cron']);
202
-					return null;
203
-				}
204
-				$this->logger->info('Skipping ' . get_class($job) . ' job with ID ' . $job->getId() . ' because another job with the same class is already running', ['app' => 'cron']);
205
-
206
-				$update = $this->connection->getQueryBuilder();
207
-				$update->update('jobs')
208
-					->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime() + 1))
209
-					->where($update->expr()->eq('id', $update->createParameter('jobid')));
210
-				$update->setParameter('jobid', $row['id']);
211
-				$update->executeStatement();
212
-
213
-				return $this->getNext($onlyTimeSensitive, $jobClasses);
214
-			}
215
-
216
-			if ($job !== null && isset($this->alreadyVisitedParallelBlocked[get_class($job)])) {
217
-				unset($this->alreadyVisitedParallelBlocked[get_class($job)]);
218
-			}
219
-
220
-			if ($job instanceof \OCP\BackgroundJob\TimedJob) {
221
-				$now = $this->timeFactory->getTime();
222
-				$nextPossibleRun = $job->getLastRun() + $job->getInterval();
223
-				if ($now < $nextPossibleRun) {
224
-					// This job is not ready for execution yet. Set timestamps to the future to avoid
225
-					// re-checking with every cron run.
226
-					// To avoid bugs that lead to jobs never executing again, the future timestamp is
227
-					// capped at two days.
228
-					$nextCheck = min($nextPossibleRun, $now + 48 * 3600);
229
-					$updateTimedJob = $this->connection->getQueryBuilder();
230
-					$updateTimedJob->update('jobs')
231
-						->set('last_checked', $updateTimedJob->createNamedParameter($nextCheck, IQueryBuilder::PARAM_INT))
232
-						->where($updateTimedJob->expr()->eq('id', $updateTimedJob->createParameter('jobid')));
233
-					$updateTimedJob->setParameter('jobid', $row['id']);
234
-					$updateTimedJob->executeStatement();
235
-
236
-					return $this->getNext($onlyTimeSensitive, $jobClasses);
237
-				}
238
-			}
239
-
240
-			$update = $this->connection->getQueryBuilder();
241
-			$update->update('jobs')
242
-				->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
243
-				->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
244
-				->where($update->expr()->eq('id', $update->createParameter('jobid')))
245
-				->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
246
-				->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
247
-			$update->setParameter('jobid', $row['id']);
248
-			$update->setParameter('reserved_at', $row['reserved_at']);
249
-			$update->setParameter('last_checked', $row['last_checked']);
250
-			$count = $update->executeStatement();
251
-
252
-			if ($count === 0) {
253
-				// Background job already executed elsewhere, try again.
254
-				return $this->getNext($onlyTimeSensitive, $jobClasses);
255
-			}
256
-
257
-			if ($job === null) {
258
-				// set the last_checked to 12h in the future to not check failing jobs all over again
259
-				$reset = $this->connection->getQueryBuilder();
260
-				$reset->update('jobs')
261
-					->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT))
262
-					->set('last_checked', $reset->createNamedParameter($this->timeFactory->getTime() + 12 * 3600, IQueryBuilder::PARAM_INT))
263
-					->where($reset->expr()->eq('id', $reset->createNamedParameter($row['id'], IQueryBuilder::PARAM_INT)));
264
-				$reset->executeStatement();
265
-
266
-				// Background job from disabled app, try again.
267
-				return $this->getNext($onlyTimeSensitive, $jobClasses);
268
-			}
269
-
270
-			return $job;
271
-		} else {
272
-			return null;
273
-		}
274
-	}
275
-
276
-	#[Override]
277
-	public function getById(string $id): ?IJob {
278
-		$row = $this->getDetailsById($id);
279
-
280
-		if ($row) {
281
-			return $this->buildJob($row);
282
-		}
283
-
284
-		return null;
285
-	}
286
-
287
-	#[Override]
288
-	public function getDetailsById(string $id): ?array {
289
-		$query = $this->connection->getQueryBuilder();
290
-		$query->select('*')
291
-			->from('jobs')
292
-			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
293
-		$result = $query->executeQuery();
294
-		$row = $result->fetch();
295
-		$result->closeCursor();
296
-
297
-		if ($row) {
298
-			return $row;
299
-		}
300
-
301
-		return null;
302
-	}
303
-
304
-	/**
305
-	 * get the job object from a row in the db
306
-	 *
307
-	 * @param array{class:class-string<IJob>, id:mixed, last_run:mixed, argument:string} $row
308
-	 * @return ?IJob the next job to run. Beware that this object may be a singleton and may be modified by the next call to buildJob.
309
-	 */
310
-	private function buildJob(array $row): ?IJob {
311
-		try {
312
-			try {
313
-				// Try to load the job as a service
314
-				/** @var IJob $job */
315
-				$job = \OCP\Server::get($row['class']);
316
-			} catch (ContainerExceptionInterface $e) {
317
-				if (class_exists($row['class'])) {
318
-					$class = $row['class'];
319
-					$job = new $class();
320
-				} else {
321
-					$this->logger->warning('failed to create instance of background job: ' . $row['class'], ['app' => 'cron', 'exception' => $e]);
322
-					// Remove job from disabled app or old version of an app
323
-					$this->removeById($row['id']);
324
-					return null;
325
-				}
326
-			}
327
-
328
-			if (!($job instanceof IJob)) {
329
-				// This most likely means an invalid job was enqueued. We can ignore it.
330
-				return null;
331
-			}
332
-			$job->setId($row['id']);
333
-			$job->setLastRun((int)$row['last_run']);
334
-			$job->setArgument(json_decode($row['argument'], true));
335
-			return $job;
336
-		} catch (AutoloadNotAllowedException $e) {
337
-			// job is from a disabled app, ignore
338
-			return null;
339
-		}
340
-	}
341
-
342
-	/**
343
-	 * set the job that was last ran
344
-	 */
345
-	public function setLastJob(IJob $job): void {
346
-		$this->unlockJob($job);
347
-		$this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
348
-	}
349
-
350
-	#[Override]
351
-	public function unlockJob(IJob $job): void {
352
-		$query = $this->connection->getQueryBuilder();
353
-		$query->update('jobs')
354
-			->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
355
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
356
-		$query->executeStatement();
357
-	}
358
-
359
-	#[Override]
360
-	public function setLastRun(IJob $job): void {
361
-		$query = $this->connection->getQueryBuilder();
362
-		$query->update('jobs')
363
-			->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
364
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
365
-
366
-		if ($job instanceof \OCP\BackgroundJob\TimedJob
367
-			&& !$job->isTimeSensitive()) {
368
-			$query->set('time_sensitive', $query->createNamedParameter(IJob::TIME_INSENSITIVE));
369
-		}
370
-
371
-		$query->executeStatement();
372
-	}
373
-
374
-	#[Override]
375
-	public function setExecutionTime(IJob $job, $timeTaken): void {
376
-		$query = $this->connection->getQueryBuilder();
377
-		$query->update('jobs')
378
-			->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
379
-			->set('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
380
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
381
-		$query->executeStatement();
382
-	}
383
-
384
-	#[Override]
385
-	public function resetBackgroundJob(IJob $job): void {
386
-		$query = $this->connection->getQueryBuilder();
387
-		$query->update('jobs')
388
-			->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
389
-			->set('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
390
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId()), IQueryBuilder::PARAM_INT));
391
-		$query->executeStatement();
392
-	}
393
-
394
-	#[Override]
395
-	public function hasReservedJob(?string $className = null): bool {
396
-		$query = $this->connection->getQueryBuilder();
397
-		$query->select('*')
398
-			->from('jobs')
399
-			->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 6 * 3600, IQueryBuilder::PARAM_INT)))
400
-			->setMaxResults(1);
401
-
402
-		if ($className !== null) {
403
-			$query->andWhere($query->expr()->eq('class', $query->createNamedParameter($className)));
404
-		}
405
-
406
-		try {
407
-			$result = $query->executeQuery();
408
-			$hasReservedJobs = $result->fetch() !== false;
409
-			$result->closeCursor();
410
-			return $hasReservedJobs;
411
-		} catch (Exception $e) {
412
-			$this->logger->debug('Querying reserved jobs failed', ['exception' => $e]);
413
-			return false;
414
-		}
415
-	}
416
-
417
-	#[Override]
418
-	public function countByClass(): array {
419
-		$query = $this->connection->getQueryBuilder();
420
-		$query->select('class')
421
-			->selectAlias($query->func()->count('id'), 'count')
422
-			->from('jobs')
423
-			->orderBy('count')
424
-			->groupBy('class');
425
-
426
-		$result = $query->executeQuery();
427
-
428
-		$jobs = [];
429
-
430
-		while (($row = $result->fetch()) !== false) {
431
-			/**
432
-			 * @var array{count:int, class:class-string<IJob>} $row
433
-			 */
434
-			$jobs[] = $row;
435
-		}
436
-
437
-		return $jobs;
438
-	}
29
+    /** @var array<string, string> */
30
+    protected array $alreadyVisitedParallelBlocked = [];
31
+
32
+    public function __construct(
33
+        protected readonly IDBConnection $connection,
34
+        protected readonly IConfig $config,
35
+        protected readonly ITimeFactory $timeFactory,
36
+        protected readonly LoggerInterface $logger,
37
+        protected readonly IGenerator $generator,
38
+    ) {
39
+    }
40
+
41
+    #[Override]
42
+    public function add(IJob|string $job, mixed $argument = null, ?int $firstCheck = null): void {
43
+        if ($firstCheck === null) {
44
+            $firstCheck = $this->timeFactory->getTime();
45
+        }
46
+
47
+        $class = ($job instanceof IJob) ? get_class($job) : $job;
48
+
49
+        $argumentJson = json_encode($argument);
50
+        if (strlen($argumentJson) > 4000) {
51
+            throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
52
+        }
53
+
54
+        $query = $this->connection->getQueryBuilder();
55
+        if (!$this->has($job, $argument)) {
56
+            $query->insert('jobs')
57
+                ->values([
58
+                    'id' => $query->createNamedParameter($this->generator->nextId(), IQueryBuilder::PARAM_INT),
59
+                    'class' => $query->createNamedParameter($class),
60
+                    'argument' => $query->createNamedParameter($argumentJson),
61
+                    'argument_hash' => $query->createNamedParameter(hash('sha256', $argumentJson)),
62
+                    'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
63
+                    'last_checked' => $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT),
64
+                ]);
65
+        } else {
66
+            $query->update('jobs')
67
+                ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
68
+                ->set('last_checked', $query->createNamedParameter($firstCheck, IQueryBuilder::PARAM_INT))
69
+                ->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
70
+                ->where($query->expr()->eq('class', $query->createNamedParameter($class)))
71
+                ->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(hash('sha256', $argumentJson))));
72
+        }
73
+        $query->executeStatement();
74
+    }
75
+
76
+    public function scheduleAfter(string $job, int $runAfter, mixed $argument = null): void {
77
+        $this->add($job, $argument, $runAfter);
78
+    }
79
+
80
+    #[Override]
81
+    public function remove(IJob|string $job, mixed $argument = null): void {
82
+        $class = ($job instanceof IJob) ? get_class($job) : $job;
83
+
84
+        $query = $this->connection->getQueryBuilder();
85
+        $query->delete('jobs')
86
+            ->where($query->expr()->eq('class', $query->createNamedParameter($class)));
87
+        if (!is_null($argument)) {
88
+            $argumentJson = json_encode($argument);
89
+            $query->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(hash('sha256', $argumentJson))));
90
+        }
91
+
92
+        // Add galera safe delete chunking if using mysql
93
+        // Stops us hitting wsrep_max_ws_rows when large row counts are deleted
94
+        if ($this->connection->getDatabaseProvider() === IDBConnection::PLATFORM_MYSQL) {
95
+            // Then use chunked delete
96
+            $max = IQueryBuilder::MAX_ROW_DELETION;
97
+
98
+            $query->setMaxResults($max);
99
+
100
+            do {
101
+                $deleted = $query->executeStatement();
102
+            } while ($deleted === $max);
103
+        } else {
104
+            // Dont use chunked delete - let the DB handle the large row count natively
105
+            $query->executeStatement();
106
+        }
107
+    }
108
+
109
+    #[Override]
110
+    public function removeById(string $id): void {
111
+        $query = $this->connection->getQueryBuilder();
112
+        $query->delete('jobs')
113
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
114
+        $query->executeStatement();
115
+    }
116
+
117
+    #[Override]
118
+    public function has(IJob|string $job, mixed $argument): bool {
119
+        $class = ($job instanceof IJob) ? get_class($job) : $job;
120
+        $argument = json_encode($argument);
121
+
122
+        $query = $this->connection->getQueryBuilder();
123
+        $query->select('id')
124
+            ->from('jobs')
125
+            ->where($query->expr()->eq('class', $query->createNamedParameter($class)))
126
+            ->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(hash('sha256', $argument))))
127
+            ->setMaxResults(1);
128
+
129
+        $result = $query->executeQuery();
130
+        $row = $result->fetch();
131
+        $result->closeCursor();
132
+
133
+        return (bool)$row;
134
+    }
135
+
136
+    #[Override]
137
+    public function getJobs(IJob|string|null $job, ?int $limit, int $offset): array {
138
+        $iterable = $this->getJobsIterator($job, $limit, $offset);
139
+        return (is_array($iterable))
140
+            ? $iterable
141
+            : iterator_to_array($iterable);
142
+    }
143
+
144
+    #[Override]
145
+    public function getJobsIterator(IJob|string|null $job, ?int $limit, int $offset): iterable {
146
+        $query = $this->connection->getQueryBuilder();
147
+        $query->select('*')
148
+            ->from('jobs')
149
+            ->setMaxResults($limit)
150
+            ->setFirstResult($offset);
151
+
152
+        if ($job !== null) {
153
+            $class = ($job instanceof IJob) ? get_class($job) : $job;
154
+            $query->where($query->expr()->eq('class', $query->createNamedParameter($class)));
155
+        }
156
+
157
+        $result = $query->executeQuery();
158
+
159
+        while ($row = $result->fetch()) {
160
+            $job = $this->buildJob($row);
161
+            if ($job) {
162
+                yield $job;
163
+            }
164
+        }
165
+        $result->closeCursor();
166
+    }
167
+
168
+    #[Override]
169
+    public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob {
170
+        $query = $this->connection->getQueryBuilder();
171
+        $query->select('*')
172
+            ->from('jobs')
173
+            ->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
174
+            ->andWhere($query->expr()->lte('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
175
+            ->orderBy('last_checked', 'ASC')
176
+            ->setMaxResults(1);
177
+
178
+        if ($onlyTimeSensitive) {
179
+            $query->andWhere($query->expr()->eq('time_sensitive', $query->createNamedParameter(IJob::TIME_SENSITIVE, IQueryBuilder::PARAM_INT)));
180
+        }
181
+
182
+        if (!empty($jobClasses)) {
183
+            $orClasses = [];
184
+            foreach ($jobClasses as $jobClass) {
185
+                $orClasses[] = $query->expr()->eq('class', $query->createNamedParameter($jobClass, IQueryBuilder::PARAM_STR));
186
+            }
187
+            $query->andWhere($query->expr()->orX(...$orClasses));
188
+        }
189
+
190
+        $result = $query->executeQuery();
191
+        $row = $result->fetch();
192
+        $result->closeCursor();
193
+
194
+        if ($row) {
195
+            $job = $this->buildJob($row);
196
+
197
+            if ($job instanceof IParallelAwareJob && !$job->getAllowParallelRuns() && $this->hasReservedJob(get_class($job))) {
198
+                if (!isset($this->alreadyVisitedParallelBlocked[get_class($job)])) {
199
+                    $this->alreadyVisitedParallelBlocked[get_class($job)] = $job->getId();
200
+                } elseif ($this->alreadyVisitedParallelBlocked[get_class($job)] === $job->getId()) {
201
+                    $this->logger->info('Skipped through all jobs and revisited a IParallelAwareJob blocked job again, giving up.', ['app' => 'cron']);
202
+                    return null;
203
+                }
204
+                $this->logger->info('Skipping ' . get_class($job) . ' job with ID ' . $job->getId() . ' because another job with the same class is already running', ['app' => 'cron']);
205
+
206
+                $update = $this->connection->getQueryBuilder();
207
+                $update->update('jobs')
208
+                    ->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime() + 1))
209
+                    ->where($update->expr()->eq('id', $update->createParameter('jobid')));
210
+                $update->setParameter('jobid', $row['id']);
211
+                $update->executeStatement();
212
+
213
+                return $this->getNext($onlyTimeSensitive, $jobClasses);
214
+            }
215
+
216
+            if ($job !== null && isset($this->alreadyVisitedParallelBlocked[get_class($job)])) {
217
+                unset($this->alreadyVisitedParallelBlocked[get_class($job)]);
218
+            }
219
+
220
+            if ($job instanceof \OCP\BackgroundJob\TimedJob) {
221
+                $now = $this->timeFactory->getTime();
222
+                $nextPossibleRun = $job->getLastRun() + $job->getInterval();
223
+                if ($now < $nextPossibleRun) {
224
+                    // This job is not ready for execution yet. Set timestamps to the future to avoid
225
+                    // re-checking with every cron run.
226
+                    // To avoid bugs that lead to jobs never executing again, the future timestamp is
227
+                    // capped at two days.
228
+                    $nextCheck = min($nextPossibleRun, $now + 48 * 3600);
229
+                    $updateTimedJob = $this->connection->getQueryBuilder();
230
+                    $updateTimedJob->update('jobs')
231
+                        ->set('last_checked', $updateTimedJob->createNamedParameter($nextCheck, IQueryBuilder::PARAM_INT))
232
+                        ->where($updateTimedJob->expr()->eq('id', $updateTimedJob->createParameter('jobid')));
233
+                    $updateTimedJob->setParameter('jobid', $row['id']);
234
+                    $updateTimedJob->executeStatement();
235
+
236
+                    return $this->getNext($onlyTimeSensitive, $jobClasses);
237
+                }
238
+            }
239
+
240
+            $update = $this->connection->getQueryBuilder();
241
+            $update->update('jobs')
242
+                ->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
243
+                ->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
244
+                ->where($update->expr()->eq('id', $update->createParameter('jobid')))
245
+                ->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
246
+                ->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
247
+            $update->setParameter('jobid', $row['id']);
248
+            $update->setParameter('reserved_at', $row['reserved_at']);
249
+            $update->setParameter('last_checked', $row['last_checked']);
250
+            $count = $update->executeStatement();
251
+
252
+            if ($count === 0) {
253
+                // Background job already executed elsewhere, try again.
254
+                return $this->getNext($onlyTimeSensitive, $jobClasses);
255
+            }
256
+
257
+            if ($job === null) {
258
+                // set the last_checked to 12h in the future to not check failing jobs all over again
259
+                $reset = $this->connection->getQueryBuilder();
260
+                $reset->update('jobs')
261
+                    ->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT))
262
+                    ->set('last_checked', $reset->createNamedParameter($this->timeFactory->getTime() + 12 * 3600, IQueryBuilder::PARAM_INT))
263
+                    ->where($reset->expr()->eq('id', $reset->createNamedParameter($row['id'], IQueryBuilder::PARAM_INT)));
264
+                $reset->executeStatement();
265
+
266
+                // Background job from disabled app, try again.
267
+                return $this->getNext($onlyTimeSensitive, $jobClasses);
268
+            }
269
+
270
+            return $job;
271
+        } else {
272
+            return null;
273
+        }
274
+    }
275
+
276
+    #[Override]
277
+    public function getById(string $id): ?IJob {
278
+        $row = $this->getDetailsById($id);
279
+
280
+        if ($row) {
281
+            return $this->buildJob($row);
282
+        }
283
+
284
+        return null;
285
+    }
286
+
287
+    #[Override]
288
+    public function getDetailsById(string $id): ?array {
289
+        $query = $this->connection->getQueryBuilder();
290
+        $query->select('*')
291
+            ->from('jobs')
292
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
293
+        $result = $query->executeQuery();
294
+        $row = $result->fetch();
295
+        $result->closeCursor();
296
+
297
+        if ($row) {
298
+            return $row;
299
+        }
300
+
301
+        return null;
302
+    }
303
+
304
+    /**
305
+     * get the job object from a row in the db
306
+     *
307
+     * @param array{class:class-string<IJob>, id:mixed, last_run:mixed, argument:string} $row
308
+     * @return ?IJob the next job to run. Beware that this object may be a singleton and may be modified by the next call to buildJob.
309
+     */
310
+    private function buildJob(array $row): ?IJob {
311
+        try {
312
+            try {
313
+                // Try to load the job as a service
314
+                /** @var IJob $job */
315
+                $job = \OCP\Server::get($row['class']);
316
+            } catch (ContainerExceptionInterface $e) {
317
+                if (class_exists($row['class'])) {
318
+                    $class = $row['class'];
319
+                    $job = new $class();
320
+                } else {
321
+                    $this->logger->warning('failed to create instance of background job: ' . $row['class'], ['app' => 'cron', 'exception' => $e]);
322
+                    // Remove job from disabled app or old version of an app
323
+                    $this->removeById($row['id']);
324
+                    return null;
325
+                }
326
+            }
327
+
328
+            if (!($job instanceof IJob)) {
329
+                // This most likely means an invalid job was enqueued. We can ignore it.
330
+                return null;
331
+            }
332
+            $job->setId($row['id']);
333
+            $job->setLastRun((int)$row['last_run']);
334
+            $job->setArgument(json_decode($row['argument'], true));
335
+            return $job;
336
+        } catch (AutoloadNotAllowedException $e) {
337
+            // job is from a disabled app, ignore
338
+            return null;
339
+        }
340
+    }
341
+
342
+    /**
343
+     * set the job that was last ran
344
+     */
345
+    public function setLastJob(IJob $job): void {
346
+        $this->unlockJob($job);
347
+        $this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
348
+    }
349
+
350
+    #[Override]
351
+    public function unlockJob(IJob $job): void {
352
+        $query = $this->connection->getQueryBuilder();
353
+        $query->update('jobs')
354
+            ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
355
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
356
+        $query->executeStatement();
357
+    }
358
+
359
+    #[Override]
360
+    public function setLastRun(IJob $job): void {
361
+        $query = $this->connection->getQueryBuilder();
362
+        $query->update('jobs')
363
+            ->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
364
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
365
+
366
+        if ($job instanceof \OCP\BackgroundJob\TimedJob
367
+            && !$job->isTimeSensitive()) {
368
+            $query->set('time_sensitive', $query->createNamedParameter(IJob::TIME_INSENSITIVE));
369
+        }
370
+
371
+        $query->executeStatement();
372
+    }
373
+
374
+    #[Override]
375
+    public function setExecutionTime(IJob $job, $timeTaken): void {
376
+        $query = $this->connection->getQueryBuilder();
377
+        $query->update('jobs')
378
+            ->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
379
+            ->set('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
380
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
381
+        $query->executeStatement();
382
+    }
383
+
384
+    #[Override]
385
+    public function resetBackgroundJob(IJob $job): void {
386
+        $query = $this->connection->getQueryBuilder();
387
+        $query->update('jobs')
388
+            ->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
389
+            ->set('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
390
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId()), IQueryBuilder::PARAM_INT));
391
+        $query->executeStatement();
392
+    }
393
+
394
+    #[Override]
395
+    public function hasReservedJob(?string $className = null): bool {
396
+        $query = $this->connection->getQueryBuilder();
397
+        $query->select('*')
398
+            ->from('jobs')
399
+            ->where($query->expr()->gt('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 6 * 3600, IQueryBuilder::PARAM_INT)))
400
+            ->setMaxResults(1);
401
+
402
+        if ($className !== null) {
403
+            $query->andWhere($query->expr()->eq('class', $query->createNamedParameter($className)));
404
+        }
405
+
406
+        try {
407
+            $result = $query->executeQuery();
408
+            $hasReservedJobs = $result->fetch() !== false;
409
+            $result->closeCursor();
410
+            return $hasReservedJobs;
411
+        } catch (Exception $e) {
412
+            $this->logger->debug('Querying reserved jobs failed', ['exception' => $e]);
413
+            return false;
414
+        }
415
+    }
416
+
417
+    #[Override]
418
+    public function countByClass(): array {
419
+        $query = $this->connection->getQueryBuilder();
420
+        $query->select('class')
421
+            ->selectAlias($query->func()->count('id'), 'count')
422
+            ->from('jobs')
423
+            ->orderBy('count')
424
+            ->groupBy('class');
425
+
426
+        $result = $query->executeQuery();
427
+
428
+        $jobs = [];
429
+
430
+        while (($row = $result->fetch()) !== false) {
431
+            /**
432
+             * @var array{count:int, class:class-string<IJob>} $row
433
+             */
434
+            $jobs[] = $row;
435
+        }
436
+
437
+        return $jobs;
438
+    }
439 439
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/BackgroundJob/RefreshWebcalJobTest.php 1 patch
Indentation   +74 added lines, -74 removed lines patch added patch discarded remove patch
@@ -19,78 +19,78 @@
 block discarded – undo
19 19
 use Test\TestCase;
20 20
 
21 21
 class RefreshWebcalJobTest extends TestCase {
22
-	private RefreshWebcalService&MockObject $refreshWebcalService;
23
-	private IConfig&MockObject $config;
24
-	private LoggerInterface $logger;
25
-	private ITimeFactory&MockObject $timeFactory;
26
-	private IJobList&MockObject $jobList;
27
-
28
-	protected function setUp(): void {
29
-		parent::setUp();
30
-
31
-		$this->refreshWebcalService = $this->createMock(RefreshWebcalService::class);
32
-		$this->config = $this->createMock(IConfig::class);
33
-		$this->logger = $this->createMock(LoggerInterface::class);
34
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
35
-
36
-		$this->jobList = $this->createMock(IJobList::class);
37
-	}
38
-
39
-	/**
40
-	 *
41
-	 * @param int $lastRun
42
-	 * @param int $time
43
-	 * @param bool $process
44
-	 */
45
-	#[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')]
46
-	public function testRun(int $lastRun, int $time, bool $process): void {
47
-		$backgroundJob = new RefreshWebcalJob($this->refreshWebcalService, $this->config, $this->logger, $this->timeFactory);
48
-		$backgroundJob->setId('42');
49
-
50
-		$backgroundJob->setArgument([
51
-			'principaluri' => 'principals/users/testuser',
52
-			'uri' => 'sub123',
53
-		]);
54
-		$backgroundJob->setLastRun($lastRun);
55
-
56
-		$this->refreshWebcalService->expects($this->once())
57
-			->method('getSubscription')
58
-			->with('principals/users/testuser', 'sub123')
59
-			->willReturn([
60
-				'id' => '99',
61
-				'uri' => 'sub456',
62
-				'{http://apple.com/ns/ical/}refreshrate' => 'P1D',
63
-				'{http://calendarserver.org/ns/}subscribed-strip-todos' => '1',
64
-				'{http://calendarserver.org/ns/}subscribed-strip-alarms' => '1',
65
-				'{http://calendarserver.org/ns/}subscribed-strip-attachments' => '1',
66
-				'source' => 'webcal://foo.bar/bla'
67
-			]);
68
-
69
-		$this->config->expects($this->once())
70
-			->method('getAppValue')
71
-			->with('dav', 'calendarSubscriptionRefreshRate', 'P1D')
72
-			->willReturn('P1W');
73
-
74
-		$this->timeFactory->method('getTime')
75
-			->willReturn($time);
76
-
77
-		if ($process) {
78
-			$this->refreshWebcalService->expects($this->once())
79
-				->method('refreshSubscription')
80
-				->with('principals/users/testuser', 'sub123');
81
-		} else {
82
-			$this->refreshWebcalService->expects($this->never())
83
-				->method('refreshSubscription')
84
-				->with('principals/users/testuser', 'sub123');
85
-		}
86
-
87
-		$backgroundJob->start($this->jobList);
88
-	}
89
-
90
-	public static function runDataProvider():array {
91
-		return [
92
-			[0, 100000, true],
93
-			[100000, 100000, false]
94
-		];
95
-	}
22
+    private RefreshWebcalService&MockObject $refreshWebcalService;
23
+    private IConfig&MockObject $config;
24
+    private LoggerInterface $logger;
25
+    private ITimeFactory&MockObject $timeFactory;
26
+    private IJobList&MockObject $jobList;
27
+
28
+    protected function setUp(): void {
29
+        parent::setUp();
30
+
31
+        $this->refreshWebcalService = $this->createMock(RefreshWebcalService::class);
32
+        $this->config = $this->createMock(IConfig::class);
33
+        $this->logger = $this->createMock(LoggerInterface::class);
34
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
35
+
36
+        $this->jobList = $this->createMock(IJobList::class);
37
+    }
38
+
39
+    /**
40
+     *
41
+     * @param int $lastRun
42
+     * @param int $time
43
+     * @param bool $process
44
+     */
45
+    #[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')]
46
+    public function testRun(int $lastRun, int $time, bool $process): void {
47
+        $backgroundJob = new RefreshWebcalJob($this->refreshWebcalService, $this->config, $this->logger, $this->timeFactory);
48
+        $backgroundJob->setId('42');
49
+
50
+        $backgroundJob->setArgument([
51
+            'principaluri' => 'principals/users/testuser',
52
+            'uri' => 'sub123',
53
+        ]);
54
+        $backgroundJob->setLastRun($lastRun);
55
+
56
+        $this->refreshWebcalService->expects($this->once())
57
+            ->method('getSubscription')
58
+            ->with('principals/users/testuser', 'sub123')
59
+            ->willReturn([
60
+                'id' => '99',
61
+                'uri' => 'sub456',
62
+                '{http://apple.com/ns/ical/}refreshrate' => 'P1D',
63
+                '{http://calendarserver.org/ns/}subscribed-strip-todos' => '1',
64
+                '{http://calendarserver.org/ns/}subscribed-strip-alarms' => '1',
65
+                '{http://calendarserver.org/ns/}subscribed-strip-attachments' => '1',
66
+                'source' => 'webcal://foo.bar/bla'
67
+            ]);
68
+
69
+        $this->config->expects($this->once())
70
+            ->method('getAppValue')
71
+            ->with('dav', 'calendarSubscriptionRefreshRate', 'P1D')
72
+            ->willReturn('P1W');
73
+
74
+        $this->timeFactory->method('getTime')
75
+            ->willReturn($time);
76
+
77
+        if ($process) {
78
+            $this->refreshWebcalService->expects($this->once())
79
+                ->method('refreshSubscription')
80
+                ->with('principals/users/testuser', 'sub123');
81
+        } else {
82
+            $this->refreshWebcalService->expects($this->never())
83
+                ->method('refreshSubscription')
84
+                ->with('principals/users/testuser', 'sub123');
85
+        }
86
+
87
+        $backgroundJob->start($this->jobList);
88
+    }
89
+
90
+    public static function runDataProvider():array {
91
+        return [
92
+            [0, 100000, true],
93
+            [100000, 100000, false]
94
+        ];
95
+    }
96 96
 }
Please login to merge, or discard this patch.
tests/lib/BackgroundJob/DummyJobList.php 1 patch
Indentation   +156 added lines, -156 removed lines patch added patch discarded remove patch
@@ -21,160 +21,160 @@
 block discarded – undo
21 21
  * in memory job list for testing purposes
22 22
  */
23 23
 class DummyJobList extends JobList {
24
-	/**
25
-	 * @var IJob[]
26
-	 */
27
-	private array $jobs = [];
28
-
29
-	/**
30
-	 * @var bool[]
31
-	 */
32
-	private array $reserved = [];
33
-
34
-	private int $last = 0;
35
-
36
-	public function __construct() {
37
-	}
38
-
39
-	/**
40
-	 * @param IJob|class-string<IJob> $job
41
-	 * @param mixed $argument
42
-	 */
43
-	public function add($job, $argument = null, ?int $firstCheck = null): void {
44
-		if (is_string($job)) {
45
-			/** @var IJob $job */
46
-			$job = Server::get($job);
47
-		}
48
-		$job->setArgument($argument);
49
-		$job->setId(Server::get(IGenerator::class)->nextId());
50
-		if (!$this->has($job, null)) {
51
-			$this->jobs[] = $job;
52
-		}
53
-	}
54
-
55
-	public function scheduleAfter(string $job, int $runAfter, $argument = null): void {
56
-		$this->add($job, $argument, $runAfter);
57
-	}
58
-
59
-	/**
60
-	 * @param IJob|string $job
61
-	 * @param mixed $argument
62
-	 */
63
-	public function remove($job, $argument = null): void {
64
-		foreach ($this->jobs as $index => $listJob) {
65
-			if (get_class($job) === get_class($listJob) && $job->getArgument() == $listJob->getArgument()) {
66
-				unset($this->jobs[$index]);
67
-				return;
68
-			}
69
-		}
70
-	}
71
-
72
-	public function removeById(string $id): void {
73
-		foreach ($this->jobs as $index => $listJob) {
74
-			if ($listJob->getId() === $id) {
75
-				unset($this->jobs[$index]);
76
-				return;
77
-			}
78
-		}
79
-	}
80
-
81
-	/**
82
-	 * check if a job is in the list
83
-	 *
84
-	 * @param $job
85
-	 * @param mixed $argument
86
-	 * @return bool
87
-	 */
88
-	public function has($job, $argument): bool {
89
-		return array_search($job, $this->jobs) !== false;
90
-	}
91
-
92
-	/**
93
-	 * get all jobs in the list
94
-	 *
95
-	 * @return IJob[]
96
-	 */
97
-	public function getAll(): array {
98
-		return $this->jobs;
99
-	}
100
-
101
-	public function getJobsIterator($job, ?int $limit, int $offset): iterable {
102
-		if ($job instanceof IJob) {
103
-			$jobClass = get_class($job);
104
-		} else {
105
-			$jobClass = $job;
106
-		}
107
-
108
-		$jobs = array_slice(
109
-			array_filter(
110
-				$this->jobs,
111
-				fn ($job) => ($jobClass === null) || (get_class($job) === $jobClass)
112
-			),
113
-			$offset,
114
-			$limit
115
-		);
116
-
117
-		return new ArrayIterator($jobs);
118
-	}
119
-
120
-	/**
121
-	 * get the next job in the list
122
-	 */
123
-	public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob {
124
-		if (count($this->jobs) > 0) {
125
-			if ($this->last < (count($this->jobs) - 1)) {
126
-				$i = $this->last + 1;
127
-			} else {
128
-				$i = 0;
129
-			}
130
-			return $this->jobs[$i];
131
-		} else {
132
-			return null;
133
-		}
134
-	}
135
-
136
-	/**
137
-	 * set the job that was last ran
138
-	 *
139
-	 * @param Job $job
140
-	 */
141
-	public function setLastJob(IJob $job): void {
142
-		$i = array_search($job, $this->jobs);
143
-		if ($i !== false) {
144
-			$this->last = $i;
145
-		} else {
146
-			$this->last = 0;
147
-		}
148
-	}
149
-
150
-	public function getById(string $id): ?IJob {
151
-		foreach ($this->jobs as $job) {
152
-			if ($job->getId() === $id) {
153
-				return $job;
154
-			}
155
-		}
156
-		return null;
157
-	}
158
-
159
-	public function getDetailsById(string $id): ?array {
160
-		return null;
161
-	}
162
-
163
-	public function setLastRun(IJob $job): void {
164
-		$job->setLastRun(time());
165
-	}
166
-
167
-	public function hasReservedJob(?string $className = null): bool {
168
-		return isset($this->reserved[$className ?? '']) && $this->reserved[$className ?? ''];
169
-	}
170
-
171
-	public function setHasReservedJob(?string $className, bool $hasReserved): void {
172
-		$this->reserved[$className ?? ''] = $hasReserved;
173
-	}
174
-
175
-	public function setExecutionTime(IJob $job, $timeTaken): void {
176
-	}
177
-
178
-	public function resetBackgroundJob(IJob $job): void {
179
-	}
24
+    /**
25
+     * @var IJob[]
26
+     */
27
+    private array $jobs = [];
28
+
29
+    /**
30
+     * @var bool[]
31
+     */
32
+    private array $reserved = [];
33
+
34
+    private int $last = 0;
35
+
36
+    public function __construct() {
37
+    }
38
+
39
+    /**
40
+     * @param IJob|class-string<IJob> $job
41
+     * @param mixed $argument
42
+     */
43
+    public function add($job, $argument = null, ?int $firstCheck = null): void {
44
+        if (is_string($job)) {
45
+            /** @var IJob $job */
46
+            $job = Server::get($job);
47
+        }
48
+        $job->setArgument($argument);
49
+        $job->setId(Server::get(IGenerator::class)->nextId());
50
+        if (!$this->has($job, null)) {
51
+            $this->jobs[] = $job;
52
+        }
53
+    }
54
+
55
+    public function scheduleAfter(string $job, int $runAfter, $argument = null): void {
56
+        $this->add($job, $argument, $runAfter);
57
+    }
58
+
59
+    /**
60
+     * @param IJob|string $job
61
+     * @param mixed $argument
62
+     */
63
+    public function remove($job, $argument = null): void {
64
+        foreach ($this->jobs as $index => $listJob) {
65
+            if (get_class($job) === get_class($listJob) && $job->getArgument() == $listJob->getArgument()) {
66
+                unset($this->jobs[$index]);
67
+                return;
68
+            }
69
+        }
70
+    }
71
+
72
+    public function removeById(string $id): void {
73
+        foreach ($this->jobs as $index => $listJob) {
74
+            if ($listJob->getId() === $id) {
75
+                unset($this->jobs[$index]);
76
+                return;
77
+            }
78
+        }
79
+    }
80
+
81
+    /**
82
+     * check if a job is in the list
83
+     *
84
+     * @param $job
85
+     * @param mixed $argument
86
+     * @return bool
87
+     */
88
+    public function has($job, $argument): bool {
89
+        return array_search($job, $this->jobs) !== false;
90
+    }
91
+
92
+    /**
93
+     * get all jobs in the list
94
+     *
95
+     * @return IJob[]
96
+     */
97
+    public function getAll(): array {
98
+        return $this->jobs;
99
+    }
100
+
101
+    public function getJobsIterator($job, ?int $limit, int $offset): iterable {
102
+        if ($job instanceof IJob) {
103
+            $jobClass = get_class($job);
104
+        } else {
105
+            $jobClass = $job;
106
+        }
107
+
108
+        $jobs = array_slice(
109
+            array_filter(
110
+                $this->jobs,
111
+                fn ($job) => ($jobClass === null) || (get_class($job) === $jobClass)
112
+            ),
113
+            $offset,
114
+            $limit
115
+        );
116
+
117
+        return new ArrayIterator($jobs);
118
+    }
119
+
120
+    /**
121
+     * get the next job in the list
122
+     */
123
+    public function getNext(bool $onlyTimeSensitive = false, ?array $jobClasses = null): ?IJob {
124
+        if (count($this->jobs) > 0) {
125
+            if ($this->last < (count($this->jobs) - 1)) {
126
+                $i = $this->last + 1;
127
+            } else {
128
+                $i = 0;
129
+            }
130
+            return $this->jobs[$i];
131
+        } else {
132
+            return null;
133
+        }
134
+    }
135
+
136
+    /**
137
+     * set the job that was last ran
138
+     *
139
+     * @param Job $job
140
+     */
141
+    public function setLastJob(IJob $job): void {
142
+        $i = array_search($job, $this->jobs);
143
+        if ($i !== false) {
144
+            $this->last = $i;
145
+        } else {
146
+            $this->last = 0;
147
+        }
148
+    }
149
+
150
+    public function getById(string $id): ?IJob {
151
+        foreach ($this->jobs as $job) {
152
+            if ($job->getId() === $id) {
153
+                return $job;
154
+            }
155
+        }
156
+        return null;
157
+    }
158
+
159
+    public function getDetailsById(string $id): ?array {
160
+        return null;
161
+    }
162
+
163
+    public function setLastRun(IJob $job): void {
164
+        $job->setLastRun(time());
165
+    }
166
+
167
+    public function hasReservedJob(?string $className = null): bool {
168
+        return isset($this->reserved[$className ?? '']) && $this->reserved[$className ?? ''];
169
+    }
170
+
171
+    public function setHasReservedJob(?string $className, bool $hasReserved): void {
172
+        $this->reserved[$className ?? ''] = $hasReserved;
173
+    }
174
+
175
+    public function setExecutionTime(IJob $job, $timeTaken): void {
176
+    }
177
+
178
+    public function resetBackgroundJob(IJob $job): void {
179
+    }
180 180
 }
Please login to merge, or discard this patch.
tests/lib/BackgroundJob/JobListTest.php 1 patch
Indentation   +271 added lines, -271 removed lines patch added patch discarded remove patch
@@ -29,290 +29,290 @@
 block discarded – undo
29 29
  */
30 30
 #[\PHPUnit\Framework\Attributes\Group('DB')]
31 31
 class JobListTest extends TestCase {
32
-	protected JobList $instance;
33
-	protected IDBConnection $connection;
34
-	protected IConfig&MockObject $config;
35
-	protected ITimeFactory&MockObject $timeFactory;
36
-
37
-	protected function setUp(): void {
38
-		parent::setUp();
39
-
40
-		$this->connection = Server::get(IDBConnection::class);
41
-		$this->clearJobsList();
42
-		$this->config = $this->createMock(IConfig::class);
43
-		$this->timeFactory = $this->createMock(ITimeFactory::class);
44
-		$this->instance = new JobList(
45
-			$this->connection,
46
-			$this->config,
47
-			$this->timeFactory,
48
-			Server::get(LoggerInterface::class),
49
-			Server::get(IGenerator::class),
50
-		);
51
-	}
52
-
53
-	protected function clearJobsList(): void {
54
-		$query = $this->connection->getQueryBuilder();
55
-		$query->delete('jobs');
56
-		$query->executeStatement();
57
-	}
58
-
59
-	protected function getAllSorted(): array {
60
-		$iterator = $this->instance->getJobsIterator(null, null, 0);
61
-		$jobs = [];
62
-
63
-		foreach ($iterator as $job) {
64
-			$jobs[] = clone $job;
65
-		}
66
-
67
-		return $jobs;
68
-	}
69
-
70
-	public static function argumentProvider(): array {
71
-		return [
72
-			[null],
73
-			[false],
74
-			['foobar'],
75
-			[12],
76
-			[[
77
-				'asd' => 5,
78
-				'foo' => 'bar'
79
-			]]
80
-		];
81
-	}
82
-
83
-	#[DataProvider('argumentProvider')]
84
-	public function testAddRemove(mixed $argument): void {
85
-		$existingJobs = $this->getAllSorted();
86
-		$job = new TestJob();
87
-		$this->instance->add($job, $argument);
88
-
89
-		$jobs = $this->getAllSorted();
90
-
91
-		$this->assertCount(count($existingJobs) + 1, $jobs);
92
-		$addedJob = $jobs[count($jobs) - 1];
93
-		$this->assertInstanceOf('\Test\BackgroundJob\TestJob', $addedJob);
94
-		$this->assertEquals($argument, $addedJob->getArgument());
95
-
96
-		$this->instance->remove($job, $argument);
97
-
98
-		$jobs = $this->getAllSorted();
99
-		$this->assertEquals($existingJobs, $jobs);
100
-	}
101
-
102
-	#[DataProvider('argumentProvider')]
103
-	public function testRemoveDifferentArgument(mixed $argument): void {
104
-		$existingJobs = $this->getAllSorted();
105
-		$job = new TestJob();
106
-		$this->instance->add($job, $argument);
107
-
108
-		$jobs = $this->getAllSorted();
109
-		$this->instance->remove($job, 10);
110
-		$jobs2 = $this->getAllSorted();
111
-
112
-		$this->assertEquals($jobs, $jobs2);
113
-
114
-		$this->instance->remove($job, $argument);
115
-
116
-		$jobs = $this->getAllSorted();
117
-		$this->assertEquals($existingJobs, $jobs);
118
-	}
119
-
120
-	#[DataProvider('argumentProvider')]
121
-	public function testHas(mixed $argument): void {
122
-		$job = new TestJob();
123
-		$this->assertFalse($this->instance->has($job, $argument));
124
-		$this->instance->add($job, $argument);
125
-
126
-		$this->assertTrue($this->instance->has($job, $argument));
127
-
128
-		$this->instance->remove($job, $argument);
129
-
130
-		$this->assertFalse($this->instance->has($job, $argument));
131
-	}
132
-
133
-	#[DataProvider('argumentProvider')]
134
-	public function testHasDifferentArgument(mixed $argument): void {
135
-		$job = new TestJob();
136
-		$this->instance->add($job, $argument);
137
-
138
-		$this->assertFalse($this->instance->has($job, 10));
139
-	}
140
-
141
-	protected function createTempJob($class,
142
-		$argument,
143
-		int $reservedTime = 0,
144
-		int $lastChecked = 0,
145
-		int $lastRun = 0): string {
146
-		if ($lastChecked === 0) {
147
-			$lastChecked = time();
148
-		}
149
-		$id = Server::get(IGenerator::class)->nextId();
150
-
151
-		$query = $this->connection->getQueryBuilder();
152
-		$query->insert('jobs')
153
-			->values([
154
-				'id' => $query->createNamedParameter($id, IQueryBuilder::PARAM_INT),
155
-				'class' => $query->createNamedParameter($class),
156
-				'argument' => $query->createNamedParameter($argument),
157
-				'last_run' => $query->createNamedParameter($lastRun, IQueryBuilder::PARAM_INT),
158
-				'last_checked' => $query->createNamedParameter($lastChecked, IQueryBuilder::PARAM_INT),
159
-				'reserved_at' => $query->createNamedParameter($reservedTime, IQueryBuilder::PARAM_INT),
160
-			]);
161
-		$query->executeStatement();
162
-		return $id;
163
-	}
164
-
165
-	public function testGetNext(): void {
166
-		$job = new TestJob();
167
-		$this->createTempJob(get_class($job), 1, 0, 12345);
168
-		$this->createTempJob(get_class($job), 2, 0, 12346);
169
-
170
-		$jobs = $this->getAllSorted();
171
-		$savedJob1 = $jobs[0];
172
-
173
-		$this->timeFactory->expects($this->atLeastOnce())
174
-			->method('getTime')
175
-			->willReturn(123456789);
176
-		$nextJob = $this->instance->getNext();
177
-
178
-		$this->assertEquals($savedJob1, $nextJob);
179
-	}
180
-
181
-	public function testGetNextSkipReserved(): void {
182
-		$job = new TestJob();
183
-		$this->createTempJob(get_class($job), 1, 123456789, 12345);
184
-		$this->createTempJob(get_class($job), 2, 0, 12346);
185
-
186
-		$this->timeFactory->expects($this->atLeastOnce())
187
-			->method('getTime')
188
-			->willReturn(123456789);
189
-		$nextJob = $this->instance->getNext();
190
-
191
-		$this->assertEquals(get_class($job), get_class($nextJob));
192
-		$this->assertEquals(2, $nextJob->getArgument());
193
-	}
194
-
195
-	public function testGetNextSkipTimed(): void {
196
-		$job = new TestTimedJobNew($this->timeFactory);
197
-		$jobId = $this->createTempJob(get_class($job), 1, 123456789, 12345, 123456789 - 5);
198
-		$this->timeFactory->expects(self::atLeastOnce())
199
-			->method('getTime')
200
-			->willReturn(123456789);
201
-
202
-		$nextJob = $this->instance->getNext();
203
-
204
-		self::assertNull($nextJob);
205
-		$job = $this->instance->getById($jobId);
206
-		self::assertInstanceOf(TestTimedJobNew::class, $job);
207
-		self::assertEquals(123456789 - 5, $job->getLastRun());
208
-	}
209
-
210
-	public function testGetNextSkipNonExisting(): void {
211
-		$job = new TestJob();
212
-		$this->createTempJob('\OC\Non\Existing\Class', 1, 0, 12345);
213
-		$this->createTempJob(get_class($job), 2, 0, 12346);
214
-
215
-		$this->timeFactory->expects($this->atLeastOnce())
216
-			->method('getTime')
217
-			->willReturn(123456789);
218
-		$nextJob = $this->instance->getNext();
219
-
220
-		$this->assertEquals(get_class($job), get_class($nextJob));
221
-		$this->assertEquals(2, $nextJob->getArgument());
222
-	}
223
-
224
-	/**
225
-	 * @param $argument
226
-	 */
227
-	#[DataProvider('argumentProvider')]
228
-	public function testGetById($argument): void {
229
-		$job = new TestJob();
230
-		$this->instance->add($job, $argument);
231
-
232
-		$jobs = $this->getAllSorted();
233
-
234
-		$addedJob = $jobs[count($jobs) - 1];
235
-
236
-		$this->assertEquals($addedJob, $this->instance->getById($addedJob->getId()));
237
-	}
238
-
239
-	public function testSetLastRun(): void {
240
-		$job = new TestJob();
241
-		$this->instance->add($job);
242
-
243
-		$jobs = $this->getAllSorted();
244
-
245
-		$addedJob = $jobs[count($jobs) - 1];
246
-
247
-		$timeStart = time();
248
-		$this->instance->setLastRun($addedJob);
249
-		$timeEnd = time();
250
-
251
-		$addedJob = $this->instance->getById($addedJob->getId());
252
-
253
-		$this->assertGreaterThanOrEqual($timeStart, $addedJob->getLastRun());
254
-		$this->assertLessThanOrEqual($timeEnd, $addedJob->getLastRun());
255
-	}
256
-
257
-	public function testHasReservedJobs(): void {
258
-		$this->clearJobsList();
32
+    protected JobList $instance;
33
+    protected IDBConnection $connection;
34
+    protected IConfig&MockObject $config;
35
+    protected ITimeFactory&MockObject $timeFactory;
36
+
37
+    protected function setUp(): void {
38
+        parent::setUp();
39
+
40
+        $this->connection = Server::get(IDBConnection::class);
41
+        $this->clearJobsList();
42
+        $this->config = $this->createMock(IConfig::class);
43
+        $this->timeFactory = $this->createMock(ITimeFactory::class);
44
+        $this->instance = new JobList(
45
+            $this->connection,
46
+            $this->config,
47
+            $this->timeFactory,
48
+            Server::get(LoggerInterface::class),
49
+            Server::get(IGenerator::class),
50
+        );
51
+    }
52
+
53
+    protected function clearJobsList(): void {
54
+        $query = $this->connection->getQueryBuilder();
55
+        $query->delete('jobs');
56
+        $query->executeStatement();
57
+    }
58
+
59
+    protected function getAllSorted(): array {
60
+        $iterator = $this->instance->getJobsIterator(null, null, 0);
61
+        $jobs = [];
62
+
63
+        foreach ($iterator as $job) {
64
+            $jobs[] = clone $job;
65
+        }
66
+
67
+        return $jobs;
68
+    }
69
+
70
+    public static function argumentProvider(): array {
71
+        return [
72
+            [null],
73
+            [false],
74
+            ['foobar'],
75
+            [12],
76
+            [[
77
+                'asd' => 5,
78
+                'foo' => 'bar'
79
+            ]]
80
+        ];
81
+    }
82
+
83
+    #[DataProvider('argumentProvider')]
84
+    public function testAddRemove(mixed $argument): void {
85
+        $existingJobs = $this->getAllSorted();
86
+        $job = new TestJob();
87
+        $this->instance->add($job, $argument);
88
+
89
+        $jobs = $this->getAllSorted();
90
+
91
+        $this->assertCount(count($existingJobs) + 1, $jobs);
92
+        $addedJob = $jobs[count($jobs) - 1];
93
+        $this->assertInstanceOf('\Test\BackgroundJob\TestJob', $addedJob);
94
+        $this->assertEquals($argument, $addedJob->getArgument());
95
+
96
+        $this->instance->remove($job, $argument);
97
+
98
+        $jobs = $this->getAllSorted();
99
+        $this->assertEquals($existingJobs, $jobs);
100
+    }
101
+
102
+    #[DataProvider('argumentProvider')]
103
+    public function testRemoveDifferentArgument(mixed $argument): void {
104
+        $existingJobs = $this->getAllSorted();
105
+        $job = new TestJob();
106
+        $this->instance->add($job, $argument);
107
+
108
+        $jobs = $this->getAllSorted();
109
+        $this->instance->remove($job, 10);
110
+        $jobs2 = $this->getAllSorted();
111
+
112
+        $this->assertEquals($jobs, $jobs2);
113
+
114
+        $this->instance->remove($job, $argument);
115
+
116
+        $jobs = $this->getAllSorted();
117
+        $this->assertEquals($existingJobs, $jobs);
118
+    }
119
+
120
+    #[DataProvider('argumentProvider')]
121
+    public function testHas(mixed $argument): void {
122
+        $job = new TestJob();
123
+        $this->assertFalse($this->instance->has($job, $argument));
124
+        $this->instance->add($job, $argument);
125
+
126
+        $this->assertTrue($this->instance->has($job, $argument));
127
+
128
+        $this->instance->remove($job, $argument);
129
+
130
+        $this->assertFalse($this->instance->has($job, $argument));
131
+    }
132
+
133
+    #[DataProvider('argumentProvider')]
134
+    public function testHasDifferentArgument(mixed $argument): void {
135
+        $job = new TestJob();
136
+        $this->instance->add($job, $argument);
137
+
138
+        $this->assertFalse($this->instance->has($job, 10));
139
+    }
140
+
141
+    protected function createTempJob($class,
142
+        $argument,
143
+        int $reservedTime = 0,
144
+        int $lastChecked = 0,
145
+        int $lastRun = 0): string {
146
+        if ($lastChecked === 0) {
147
+            $lastChecked = time();
148
+        }
149
+        $id = Server::get(IGenerator::class)->nextId();
150
+
151
+        $query = $this->connection->getQueryBuilder();
152
+        $query->insert('jobs')
153
+            ->values([
154
+                'id' => $query->createNamedParameter($id, IQueryBuilder::PARAM_INT),
155
+                'class' => $query->createNamedParameter($class),
156
+                'argument' => $query->createNamedParameter($argument),
157
+                'last_run' => $query->createNamedParameter($lastRun, IQueryBuilder::PARAM_INT),
158
+                'last_checked' => $query->createNamedParameter($lastChecked, IQueryBuilder::PARAM_INT),
159
+                'reserved_at' => $query->createNamedParameter($reservedTime, IQueryBuilder::PARAM_INT),
160
+            ]);
161
+        $query->executeStatement();
162
+        return $id;
163
+    }
164
+
165
+    public function testGetNext(): void {
166
+        $job = new TestJob();
167
+        $this->createTempJob(get_class($job), 1, 0, 12345);
168
+        $this->createTempJob(get_class($job), 2, 0, 12346);
169
+
170
+        $jobs = $this->getAllSorted();
171
+        $savedJob1 = $jobs[0];
172
+
173
+        $this->timeFactory->expects($this->atLeastOnce())
174
+            ->method('getTime')
175
+            ->willReturn(123456789);
176
+        $nextJob = $this->instance->getNext();
177
+
178
+        $this->assertEquals($savedJob1, $nextJob);
179
+    }
180
+
181
+    public function testGetNextSkipReserved(): void {
182
+        $job = new TestJob();
183
+        $this->createTempJob(get_class($job), 1, 123456789, 12345);
184
+        $this->createTempJob(get_class($job), 2, 0, 12346);
185
+
186
+        $this->timeFactory->expects($this->atLeastOnce())
187
+            ->method('getTime')
188
+            ->willReturn(123456789);
189
+        $nextJob = $this->instance->getNext();
190
+
191
+        $this->assertEquals(get_class($job), get_class($nextJob));
192
+        $this->assertEquals(2, $nextJob->getArgument());
193
+    }
194
+
195
+    public function testGetNextSkipTimed(): void {
196
+        $job = new TestTimedJobNew($this->timeFactory);
197
+        $jobId = $this->createTempJob(get_class($job), 1, 123456789, 12345, 123456789 - 5);
198
+        $this->timeFactory->expects(self::atLeastOnce())
199
+            ->method('getTime')
200
+            ->willReturn(123456789);
201
+
202
+        $nextJob = $this->instance->getNext();
203
+
204
+        self::assertNull($nextJob);
205
+        $job = $this->instance->getById($jobId);
206
+        self::assertInstanceOf(TestTimedJobNew::class, $job);
207
+        self::assertEquals(123456789 - 5, $job->getLastRun());
208
+    }
209
+
210
+    public function testGetNextSkipNonExisting(): void {
211
+        $job = new TestJob();
212
+        $this->createTempJob('\OC\Non\Existing\Class', 1, 0, 12345);
213
+        $this->createTempJob(get_class($job), 2, 0, 12346);
214
+
215
+        $this->timeFactory->expects($this->atLeastOnce())
216
+            ->method('getTime')
217
+            ->willReturn(123456789);
218
+        $nextJob = $this->instance->getNext();
219
+
220
+        $this->assertEquals(get_class($job), get_class($nextJob));
221
+        $this->assertEquals(2, $nextJob->getArgument());
222
+    }
223
+
224
+    /**
225
+     * @param $argument
226
+     */
227
+    #[DataProvider('argumentProvider')]
228
+    public function testGetById($argument): void {
229
+        $job = new TestJob();
230
+        $this->instance->add($job, $argument);
231
+
232
+        $jobs = $this->getAllSorted();
233
+
234
+        $addedJob = $jobs[count($jobs) - 1];
235
+
236
+        $this->assertEquals($addedJob, $this->instance->getById($addedJob->getId()));
237
+    }
238
+
239
+    public function testSetLastRun(): void {
240
+        $job = new TestJob();
241
+        $this->instance->add($job);
242
+
243
+        $jobs = $this->getAllSorted();
244
+
245
+        $addedJob = $jobs[count($jobs) - 1];
246
+
247
+        $timeStart = time();
248
+        $this->instance->setLastRun($addedJob);
249
+        $timeEnd = time();
250
+
251
+        $addedJob = $this->instance->getById($addedJob->getId());
252
+
253
+        $this->assertGreaterThanOrEqual($timeStart, $addedJob->getLastRun());
254
+        $this->assertLessThanOrEqual($timeEnd, $addedJob->getLastRun());
255
+    }
256
+
257
+    public function testHasReservedJobs(): void {
258
+        $this->clearJobsList();
259 259
 
260
-		$this->timeFactory->expects($this->atLeastOnce())
261
-			->method('getTime')
262
-			->willReturn(123456789);
260
+        $this->timeFactory->expects($this->atLeastOnce())
261
+            ->method('getTime')
262
+            ->willReturn(123456789);
263 263
 
264
-		$job = new TestJob($this->timeFactory, $this, function (): void {
265
-		});
264
+        $job = new TestJob($this->timeFactory, $this, function (): void {
265
+        });
266 266
 
267
-		$job2 = new TestJob($this->timeFactory, $this, function (): void {
268
-		});
267
+        $job2 = new TestJob($this->timeFactory, $this, function (): void {
268
+        });
269 269
 
270
-		$this->instance->add($job, 1);
271
-		$this->instance->add($job2, 2);
270
+        $this->instance->add($job, 1);
271
+        $this->instance->add($job2, 2);
272 272
 
273
-		$this->assertCount(2, iterator_to_array($this->instance->getJobsIterator(null, 10, 0)));
273
+        $this->assertCount(2, iterator_to_array($this->instance->getJobsIterator(null, 10, 0)));
274 274
 
275
-		$this->assertFalse($this->instance->hasReservedJob());
276
-		$this->assertFalse($this->instance->hasReservedJob(TestJob::class));
275
+        $this->assertFalse($this->instance->hasReservedJob());
276
+        $this->assertFalse($this->instance->hasReservedJob(TestJob::class));
277 277
 
278
-		$job = $this->instance->getNext();
279
-		$this->assertNotNull($job);
280
-		$this->assertTrue($this->instance->hasReservedJob());
281
-		$this->assertTrue($this->instance->hasReservedJob(TestJob::class));
282
-		$job = $this->instance->getNext();
283
-		$this->assertNotNull($job);
284
-		$this->assertTrue($this->instance->hasReservedJob());
285
-		$this->assertTrue($this->instance->hasReservedJob(TestJob::class));
286
-	}
278
+        $job = $this->instance->getNext();
279
+        $this->assertNotNull($job);
280
+        $this->assertTrue($this->instance->hasReservedJob());
281
+        $this->assertTrue($this->instance->hasReservedJob(TestJob::class));
282
+        $job = $this->instance->getNext();
283
+        $this->assertNotNull($job);
284
+        $this->assertTrue($this->instance->hasReservedJob());
285
+        $this->assertTrue($this->instance->hasReservedJob(TestJob::class));
286
+    }
287 287
 
288
-	public function testHasReservedJobsAndParallelAwareJob(): void {
289
-		$this->clearJobsList();
288
+    public function testHasReservedJobsAndParallelAwareJob(): void {
289
+        $this->clearJobsList();
290 290
 
291
-		$this->timeFactory->expects($this->atLeastOnce())
292
-			->method('getTime')
293
-			->willReturnCallback(function () use (&$time) {
294
-				return time();
295
-			});
291
+        $this->timeFactory->expects($this->atLeastOnce())
292
+            ->method('getTime')
293
+            ->willReturnCallback(function () use (&$time) {
294
+                return time();
295
+            });
296 296
 
297
-		$job = new TestParallelAwareJob($this->timeFactory, $this, function (): void {
298
-		});
297
+        $job = new TestParallelAwareJob($this->timeFactory, $this, function (): void {
298
+        });
299 299
 
300
-		$job2 = new TestParallelAwareJob($this->timeFactory, $this, function (): void {
301
-		});
300
+        $job2 = new TestParallelAwareJob($this->timeFactory, $this, function (): void {
301
+        });
302 302
 
303
-		$this->instance->add($job, 1);
304
-		$this->instance->add($job2, 2);
303
+        $this->instance->add($job, 1);
304
+        $this->instance->add($job2, 2);
305 305
 
306
-		$this->assertCount(2, iterator_to_array($this->instance->getJobsIterator(null, 10, 0)));
306
+        $this->assertCount(2, iterator_to_array($this->instance->getJobsIterator(null, 10, 0)));
307 307
 
308
-		$this->assertFalse($this->instance->hasReservedJob());
309
-		$this->assertFalse($this->instance->hasReservedJob(TestParallelAwareJob::class));
308
+        $this->assertFalse($this->instance->hasReservedJob());
309
+        $this->assertFalse($this->instance->hasReservedJob(TestParallelAwareJob::class));
310 310
 
311
-		$job = $this->instance->getNext();
312
-		$this->assertNotNull($job);
313
-		$this->assertTrue($this->instance->hasReservedJob());
314
-		$this->assertTrue($this->instance->hasReservedJob(TestParallelAwareJob::class));
315
-		$job = $this->instance->getNext();
316
-		$this->assertNull($job); // Job doesn't allow parallel runs
317
-	}
311
+        $job = $this->instance->getNext();
312
+        $this->assertNotNull($job);
313
+        $this->assertTrue($this->instance->hasReservedJob());
314
+        $this->assertTrue($this->instance->hasReservedJob(TestParallelAwareJob::class));
315
+        $job = $this->instance->getNext();
316
+        $this->assertNull($job); // Job doesn't allow parallel runs
317
+    }
318 318
 }
Please login to merge, or discard this patch.
core/Migrations/Version33000Date20251124110529.php 1 patch
Indentation   +11 added lines, -11 removed lines patch added patch discarded remove patch
@@ -17,17 +17,17 @@
 block discarded – undo
17 17
 
18 18
 #[ModifyColumn(table: 'jobs', name: 'id', description: 'Remove auto-increment')]
19 19
 class Version33000Date20251124110529 extends SimpleMigrationStep {
20
-	/**
21
-	 * @param Closure(): ISchemaWrapper $schemaClosure The `\Closure` returns a `ISchemaWrapper`
22
-	 */
23
-	#[Override]
24
-	public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
25
-		$schema = $schemaClosure();
20
+    /**
21
+     * @param Closure(): ISchemaWrapper $schemaClosure The `\Closure` returns a `ISchemaWrapper`
22
+     */
23
+    #[Override]
24
+    public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
25
+        $schema = $schemaClosure();
26 26
 
27
-		if ($schema->hasTable('jobs')) {
28
-			$schema->dropAutoincrementColumn('jobs', 'id');
29
-		}
27
+        if ($schema->hasTable('jobs')) {
28
+            $schema->dropAutoincrementColumn('jobs', 'id');
29
+        }
30 30
 
31
-		return $schema;
32
-	}
31
+        return $schema;
32
+    }
33 33
 }
Please login to merge, or discard this patch.
core/Command/Background/Job.php 1 patch
Indentation   +116 added lines, -116 removed lines patch added patch discarded remove patch
@@ -19,120 +19,120 @@
 block discarded – undo
19 19
 use Symfony\Component\Console\Output\OutputInterface;
20 20
 
21 21
 class Job extends Command {
22
-	public function __construct(
23
-		protected IJobList $jobList,
24
-	) {
25
-		parent::__construct();
26
-	}
27
-
28
-	protected function configure(): void {
29
-		$this
30
-			->setName('background-job:execute')
31
-			->setDescription('Execute a single background job manually')
32
-			->addArgument(
33
-				'job-id',
34
-				InputArgument::REQUIRED,
35
-				'The ID of the job in the database'
36
-			)
37
-			->addOption(
38
-				'force-execute',
39
-				null,
40
-				InputOption::VALUE_NONE,
41
-				'Force execute the background job, independent from last run and being reserved'
42
-			)
43
-		;
44
-	}
45
-
46
-	protected function execute(InputInterface $input, OutputInterface $output): int {
47
-		$jobId = (string)$input->getArgument('job-id');
48
-
49
-		$job = $this->jobList->getById($jobId);
50
-		if ($job === null) {
51
-			$output->writeln('<error>Job with ID ' . $jobId . ' could not be found in the database</error>');
52
-			return 1;
53
-		}
54
-
55
-		$this->printJobInfo($jobId, $job, $output);
56
-		$output->writeln('');
57
-
58
-		$lastRun = $job->getLastRun();
59
-		if ($input->getOption('force-execute')) {
60
-			$lastRun = 0;
61
-			$output->writeln('<comment>Forcing execution of the job</comment>');
62
-			$output->writeln('');
63
-
64
-			$this->jobList->resetBackgroundJob($job);
65
-		}
66
-
67
-		$job = $this->jobList->getById($jobId);
68
-		if ($job === null) {
69
-			$output->writeln('<error>Something went wrong when trying to retrieve Job with ID ' . $jobId . ' from database</error>');
70
-			return 1;
71
-		}
72
-		$job->start($this->jobList);
73
-		$job = $this->jobList->getById($jobId);
74
-
75
-		if (($job === null) || ($lastRun !== $job->getLastRun())) {
76
-			$output->writeln('<info>Job executed!</info>');
77
-			$output->writeln('');
78
-
79
-			if ($job instanceof TimedJob) {
80
-				$this->printJobInfo($jobId, $job, $output);
81
-			}
82
-		} else {
83
-			$output->writeln('<comment>Job was not executed because it is not due</comment>');
84
-			$output->writeln('Specify the <question>--force-execute</question> option to run it anyway');
85
-		}
86
-
87
-		return 0;
88
-	}
89
-
90
-	protected function printJobInfo(string $jobId, IJob $job, OutputInterface $output): void {
91
-		$row = $this->jobList->getDetailsById($jobId);
92
-
93
-		$lastRun = new \DateTime();
94
-		$lastRun->setTimestamp((int)$row['last_run']);
95
-		$lastChecked = new \DateTime();
96
-		$lastChecked->setTimestamp((int)$row['last_checked']);
97
-		$reservedAt = new \DateTime();
98
-		$reservedAt->setTimestamp((int)$row['reserved_at']);
99
-
100
-		$output->writeln('Job class:            ' . get_class($job));
101
-		$output->writeln('Arguments:            ' . json_encode($job->getArgument()));
102
-
103
-		$isTimedJob = $job instanceof TimedJob;
104
-		if ($isTimedJob) {
105
-			$output->writeln('Type:                 timed');
106
-		} elseif ($job instanceof QueuedJob) {
107
-			$output->writeln('Type:                 queued');
108
-		} else {
109
-			$output->writeln('Type:                 job');
110
-		}
111
-
112
-		$output->writeln('');
113
-		$output->writeln('Last checked:         ' . $lastChecked->format(\DateTimeInterface::ATOM));
114
-		if ((int)$row['reserved_at'] === 0) {
115
-			$output->writeln('Reserved at:          -');
116
-		} else {
117
-			$output->writeln('Reserved at:          <comment>' . $reservedAt->format(\DateTimeInterface::ATOM) . '</comment>');
118
-		}
119
-		$output->writeln('Last executed:        ' . $lastRun->format(\DateTimeInterface::ATOM));
120
-		$output->writeln('Last duration:        ' . $row['execution_duration']);
121
-
122
-		if ($isTimedJob) {
123
-			$reflection = new \ReflectionClass($job);
124
-			$intervalProperty = $reflection->getProperty('interval');
125
-			$intervalProperty->setAccessible(true);
126
-			$interval = $intervalProperty->getValue($job);
127
-
128
-			$nextRun = new \DateTime();
129
-			$nextRun->setTimestamp($row['last_run'] + $interval);
130
-
131
-			if ($nextRun > new \DateTime()) {
132
-				$output->writeln('Next execution:       <comment>' . $nextRun->format(\DateTimeInterface::ATOM) . '</comment>');
133
-			} else {
134
-				$output->writeln('Next execution:       <info>' . $nextRun->format(\DateTimeInterface::ATOM) . '</info>');
135
-			}
136
-		}
137
-	}
22
+    public function __construct(
23
+        protected IJobList $jobList,
24
+    ) {
25
+        parent::__construct();
26
+    }
27
+
28
+    protected function configure(): void {
29
+        $this
30
+            ->setName('background-job:execute')
31
+            ->setDescription('Execute a single background job manually')
32
+            ->addArgument(
33
+                'job-id',
34
+                InputArgument::REQUIRED,
35
+                'The ID of the job in the database'
36
+            )
37
+            ->addOption(
38
+                'force-execute',
39
+                null,
40
+                InputOption::VALUE_NONE,
41
+                'Force execute the background job, independent from last run and being reserved'
42
+            )
43
+        ;
44
+    }
45
+
46
+    protected function execute(InputInterface $input, OutputInterface $output): int {
47
+        $jobId = (string)$input->getArgument('job-id');
48
+
49
+        $job = $this->jobList->getById($jobId);
50
+        if ($job === null) {
51
+            $output->writeln('<error>Job with ID ' . $jobId . ' could not be found in the database</error>');
52
+            return 1;
53
+        }
54
+
55
+        $this->printJobInfo($jobId, $job, $output);
56
+        $output->writeln('');
57
+
58
+        $lastRun = $job->getLastRun();
59
+        if ($input->getOption('force-execute')) {
60
+            $lastRun = 0;
61
+            $output->writeln('<comment>Forcing execution of the job</comment>');
62
+            $output->writeln('');
63
+
64
+            $this->jobList->resetBackgroundJob($job);
65
+        }
66
+
67
+        $job = $this->jobList->getById($jobId);
68
+        if ($job === null) {
69
+            $output->writeln('<error>Something went wrong when trying to retrieve Job with ID ' . $jobId . ' from database</error>');
70
+            return 1;
71
+        }
72
+        $job->start($this->jobList);
73
+        $job = $this->jobList->getById($jobId);
74
+
75
+        if (($job === null) || ($lastRun !== $job->getLastRun())) {
76
+            $output->writeln('<info>Job executed!</info>');
77
+            $output->writeln('');
78
+
79
+            if ($job instanceof TimedJob) {
80
+                $this->printJobInfo($jobId, $job, $output);
81
+            }
82
+        } else {
83
+            $output->writeln('<comment>Job was not executed because it is not due</comment>');
84
+            $output->writeln('Specify the <question>--force-execute</question> option to run it anyway');
85
+        }
86
+
87
+        return 0;
88
+    }
89
+
90
+    protected function printJobInfo(string $jobId, IJob $job, OutputInterface $output): void {
91
+        $row = $this->jobList->getDetailsById($jobId);
92
+
93
+        $lastRun = new \DateTime();
94
+        $lastRun->setTimestamp((int)$row['last_run']);
95
+        $lastChecked = new \DateTime();
96
+        $lastChecked->setTimestamp((int)$row['last_checked']);
97
+        $reservedAt = new \DateTime();
98
+        $reservedAt->setTimestamp((int)$row['reserved_at']);
99
+
100
+        $output->writeln('Job class:            ' . get_class($job));
101
+        $output->writeln('Arguments:            ' . json_encode($job->getArgument()));
102
+
103
+        $isTimedJob = $job instanceof TimedJob;
104
+        if ($isTimedJob) {
105
+            $output->writeln('Type:                 timed');
106
+        } elseif ($job instanceof QueuedJob) {
107
+            $output->writeln('Type:                 queued');
108
+        } else {
109
+            $output->writeln('Type:                 job');
110
+        }
111
+
112
+        $output->writeln('');
113
+        $output->writeln('Last checked:         ' . $lastChecked->format(\DateTimeInterface::ATOM));
114
+        if ((int)$row['reserved_at'] === 0) {
115
+            $output->writeln('Reserved at:          -');
116
+        } else {
117
+            $output->writeln('Reserved at:          <comment>' . $reservedAt->format(\DateTimeInterface::ATOM) . '</comment>');
118
+        }
119
+        $output->writeln('Last executed:        ' . $lastRun->format(\DateTimeInterface::ATOM));
120
+        $output->writeln('Last duration:        ' . $row['execution_duration']);
121
+
122
+        if ($isTimedJob) {
123
+            $reflection = new \ReflectionClass($job);
124
+            $intervalProperty = $reflection->getProperty('interval');
125
+            $intervalProperty->setAccessible(true);
126
+            $interval = $intervalProperty->getValue($job);
127
+
128
+            $nextRun = new \DateTime();
129
+            $nextRun->setTimestamp($row['last_run'] + $interval);
130
+
131
+            if ($nextRun > new \DateTime()) {
132
+                $output->writeln('Next execution:       <comment>' . $nextRun->format(\DateTimeInterface::ATOM) . '</comment>');
133
+            } else {
134
+                $output->writeln('Next execution:       <info>' . $nextRun->format(\DateTimeInterface::ATOM) . '</info>');
135
+            }
136
+        }
137
+    }
138 138
 }
Please login to merge, or discard this patch.