Completed
Pull Request — master (#24551)
by Thomas
19:32 queued 48s
created

JobList   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 310
Duplicated Lines 13.87 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 43
loc 310
rs 9.3999
c 0
b 0
f 0
wmc 33
lcom 1
cbo 8

15 Methods

Rating   Name   Duplication   Size   Complexity  
A remove() 0 16 3
A removeById() 0 6 1
A has() 0 21 2
A __construct() 0 5 1
B add() 0 24 4
A getAll() 6 17 3
A getById() 15 15 2
B buildJob() 0 25 4
A setLastJob() 0 4 1
A unlockJob() 7 7 1
A getLastJob() 0 3 1
B getNext() 0 42 4
A setLastRun() 0 7 1
A setExecutionTime() 7 7 1
A listJobs() 8 16 4

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * @author Joas Schilling <[email protected]>
4
 * @author Morris Jobke <[email protected]>
5
 * @author Robin Appelman <[email protected]>
6
 * @author Robin McCorkell <[email protected]>
7
 *
8
 * @copyright Copyright (c) 2018, ownCloud GmbH
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OC\BackgroundJob;
26
27
use OCP\AppFramework\QueryException;
28
use OCP\AppFramework\Utility\ITimeFactory;
29
use OCP\BackgroundJob\IJob;
30
use OCP\BackgroundJob\IJobList;
31
use OCP\AutoloadNotAllowedException;
32
use OCP\DB\QueryBuilder\IQueryBuilder;
33
use OCP\IConfig;
34
use OCP\IDBConnection;
35
36
class JobList implements IJobList {
37
38
	/** @var IDBConnection */
39
	protected $connection;
40
41
	/**@var IConfig */
42
	protected $config;
43
44
	/**@var ITimeFactory */
45
	protected $timeFactory;
46
47
	/**
48
	 * @param IDBConnection $connection
49
	 * @param IConfig $config
50
	 * @param ITimeFactory $timeFactory
51
	 */
52
	public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
53
		$this->connection = $connection;
54
		$this->config = $config;
55
		$this->timeFactory = $timeFactory;
56
	}
57
58
	/**
59
	 * @param IJob|string $job
60
	 * @param mixed $argument
61
	 */
62
	public function add($job, $argument = null) {
63
		if (!$this->has($job, $argument)) {
64
			if ($job instanceof IJob) {
65
				$class = \get_class($job);
66
			} else {
67
				$class = $job;
68
			}
69
70
			$argument = \json_encode($argument);
71
			if (\strlen($argument) > 4000) {
72
				throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
73
			}
74
75
			$query = $this->connection->getQueryBuilder();
76
			$query->insert('jobs')
77
				->values([
78
					'class' => $query->createNamedParameter($class),
79
					'argument' => $query->createNamedParameter($argument),
80
					'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
81
					'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
82
				]);
83
			$query->execute();
84
		}
85
	}
86
87
	/**
88
	 * @param IJob|string $job
89
	 * @param mixed $argument
90
	 */
91
	public function remove($job, $argument = null) {
92
		if ($job instanceof IJob) {
93
			$class = \get_class($job);
94
		} else {
95
			$class = $job;
96
		}
97
98
		$query = $this->connection->getQueryBuilder();
99
		$query->delete('jobs')
100
			->where($query->expr()->eq('class', $query->createNamedParameter($class)));
101
		if ($argument !== null) {
102
			$argument = \json_encode($argument);
103
			$query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)));
104
		}
105
		$query->execute();
106
	}
107
108
	/**
109
	 * @param int $id
110
	 */
111
	public function removeById($id) {
112
		$query = $this->connection->getQueryBuilder();
113
		$query->delete('jobs')
114
			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
115
		$query->execute();
116
	}
117
118
	/**
119
	 * check if a job is in the list
120
	 *
121
	 * @param IJob|string $job
122
	 * @param mixed $argument
123
	 * @return bool
124
	 */
125
	public function has($job, $argument) {
126
		if ($job instanceof IJob) {
127
			$class = \get_class($job);
128
		} else {
129
			$class = $job;
130
		}
131
		$argument = \json_encode($argument);
132
133
		$query = $this->connection->getQueryBuilder();
134
		$query->select('id')
135
			->from('jobs')
136
			->where($query->expr()->eq('class', $query->createNamedParameter($class)))
137
			->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)))
138
			->setMaxResults(1);
139
140
		$result = $query->execute();
141
		$row = $result->fetch();
142
		$result->closeCursor();
143
144
		return (bool) $row;
145
	}
146
147
	/**
148
	 * get all jobs in the list
149
	 *
150
	 * @return IJob[]
151
	 * @deprecated 9.0.0 - This method is dangerous since it can cause load and
152
	 * memory problems when creating too many instances.
153
	 */
154
	public function getAll() {
155
		$query = $this->connection->getQueryBuilder();
156
		$query->select('*')
157
			->from('jobs');
158
		$result = $query->execute();
159
160
		$jobs = [];
161 View Code Duplication
		while ($row = $result->fetch()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
			$job = $this->buildJob($row);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $job is correct as $this->buildJob($row) (which targets OC\BackgroundJob\JobList::buildJob()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
163
			if ($job) {
164
				$jobs[] = $job;
165
			}
166
		}
167
		$result->closeCursor();
168
169
		return $jobs;
170
	}
171
172
	/**
173
	 * get the next job in the list
174
	 *
175
	 * @return IJob|null
176
	 */
177
	public function getNext() {
178
		$query = $this->connection->getQueryBuilder();
179
		$query->select('*')
180
			->from('jobs')
181
			->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
182
			->orderBy('last_checked', 'ASC')
183
			->setMaxResults(1);
184
185
		$update = $this->connection->getQueryBuilder();
186
		$update->update('jobs')
187
			->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
188
			->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
189
			->where($update->expr()->eq('id', $update->createParameter('jobid')))
190
			->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
191
			->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
192
193
		$result = $query->execute();
194
		$row = $result->fetch();
195
		$result->closeCursor();
196
197
		if ($row) {
198
			$update->setParameter('jobid', $row['id']);
199
			$update->setParameter('reserved_at', $row['reserved_at']);
200
			$update->setParameter('last_checked', $row['last_checked']);
201
			$count = $update->execute();
202
203
			if ($count === 0) {
204
				// Background job already executed elsewhere, try again.
205
				return $this->getNext();
206
			}
207
			$job = $this->buildJob($row);
208
209
			if ($job === null) {
210
				// Background job from disabled app, try again.
211
				return $this->getNext();
212
			}
213
214
			return $job;
215
		} else {
216
			return null;
217
		}
218
	}
219
220
	/**
221
	 * @param int $id
222
	 * @return IJob|null
223
	 */
224 View Code Duplication
	public function getById($id) {
225
		$query = $this->connection->getQueryBuilder();
226
		$query->select('*')
227
			->from('jobs')
228
			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
229
		$result = $query->execute();
230
		$row = $result->fetch();
231
		$result->closeCursor();
232
233
		if ($row) {
234
			return $this->buildJob($row);
235
		} else {
236
			return null;
237
		}
238
	}
239
240
	/**
241
	 * get the job object from a row in the db
242
	 *
243
	 * @param array $row
244
	 * @return IJob|null
245
	 */
246
	private function buildJob($row) {
247
		try {
248
			try {
249
				// Try to load the job as a service
250
				/** @var IJob $job */
251
				$job = \OC::$server->query($row['class']);
252
			} catch (QueryException $e) {
253
				if (\class_exists($row['class'])) {
254
					$class = $row['class'];
255
					$job = new $class();
256
				} else {
257
					// job from disabled app or old version of an app, no need to do anything
258
					return null;
259
				}
260
			}
261
262
			$job->setId($row['id']);
263
			$job->setLastRun($row['last_run']);
264
			$job->setArgument(\json_decode($row['argument'], true));
265
			return $job;
266
		} catch (AutoloadNotAllowedException $e) {
267
			// job is from a disabled app, ignore
268
			return null;
269
		}
270
	}
271
272
	/**
273
	 * set the job that was last ran
274
	 *
275
	 * @param IJob $job
276
	 */
277
	public function setLastJob($job) {
278
		$this->unlockJob($job);
279
		$this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
280
	}
281
282
	/**
283
	 * Remove the reservation for a job
284
	 *
285
	 * @param IJob $job
286
	 */
287 View Code Duplication
	public function unlockJob($job) {
288
		$query = $this->connection->getQueryBuilder();
289
		$query->update('jobs')
290
			->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
291
			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
292
		$query->execute();
293
	}
294
295
	/**
296
	 * get the id of the last ran job
297
	 *
298
	 * @return int
299
	 * @deprecated 9.1.0 - The functionality behind the value is deprecated, it
300
	 *    only tells you which job finished last, but since we now allow multiple
301
	 *    executors to run in parallel, it's not used to calculate the next job.
302
	 */
303
	public function getLastJob() {
304
		return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0);
305
	}
306
307
	/**
308
	 * @inheritdoc
309
	 */
310
	public function setLastRun($job) {
311
		$query = $this->connection->getQueryBuilder();
312
		$query->update('jobs')
313
			->set('last_run', $query->createNamedParameter(\time(), IQueryBuilder::PARAM_INT))
314
			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
315
		$query->execute();
316
	}
317
318 View Code Duplication
	public function setExecutionTime($job, $timeTaken) {
319
		$query = $this->connection->getQueryBuilder();
320
		$query->update('jobs')
321
			->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
322
			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
323
		$query->execute();
324
	}
325
326
	/**
327
	 * @inheritdoc
328
	 */
329
	public function listJobs(\Closure $callback) {
330
		$query = $this->connection->getQueryBuilder();
331
		$query->select('*')
332
			->from('jobs');
333
		$result = $query->execute();
334
335 View Code Duplication
		while ($row = $result->fetch()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
336
			$job = $this->buildJob($row);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $job is correct as $this->buildJob($row) (which targets OC\BackgroundJob\JobList::buildJob()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
337
			if ($job) {
338
				if ($callback($job) === false) {
339
					break;
340
				}
341
			}
342
		}
343
		$result->closeCursor();
344
	}
345
}
346