Passed
Push — master ( 5e090d...dbfe7b )
by Blizzz
16:19 queued 13s
created
lib/public/BackgroundJob/IJobList.php 1 patch
Indentation   +85 added lines, -85 removed lines patch added patch discarded remove patch
@@ -48,101 +48,101 @@
 block discarded – undo
48 48
  * @since 7.0.0
49 49
  */
50 50
 interface IJobList {
51
-	/**
52
-	 * Add a job to the list
53
-	 *
54
-	 * @param IJob|class-string<IJob> $job
55
-	 * @param mixed $argument The argument to be passed to $job->run() when the job is exectured
56
-	 * @since 7.0.0
57
-	 */
58
-	public function add($job, $argument = null): void;
51
+    /**
52
+     * Add a job to the list
53
+     *
54
+     * @param IJob|class-string<IJob> $job
55
+     * @param mixed $argument The argument to be passed to $job->run() when the job is exectured
56
+     * @since 7.0.0
57
+     */
58
+    public function add($job, $argument = null): void;
59 59
 
60
-	/**
61
-	 * Remove a job from the list
62
-	 *
63
-	 * @param IJob|class-string<IJob> $job
64
-	 * @param mixed $argument
65
-	 * @since 7.0.0
66
-	 */
67
-	public function remove($job, $argument = null): void;
60
+    /**
61
+     * Remove a job from the list
62
+     *
63
+     * @param IJob|class-string<IJob> $job
64
+     * @param mixed $argument
65
+     * @since 7.0.0
66
+     */
67
+    public function remove($job, $argument = null): void;
68 68
 
69
-	/**
70
-	 * check if a job is in the list
71
-	 *
72
-	 * @param IJob|class-string<IJob> $job
73
-	 * @param mixed $argument
74
-	 * @since 7.0.0
75
-	 */
76
-	public function has($job, $argument): bool;
69
+    /**
70
+     * check if a job is in the list
71
+     *
72
+     * @param IJob|class-string<IJob> $job
73
+     * @param mixed $argument
74
+     * @since 7.0.0
75
+     */
76
+    public function has($job, $argument): bool;
77 77
 
78
-	/**
79
-	 * Get jobs matching the search
80
-	 *
81
-	 * @param IJob|class-string<IJob>|null $job
82
-	 * @return array<IJob>
83
-	 * @since 25.0.0
84
-	 * @deprecated 26.0.0 Use getJobsIterator instead to avoid duplicated job objects
85
-	 */
86
-	public function getJobs($job, ?int $limit, int $offset): array;
78
+    /**
79
+     * Get jobs matching the search
80
+     *
81
+     * @param IJob|class-string<IJob>|null $job
82
+     * @return array<IJob>
83
+     * @since 25.0.0
84
+     * @deprecated 26.0.0 Use getJobsIterator instead to avoid duplicated job objects
85
+     */
86
+    public function getJobs($job, ?int $limit, int $offset): array;
87 87
 
88
-	/**
89
-	 * Get jobs matching the search
90
-	 *
91
-	 * @param IJob|class-string<IJob>|null $job
92
-	 * @return iterable<IJob>
93
-	 * @since 26.0.0
94
-	 */
95
-	public function getJobsIterator($job, ?int $limit, int $offset): iterable;
88
+    /**
89
+     * Get jobs matching the search
90
+     *
91
+     * @param IJob|class-string<IJob>|null $job
92
+     * @return iterable<IJob>
93
+     * @since 26.0.0
94
+     */
95
+    public function getJobsIterator($job, ?int $limit, int $offset): iterable;
96 96
 
97
-	/**
98
-	 * get the next job in the list
99
-	 *
100
-	 * @since 7.0.0 - In 24.0.0 parameter $onlyTimeSensitive got added
101
-	 */
102
-	public function getNext(bool $onlyTimeSensitive = false): ?IJob;
97
+    /**
98
+     * get the next job in the list
99
+     *
100
+     * @since 7.0.0 - In 24.0.0 parameter $onlyTimeSensitive got added
101
+     */
102
+    public function getNext(bool $onlyTimeSensitive = false): ?IJob;
103 103
 
104
-	/**
105
-	 * @since 7.0.0
106
-	 */
107
-	public function getById(int $id): ?IJob;
104
+    /**
105
+     * @since 7.0.0
106
+     */
107
+    public function getById(int $id): ?IJob;
108 108
 
109
-	/**
110
-	 * @since 23.0.0
111
-	 */
112
-	public function getDetailsById(int $id): ?array;
109
+    /**
110
+     * @since 23.0.0
111
+     */
112
+    public function getDetailsById(int $id): ?array;
113 113
 
114
-	/**
115
-	 * set the job that was last ran to the current time
116
-	 *
117
-	 * @since 7.0.0
118
-	 */
119
-	public function setLastJob(IJob $job): void;
114
+    /**
115
+     * set the job that was last ran to the current time
116
+     *
117
+     * @since 7.0.0
118
+     */
119
+    public function setLastJob(IJob $job): void;
120 120
 
121
-	/**
122
-	 * Remove the reservation for a job
123
-	 *
124
-	 * @since 9.1.0
125
-	 */
126
-	public function unlockJob(IJob $job): void;
121
+    /**
122
+     * Remove the reservation for a job
123
+     *
124
+     * @since 9.1.0
125
+     */
126
+    public function unlockJob(IJob $job): void;
127 127
 
128
-	/**
129
-	 * set the lastRun of $job to now
130
-	 *
131
-	 * @since 7.0.0
132
-	 */
133
-	public function setLastRun(IJob $job): void;
128
+    /**
129
+     * set the lastRun of $job to now
130
+     *
131
+     * @since 7.0.0
132
+     */
133
+    public function setLastRun(IJob $job): void;
134 134
 
135
-	/**
136
-	 * set the run duration of $job
137
-	 *
138
-	 * @since 12.0.0
139
-	 */
140
-	public function setExecutionTime(IJob $job, int $timeTaken): void;
135
+    /**
136
+     * set the run duration of $job
137
+     *
138
+     * @since 12.0.0
139
+     */
140
+    public function setExecutionTime(IJob $job, int $timeTaken): void;
141 141
 
142
-	/**
143
-	 * Reset the $job so it executes on the next trigger
144
-	 *
145
-	 * @since 23.0.0
146
-	 */
147
-	public function resetBackgroundJob(IJob $job): void;
142
+    /**
143
+     * Reset the $job so it executes on the next trigger
144
+     *
145
+     * @since 23.0.0
146
+     */
147
+    public function resetBackgroundJob(IJob $job): void;
148 148
 }
Please login to merge, or discard this patch.
lib/private/BackgroundJob/JobList.php 1 patch
Indentation   +342 added lines, -342 removed lines patch added patch discarded remove patch
@@ -40,346 +40,346 @@
 block discarded – undo
40 40
 use OCP\IDBConnection;
41 41
 
42 42
 class JobList implements IJobList {
43
-	protected IDBConnection $connection;
44
-	protected IConfig $config;
45
-	protected ITimeFactory $timeFactory;
46
-
47
-	public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
48
-		$this->connection = $connection;
49
-		$this->config = $config;
50
-		$this->timeFactory = $timeFactory;
51
-	}
52
-
53
-	/**
54
-	 * @param IJob|class-string<IJob> $job
55
-	 * @param mixed $argument
56
-	 */
57
-	public function add($job, $argument = null): void {
58
-		if ($job instanceof IJob) {
59
-			$class = get_class($job);
60
-		} else {
61
-			$class = $job;
62
-		}
63
-
64
-		$argumentJson = json_encode($argument);
65
-		if (strlen($argumentJson) > 4000) {
66
-			throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
67
-		}
68
-
69
-		$query = $this->connection->getQueryBuilder();
70
-		if (!$this->has($job, $argument)) {
71
-			$query->insert('jobs')
72
-				->values([
73
-					'class' => $query->createNamedParameter($class),
74
-					'argument' => $query->createNamedParameter($argumentJson),
75
-					'argument_hash' => $query->createNamedParameter(md5($argumentJson)),
76
-					'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
77
-					'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
78
-				]);
79
-		} else {
80
-			$query->update('jobs')
81
-				->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
82
-				->set('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT))
83
-				->where($query->expr()->eq('class', $query->createNamedParameter($class)))
84
-				->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson))));
85
-		}
86
-		$query->executeStatement();
87
-	}
88
-
89
-	/**
90
-	 * @param IJob|string $job
91
-	 * @param mixed $argument
92
-	 */
93
-	public function remove($job, $argument = null): void {
94
-		if ($job instanceof IJob) {
95
-			$class = get_class($job);
96
-		} else {
97
-			$class = $job;
98
-		}
99
-
100
-		$query = $this->connection->getQueryBuilder();
101
-		$query->delete('jobs')
102
-			->where($query->expr()->eq('class', $query->createNamedParameter($class)));
103
-		if (!is_null($argument)) {
104
-			$argumentJson = json_encode($argument);
105
-			$query->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson))));
106
-		}
107
-
108
-		// Add galera safe delete chunking if using mysql
109
-		// Stops us hitting wsrep_max_ws_rows when large row counts are deleted
110
-		if ($this->connection->getDatabasePlatform() instanceof MySQLPlatform) {
111
-			// Then use chunked delete
112
-			$max = IQueryBuilder::MAX_ROW_DELETION;
113
-
114
-			$query->setMaxResults($max);
115
-
116
-			do {
117
-				$deleted = $query->execute();
118
-			} while ($deleted === $max);
119
-		} else {
120
-			// Dont use chunked delete - let the DB handle the large row count natively
121
-			$query->execute();
122
-		}
123
-	}
124
-
125
-	protected function removeById(int $id): void {
126
-		$query = $this->connection->getQueryBuilder();
127
-		$query->delete('jobs')
128
-			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
129
-		$query->executeStatement();
130
-	}
131
-
132
-	/**
133
-	 * check if a job is in the list
134
-	 *
135
-	 * @param IJob|class-string<IJob> $job
136
-	 * @param mixed $argument
137
-	 */
138
-	public function has($job, $argument): bool {
139
-		if ($job instanceof IJob) {
140
-			$class = get_class($job);
141
-		} else {
142
-			$class = $job;
143
-		}
144
-		$argument = json_encode($argument);
145
-
146
-		$query = $this->connection->getQueryBuilder();
147
-		$query->select('id')
148
-			->from('jobs')
149
-			->where($query->expr()->eq('class', $query->createNamedParameter($class)))
150
-			->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argument))))
151
-			->setMaxResults(1);
152
-
153
-		$result = $query->executeQuery();
154
-		$row = $result->fetch();
155
-		$result->closeCursor();
156
-
157
-		return (bool) $row;
158
-	}
159
-
160
-	public function getJobs($job, ?int $limit, int $offset): array {
161
-		$iterable = $this->getJobsIterator($job, $limit, $offset);
162
-		if (is_array($iterable)) {
163
-			return $iterable;
164
-		} else {
165
-			return iterator_to_array($iterable);
166
-		}
167
-	}
168
-
169
-	/**
170
-	 * @param IJob|class-string<IJob>|null $job
171
-	 * @return iterable<IJob> Avoid to store these objects as they may share a Singleton instance. You should instead use these IJobs instances while looping on the iterable.
172
-	 */
173
-	public function getJobsIterator($job, ?int $limit, int $offset): iterable {
174
-		$query = $this->connection->getQueryBuilder();
175
-		$query->select('*')
176
-			->from('jobs')
177
-			->setMaxResults($limit)
178
-			->setFirstResult($offset);
179
-
180
-		if ($job !== null) {
181
-			if ($job instanceof IJob) {
182
-				$class = get_class($job);
183
-			} else {
184
-				$class = $job;
185
-			}
186
-			$query->where($query->expr()->eq('class', $query->createNamedParameter($class)));
187
-		}
188
-
189
-		$result = $query->executeQuery();
190
-
191
-		while ($row = $result->fetch()) {
192
-			$job = $this->buildJob($row);
193
-			if ($job) {
194
-				yield $job;
195
-			}
196
-		}
197
-		$result->closeCursor();
198
-	}
199
-
200
-	/**
201
-	 * Get the next job in the list
202
-	 * @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.
203
-	 */
204
-	public function getNext(bool $onlyTimeSensitive = false): ?IJob {
205
-		$query = $this->connection->getQueryBuilder();
206
-		$query->select('*')
207
-			->from('jobs')
208
-			->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
209
-			->andWhere($query->expr()->lte('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
210
-			->orderBy('last_checked', 'ASC')
211
-			->setMaxResults(1);
212
-
213
-		if ($onlyTimeSensitive) {
214
-			$query->andWhere($query->expr()->eq('time_sensitive', $query->createNamedParameter(IJob::TIME_SENSITIVE, IQueryBuilder::PARAM_INT)));
215
-		}
216
-
217
-		$update = $this->connection->getQueryBuilder();
218
-		$update->update('jobs')
219
-			->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
220
-			->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
221
-			->where($update->expr()->eq('id', $update->createParameter('jobid')))
222
-			->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
223
-			->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
224
-
225
-		$result = $query->executeQuery();
226
-		$row = $result->fetch();
227
-		$result->closeCursor();
228
-
229
-		if ($row) {
230
-			$update->setParameter('jobid', $row['id']);
231
-			$update->setParameter('reserved_at', $row['reserved_at']);
232
-			$update->setParameter('last_checked', $row['last_checked']);
233
-			$count = $update->executeStatement();
234
-
235
-			if ($count === 0) {
236
-				// Background job already executed elsewhere, try again.
237
-				return $this->getNext($onlyTimeSensitive);
238
-			}
239
-			$job = $this->buildJob($row);
240
-
241
-			if ($job === null) {
242
-				// set the last_checked to 12h in the future to not check failing jobs all over again
243
-				$reset = $this->connection->getQueryBuilder();
244
-				$reset->update('jobs')
245
-					->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT))
246
-					->set('last_checked', $reset->createNamedParameter($this->timeFactory->getTime() + 12 * 3600, IQueryBuilder::PARAM_INT))
247
-					->where($reset->expr()->eq('id', $reset->createNamedParameter($row['id'], IQueryBuilder::PARAM_INT)));
248
-				$reset->executeStatement();
249
-
250
-				// Background job from disabled app, try again.
251
-				return $this->getNext($onlyTimeSensitive);
252
-			}
253
-
254
-			return $job;
255
-		} else {
256
-			return null;
257
-		}
258
-	}
259
-
260
-	/**
261
-	 * @return ?IJob The job matching the id. Beware that this object may be a singleton and may be modified by the next call to buildJob.
262
-	 */
263
-	public function getById(int $id): ?IJob {
264
-		$row = $this->getDetailsById($id);
265
-
266
-		if ($row) {
267
-			return $this->buildJob($row);
268
-		}
269
-
270
-		return null;
271
-	}
272
-
273
-	public function getDetailsById(int $id): ?array {
274
-		$query = $this->connection->getQueryBuilder();
275
-		$query->select('*')
276
-			->from('jobs')
277
-			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
278
-		$result = $query->executeQuery();
279
-		$row = $result->fetch();
280
-		$result->closeCursor();
281
-
282
-		if ($row) {
283
-			return $row;
284
-		}
285
-
286
-		return null;
287
-	}
288
-
289
-	/**
290
-	 * get the job object from a row in the db
291
-	 *
292
-	 * @param array{class:class-string<IJob>, id:mixed, last_run:mixed, argument:string} $row
293
-	 * @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.
294
-	 */
295
-	private function buildJob(array $row): ?IJob {
296
-		try {
297
-			try {
298
-				// Try to load the job as a service
299
-				/** @var IJob $job */
300
-				$job = \OCP\Server::get($row['class']);
301
-			} catch (QueryException $e) {
302
-				if (class_exists($row['class'])) {
303
-					$class = $row['class'];
304
-					$job = new $class();
305
-				} else {
306
-					// job from disabled app or old version of an app, no need to do anything
307
-					return null;
308
-				}
309
-			}
310
-
311
-			if (!($job instanceof IJob)) {
312
-				// This most likely means an invalid job was enqueued. We can ignore it.
313
-				return null;
314
-			}
315
-			$job->setId((int) $row['id']);
316
-			$job->setLastRun((int) $row['last_run']);
317
-			$job->setArgument(json_decode($row['argument'], true));
318
-			return $job;
319
-		} catch (AutoloadNotAllowedException $e) {
320
-			// job is from a disabled app, ignore
321
-			return null;
322
-		}
323
-	}
324
-
325
-	/**
326
-	 * set the job that was last ran
327
-	 */
328
-	public function setLastJob(IJob $job): void {
329
-		$this->unlockJob($job);
330
-		$this->config->setAppValue('backgroundjob', 'lastjob', (string)$job->getId());
331
-	}
332
-
333
-	/**
334
-	 * Remove the reservation for a job
335
-	 */
336
-	public function unlockJob(IJob $job): void {
337
-		$query = $this->connection->getQueryBuilder();
338
-		$query->update('jobs')
339
-			->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
340
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
341
-		$query->executeStatement();
342
-	}
343
-
344
-	/**
345
-	 * set the lastRun of $job to now
346
-	 */
347
-	public function setLastRun(IJob $job): void {
348
-		$query = $this->connection->getQueryBuilder();
349
-		$query->update('jobs')
350
-			->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
351
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
352
-
353
-		if ($job instanceof \OCP\BackgroundJob\TimedJob
354
-			&& !$job->isTimeSensitive()) {
355
-			$query->set('time_sensitive', $query->createNamedParameter(IJob::TIME_INSENSITIVE));
356
-		}
357
-
358
-		$query->executeStatement();
359
-	}
360
-
361
-	/**
362
-	 * @param int $timeTaken
363
-	 */
364
-	public function setExecutionTime(IJob $job, $timeTaken): void {
365
-		$query = $this->connection->getQueryBuilder();
366
-		$query->update('jobs')
367
-			->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
368
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
369
-		$query->executeStatement();
370
-	}
371
-
372
-	/**
373
-	 * Reset the $job so it executes on the next trigger
374
-	 *
375
-	 * @since 23.0.0
376
-	 */
377
-	public function resetBackgroundJob(IJob $job): void {
378
-		$query = $this->connection->getQueryBuilder();
379
-		$query->update('jobs')
380
-			->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
381
-			->set('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
382
-			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId()), IQueryBuilder::PARAM_INT));
383
-		$query->executeStatement();
384
-	}
43
+    protected IDBConnection $connection;
44
+    protected IConfig $config;
45
+    protected ITimeFactory $timeFactory;
46
+
47
+    public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
48
+        $this->connection = $connection;
49
+        $this->config = $config;
50
+        $this->timeFactory = $timeFactory;
51
+    }
52
+
53
+    /**
54
+     * @param IJob|class-string<IJob> $job
55
+     * @param mixed $argument
56
+     */
57
+    public function add($job, $argument = null): void {
58
+        if ($job instanceof IJob) {
59
+            $class = get_class($job);
60
+        } else {
61
+            $class = $job;
62
+        }
63
+
64
+        $argumentJson = json_encode($argument);
65
+        if (strlen($argumentJson) > 4000) {
66
+            throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
67
+        }
68
+
69
+        $query = $this->connection->getQueryBuilder();
70
+        if (!$this->has($job, $argument)) {
71
+            $query->insert('jobs')
72
+                ->values([
73
+                    'class' => $query->createNamedParameter($class),
74
+                    'argument' => $query->createNamedParameter($argumentJson),
75
+                    'argument_hash' => $query->createNamedParameter(md5($argumentJson)),
76
+                    'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
77
+                    'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
78
+                ]);
79
+        } else {
80
+            $query->update('jobs')
81
+                ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
82
+                ->set('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT))
83
+                ->where($query->expr()->eq('class', $query->createNamedParameter($class)))
84
+                ->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson))));
85
+        }
86
+        $query->executeStatement();
87
+    }
88
+
89
+    /**
90
+     * @param IJob|string $job
91
+     * @param mixed $argument
92
+     */
93
+    public function remove($job, $argument = null): void {
94
+        if ($job instanceof IJob) {
95
+            $class = get_class($job);
96
+        } else {
97
+            $class = $job;
98
+        }
99
+
100
+        $query = $this->connection->getQueryBuilder();
101
+        $query->delete('jobs')
102
+            ->where($query->expr()->eq('class', $query->createNamedParameter($class)));
103
+        if (!is_null($argument)) {
104
+            $argumentJson = json_encode($argument);
105
+            $query->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argumentJson))));
106
+        }
107
+
108
+        // Add galera safe delete chunking if using mysql
109
+        // Stops us hitting wsrep_max_ws_rows when large row counts are deleted
110
+        if ($this->connection->getDatabasePlatform() instanceof MySQLPlatform) {
111
+            // Then use chunked delete
112
+            $max = IQueryBuilder::MAX_ROW_DELETION;
113
+
114
+            $query->setMaxResults($max);
115
+
116
+            do {
117
+                $deleted = $query->execute();
118
+            } while ($deleted === $max);
119
+        } else {
120
+            // Dont use chunked delete - let the DB handle the large row count natively
121
+            $query->execute();
122
+        }
123
+    }
124
+
125
+    protected function removeById(int $id): void {
126
+        $query = $this->connection->getQueryBuilder();
127
+        $query->delete('jobs')
128
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
129
+        $query->executeStatement();
130
+    }
131
+
132
+    /**
133
+     * check if a job is in the list
134
+     *
135
+     * @param IJob|class-string<IJob> $job
136
+     * @param mixed $argument
137
+     */
138
+    public function has($job, $argument): bool {
139
+        if ($job instanceof IJob) {
140
+            $class = get_class($job);
141
+        } else {
142
+            $class = $job;
143
+        }
144
+        $argument = json_encode($argument);
145
+
146
+        $query = $this->connection->getQueryBuilder();
147
+        $query->select('id')
148
+            ->from('jobs')
149
+            ->where($query->expr()->eq('class', $query->createNamedParameter($class)))
150
+            ->andWhere($query->expr()->eq('argument_hash', $query->createNamedParameter(md5($argument))))
151
+            ->setMaxResults(1);
152
+
153
+        $result = $query->executeQuery();
154
+        $row = $result->fetch();
155
+        $result->closeCursor();
156
+
157
+        return (bool) $row;
158
+    }
159
+
160
+    public function getJobs($job, ?int $limit, int $offset): array {
161
+        $iterable = $this->getJobsIterator($job, $limit, $offset);
162
+        if (is_array($iterable)) {
163
+            return $iterable;
164
+        } else {
165
+            return iterator_to_array($iterable);
166
+        }
167
+    }
168
+
169
+    /**
170
+     * @param IJob|class-string<IJob>|null $job
171
+     * @return iterable<IJob> Avoid to store these objects as they may share a Singleton instance. You should instead use these IJobs instances while looping on the iterable.
172
+     */
173
+    public function getJobsIterator($job, ?int $limit, int $offset): iterable {
174
+        $query = $this->connection->getQueryBuilder();
175
+        $query->select('*')
176
+            ->from('jobs')
177
+            ->setMaxResults($limit)
178
+            ->setFirstResult($offset);
179
+
180
+        if ($job !== null) {
181
+            if ($job instanceof IJob) {
182
+                $class = get_class($job);
183
+            } else {
184
+                $class = $job;
185
+            }
186
+            $query->where($query->expr()->eq('class', $query->createNamedParameter($class)));
187
+        }
188
+
189
+        $result = $query->executeQuery();
190
+
191
+        while ($row = $result->fetch()) {
192
+            $job = $this->buildJob($row);
193
+            if ($job) {
194
+                yield $job;
195
+            }
196
+        }
197
+        $result->closeCursor();
198
+    }
199
+
200
+    /**
201
+     * Get the next job in the list
202
+     * @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.
203
+     */
204
+    public function getNext(bool $onlyTimeSensitive = false): ?IJob {
205
+        $query = $this->connection->getQueryBuilder();
206
+        $query->select('*')
207
+            ->from('jobs')
208
+            ->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
209
+            ->andWhere($query->expr()->lte('last_checked', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
210
+            ->orderBy('last_checked', 'ASC')
211
+            ->setMaxResults(1);
212
+
213
+        if ($onlyTimeSensitive) {
214
+            $query->andWhere($query->expr()->eq('time_sensitive', $query->createNamedParameter(IJob::TIME_SENSITIVE, IQueryBuilder::PARAM_INT)));
215
+        }
216
+
217
+        $update = $this->connection->getQueryBuilder();
218
+        $update->update('jobs')
219
+            ->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
220
+            ->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
221
+            ->where($update->expr()->eq('id', $update->createParameter('jobid')))
222
+            ->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
223
+            ->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
224
+
225
+        $result = $query->executeQuery();
226
+        $row = $result->fetch();
227
+        $result->closeCursor();
228
+
229
+        if ($row) {
230
+            $update->setParameter('jobid', $row['id']);
231
+            $update->setParameter('reserved_at', $row['reserved_at']);
232
+            $update->setParameter('last_checked', $row['last_checked']);
233
+            $count = $update->executeStatement();
234
+
235
+            if ($count === 0) {
236
+                // Background job already executed elsewhere, try again.
237
+                return $this->getNext($onlyTimeSensitive);
238
+            }
239
+            $job = $this->buildJob($row);
240
+
241
+            if ($job === null) {
242
+                // set the last_checked to 12h in the future to not check failing jobs all over again
243
+                $reset = $this->connection->getQueryBuilder();
244
+                $reset->update('jobs')
245
+                    ->set('reserved_at', $reset->expr()->literal(0, IQueryBuilder::PARAM_INT))
246
+                    ->set('last_checked', $reset->createNamedParameter($this->timeFactory->getTime() + 12 * 3600, IQueryBuilder::PARAM_INT))
247
+                    ->where($reset->expr()->eq('id', $reset->createNamedParameter($row['id'], IQueryBuilder::PARAM_INT)));
248
+                $reset->executeStatement();
249
+
250
+                // Background job from disabled app, try again.
251
+                return $this->getNext($onlyTimeSensitive);
252
+            }
253
+
254
+            return $job;
255
+        } else {
256
+            return null;
257
+        }
258
+    }
259
+
260
+    /**
261
+     * @return ?IJob The job matching the id. Beware that this object may be a singleton and may be modified by the next call to buildJob.
262
+     */
263
+    public function getById(int $id): ?IJob {
264
+        $row = $this->getDetailsById($id);
265
+
266
+        if ($row) {
267
+            return $this->buildJob($row);
268
+        }
269
+
270
+        return null;
271
+    }
272
+
273
+    public function getDetailsById(int $id): ?array {
274
+        $query = $this->connection->getQueryBuilder();
275
+        $query->select('*')
276
+            ->from('jobs')
277
+            ->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
278
+        $result = $query->executeQuery();
279
+        $row = $result->fetch();
280
+        $result->closeCursor();
281
+
282
+        if ($row) {
283
+            return $row;
284
+        }
285
+
286
+        return null;
287
+    }
288
+
289
+    /**
290
+     * get the job object from a row in the db
291
+     *
292
+     * @param array{class:class-string<IJob>, id:mixed, last_run:mixed, argument:string} $row
293
+     * @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.
294
+     */
295
+    private function buildJob(array $row): ?IJob {
296
+        try {
297
+            try {
298
+                // Try to load the job as a service
299
+                /** @var IJob $job */
300
+                $job = \OCP\Server::get($row['class']);
301
+            } catch (QueryException $e) {
302
+                if (class_exists($row['class'])) {
303
+                    $class = $row['class'];
304
+                    $job = new $class();
305
+                } else {
306
+                    // job from disabled app or old version of an app, no need to do anything
307
+                    return null;
308
+                }
309
+            }
310
+
311
+            if (!($job instanceof IJob)) {
312
+                // This most likely means an invalid job was enqueued. We can ignore it.
313
+                return null;
314
+            }
315
+            $job->setId((int) $row['id']);
316
+            $job->setLastRun((int) $row['last_run']);
317
+            $job->setArgument(json_decode($row['argument'], true));
318
+            return $job;
319
+        } catch (AutoloadNotAllowedException $e) {
320
+            // job is from a disabled app, ignore
321
+            return null;
322
+        }
323
+    }
324
+
325
+    /**
326
+     * set the job that was last ran
327
+     */
328
+    public function setLastJob(IJob $job): void {
329
+        $this->unlockJob($job);
330
+        $this->config->setAppValue('backgroundjob', 'lastjob', (string)$job->getId());
331
+    }
332
+
333
+    /**
334
+     * Remove the reservation for a job
335
+     */
336
+    public function unlockJob(IJob $job): void {
337
+        $query = $this->connection->getQueryBuilder();
338
+        $query->update('jobs')
339
+            ->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
340
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
341
+        $query->executeStatement();
342
+    }
343
+
344
+    /**
345
+     * set the lastRun of $job to now
346
+     */
347
+    public function setLastRun(IJob $job): void {
348
+        $query = $this->connection->getQueryBuilder();
349
+        $query->update('jobs')
350
+            ->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
351
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
352
+
353
+        if ($job instanceof \OCP\BackgroundJob\TimedJob
354
+            && !$job->isTimeSensitive()) {
355
+            $query->set('time_sensitive', $query->createNamedParameter(IJob::TIME_INSENSITIVE));
356
+        }
357
+
358
+        $query->executeStatement();
359
+    }
360
+
361
+    /**
362
+     * @param int $timeTaken
363
+     */
364
+    public function setExecutionTime(IJob $job, $timeTaken): void {
365
+        $query = $this->connection->getQueryBuilder();
366
+        $query->update('jobs')
367
+            ->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
368
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
369
+        $query->executeStatement();
370
+    }
371
+
372
+    /**
373
+     * Reset the $job so it executes on the next trigger
374
+     *
375
+     * @since 23.0.0
376
+     */
377
+    public function resetBackgroundJob(IJob $job): void {
378
+        $query = $this->connection->getQueryBuilder();
379
+        $query->update('jobs')
380
+            ->set('last_run', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
381
+            ->set('reserved_at', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT))
382
+            ->where($query->expr()->eq('id', $query->createNamedParameter($job->getId()), IQueryBuilder::PARAM_INT));
383
+        $query->executeStatement();
384
+    }
385 385
 }
Please login to merge, or discard this patch.
core/Command/Background/ListCommand.php 2 patches
Indentation   +48 added lines, -48 removed lines patch added patch discarded remove patch
@@ -32,56 +32,56 @@
 block discarded – undo
32 32
 use Symfony\Component\Console\Output\OutputInterface;
33 33
 
34 34
 class ListCommand extends Base {
35
-	protected IJobList $jobList;
35
+    protected IJobList $jobList;
36 36
 
37
-	public function __construct(IJobList $jobList) {
38
-		parent::__construct();
39
-		$this->jobList = $jobList;
40
-	}
37
+    public function __construct(IJobList $jobList) {
38
+        parent::__construct();
39
+        $this->jobList = $jobList;
40
+    }
41 41
 
42
-	protected function configure(): void {
43
-		$this
44
-			->setName('background-job:list')
45
-			->setDescription('List background jobs')
46
-			->addOption(
47
-				'class',
48
-				'c',
49
-				InputOption::VALUE_OPTIONAL,
50
-				'Job class to search for',
51
-				null
52
-			)->addOption(
53
-				'limit',
54
-				'l',
55
-				InputOption::VALUE_OPTIONAL,
56
-				'Number of jobs to retrieve',
57
-				'10'
58
-			)->addOption(
59
-				'offset',
60
-				'o',
61
-				InputOption::VALUE_OPTIONAL,
62
-				'Offset for retrieving jobs',
63
-				'0'
64
-			)
65
-		;
66
-		parent::configure();
67
-	}
42
+    protected function configure(): void {
43
+        $this
44
+            ->setName('background-job:list')
45
+            ->setDescription('List background jobs')
46
+            ->addOption(
47
+                'class',
48
+                'c',
49
+                InputOption::VALUE_OPTIONAL,
50
+                'Job class to search for',
51
+                null
52
+            )->addOption(
53
+                'limit',
54
+                'l',
55
+                InputOption::VALUE_OPTIONAL,
56
+                'Number of jobs to retrieve',
57
+                '10'
58
+            )->addOption(
59
+                'offset',
60
+                'o',
61
+                InputOption::VALUE_OPTIONAL,
62
+                'Offset for retrieving jobs',
63
+                '0'
64
+            )
65
+        ;
66
+        parent::configure();
67
+    }
68 68
 
69
-	protected function execute(InputInterface $input, OutputInterface $output): int {
70
-		$jobs = $this->jobList->getJobsIterator($input->getOption('class'), (int)$input->getOption('limit'), (int)$input->getOption('offset'));
71
-		$this->writeTableInOutputFormat($input, $output, $this->formatJobs($jobs));
72
-		return 0;
73
-	}
69
+    protected function execute(InputInterface $input, OutputInterface $output): int {
70
+        $jobs = $this->jobList->getJobsIterator($input->getOption('class'), (int)$input->getOption('limit'), (int)$input->getOption('offset'));
71
+        $this->writeTableInOutputFormat($input, $output, $this->formatJobs($jobs));
72
+        return 0;
73
+    }
74 74
 
75
-	protected function formatJobs(iterable $jobs): array {
76
-		$jobsInfo = [];
77
-		foreach ($jobs as $job) {
78
-			$jobsInfo[] = [
79
-				'id' => $job->getId(),
80
-				'class' => get_class($job),
81
-				'last_run' => date(DATE_ATOM, $job->getLastRun()),
82
-				'argument' => json_encode($job->getArgument()),
83
-			];
84
-		}
85
-		return $jobsInfo;
86
-	}
75
+    protected function formatJobs(iterable $jobs): array {
76
+        $jobsInfo = [];
77
+        foreach ($jobs as $job) {
78
+            $jobsInfo[] = [
79
+                'id' => $job->getId(),
80
+                'class' => get_class($job),
81
+                'last_run' => date(DATE_ATOM, $job->getLastRun()),
82
+                'argument' => json_encode($job->getArgument()),
83
+            ];
84
+        }
85
+        return $jobsInfo;
86
+    }
87 87
 }
Please login to merge, or discard this patch.
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -67,7 +67,7 @@
 block discarded – undo
67 67
 	}
68 68
 
69 69
 	protected function execute(InputInterface $input, OutputInterface $output): int {
70
-		$jobs = $this->jobList->getJobsIterator($input->getOption('class'), (int)$input->getOption('limit'), (int)$input->getOption('offset'));
70
+		$jobs = $this->jobList->getJobsIterator($input->getOption('class'), (int) $input->getOption('limit'), (int) $input->getOption('offset'));
71 71
 		$this->writeTableInOutputFormat($input, $output, $this->formatJobs($jobs));
72 72
 		return 0;
73 73
 	}
Please login to merge, or discard this patch.