Completed
Push — master ( 10ec3f...4dab01 )
by Morris
15:18 queued 03:52
created

JobList   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 296
Duplicated Lines 7.43 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 22
loc 296
rs 10
c 0
b 0
f 0
wmc 29
lcom 1
cbo 8

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
B add() 0 24 4
A remove() 0 16 3
A removeById() 0 6 1
A has() 0 21 2
A getAll() 0 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() 0 7 1

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
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Joas Schilling <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Robin Appelman <[email protected]>
8
 * @author Robin McCorkell <[email protected]>
9
 *
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OC\BackgroundJob;
27
28
use OCP\AppFramework\QueryException;
29
use OCP\AppFramework\Utility\ITimeFactory;
30
use OCP\BackgroundJob\IJob;
31
use OCP\BackgroundJob\IJobList;
32
use OCP\AutoloadNotAllowedException;
33
use OCP\DB\QueryBuilder\IQueryBuilder;
34
use OCP\IConfig;
35
use OCP\IDBConnection;
36
37
class JobList implements IJobList {
38
39
	/** @var IDBConnection */
40
	protected $connection;
41
42
	/**@var IConfig */
43
	protected $config;
44
45
	/**@var ITimeFactory */
46
	protected $timeFactory;
47
48
	/**
49
	 * @param IDBConnection $connection
50
	 * @param IConfig $config
51
	 * @param ITimeFactory $timeFactory
52
	 */
53
	public function __construct(IDBConnection $connection, IConfig $config, ITimeFactory $timeFactory) {
54
		$this->connection = $connection;
55
		$this->config = $config;
56
		$this->timeFactory = $timeFactory;
57
	}
58
59
	/**
60
	 * @param IJob|string $job
61
	 * @param mixed $argument
62
	 */
63
	public function add($job, $argument = null) {
64
		if (!$this->has($job, $argument)) {
65
			if ($job instanceof IJob) {
66
				$class = get_class($job);
67
			} else {
68
				$class = $job;
69
			}
70
71
			$argument = json_encode($argument);
72
			if (strlen($argument) > 4000) {
73
				throw new \InvalidArgumentException('Background job arguments can\'t exceed 4000 characters (json encoded)');
74
			}
75
76
			$query = $this->connection->getQueryBuilder();
77
			$query->insert('jobs')
78
				->values([
79
					'class' => $query->createNamedParameter($class),
80
					'argument' => $query->createNamedParameter($argument),
81
					'last_run' => $query->createNamedParameter(0, IQueryBuilder::PARAM_INT),
82
					'last_checked' => $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT),
83
				]);
84
			$query->execute();
85
		}
86
	}
87
88
	/**
89
	 * @param IJob|string $job
90
	 * @param mixed $argument
91
	 */
92
	public function remove($job, $argument = null) {
93
		if ($job instanceof IJob) {
94
			$class = get_class($job);
95
		} else {
96
			$class = $job;
97
		}
98
99
		$query = $this->connection->getQueryBuilder();
100
		$query->delete('jobs')
101
			->where($query->expr()->eq('class', $query->createNamedParameter($class)));
102
		if (!is_null($argument)) {
103
			$argument = json_encode($argument);
104
			$query->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)));
105
		}
106
		$query->execute();
107
	}
108
109
	/**
110
	 * @param int $id
111
	 */
112
	protected function removeById($id) {
113
		$query = $this->connection->getQueryBuilder();
114
		$query->delete('jobs')
115
			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
116
		$query->execute();
117
	}
118
119
	/**
120
	 * check if a job is in the list
121
	 *
122
	 * @param IJob|string $job
123
	 * @param mixed $argument
124
	 * @return bool
125
	 */
126
	public function has($job, $argument) {
127
		if ($job instanceof IJob) {
128
			$class = get_class($job);
129
		} else {
130
			$class = $job;
131
		}
132
		$argument = json_encode($argument);
133
134
		$query = $this->connection->getQueryBuilder();
135
		$query->select('id')
136
			->from('jobs')
137
			->where($query->expr()->eq('class', $query->createNamedParameter($class)))
138
			->andWhere($query->expr()->eq('argument', $query->createNamedParameter($argument)))
139
			->setMaxResults(1);
140
141
		$result = $query->execute();
142
		$row = $result->fetch();
143
		$result->closeCursor();
144
145
		return (bool) $row;
146
	}
147
148
	/**
149
	 * get all jobs in the list
150
	 *
151
	 * @return IJob[]
152
	 * @deprecated 9.0.0 - This method is dangerous since it can cause load and
153
	 * memory problems when creating too many instances.
154
	 */
155
	public function getAll() {
156
		$query = $this->connection->getQueryBuilder();
157
		$query->select('*')
158
			->from('jobs');
159
		$result = $query->execute();
160
161
		$jobs = [];
162
		while ($row = $result->fetch()) {
163
			$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...
164
			if ($job) {
165
				$jobs[] = $job;
166
			}
167
		}
168
		$result->closeCursor();
169
170
		return $jobs;
171
	}
172
173
	/**
174
	 * get the next job in the list
175
	 *
176
	 * @return IJob|null
177
	 */
178
	public function getNext() {
179
		$query = $this->connection->getQueryBuilder();
180
		$query->select('*')
181
			->from('jobs')
182
			->where($query->expr()->lte('reserved_at', $query->createNamedParameter($this->timeFactory->getTime() - 12 * 3600, IQueryBuilder::PARAM_INT)))
183
			->orderBy('last_checked', 'ASC')
184
			->setMaxResults(1);
185
186
		$update = $this->connection->getQueryBuilder();
187
		$update->update('jobs')
188
			->set('reserved_at', $update->createNamedParameter($this->timeFactory->getTime()))
189
			->set('last_checked', $update->createNamedParameter($this->timeFactory->getTime()))
190
			->where($update->expr()->eq('id', $update->createParameter('jobid')))
191
			->andWhere($update->expr()->eq('reserved_at', $update->createParameter('reserved_at')))
192
			->andWhere($update->expr()->eq('last_checked', $update->createParameter('last_checked')));
193
194
		$result = $query->execute();
195
		$row = $result->fetch();
196
		$result->closeCursor();
197
198
		if ($row) {
199
			$update->setParameter('jobid', $row['id']);
200
			$update->setParameter('reserved_at', $row['reserved_at']);
201
			$update->setParameter('last_checked', $row['last_checked']);
202
			$count = $update->execute();
203
204
			if ($count === 0) {
205
				// Background job already executed elsewhere, try again.
206
				return $this->getNext();
207
			}
208
			$job = $this->buildJob($row);
209
210
			if ($job === null) {
211
				// Background job from disabled app, try again.
212
				return $this->getNext();
213
			}
214
215
			return $job;
216
		} else {
217
			return null;
218
		}
219
	}
220
221
	/**
222
	 * @param int $id
223
	 * @return IJob|null
224
	 */
225 View Code Duplication
	public function getById($id) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
226
		$query = $this->connection->getQueryBuilder();
227
		$query->select('*')
228
			->from('jobs')
229
			->where($query->expr()->eq('id', $query->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
230
		$result = $query->execute();
231
		$row = $result->fetch();
232
		$result->closeCursor();
233
234
		if ($row) {
235
			return $this->buildJob($row);
236
		} else {
237
			return null;
238
		}
239
	}
240
241
	/**
242
	 * get the job object from a row in the db
243
	 *
244
	 * @param array $row
245
	 * @return IJob|null
246
	 */
247
	private function buildJob($row) {
248
		try {
249
			try {
250
				// Try to load the job as a service
251
				/** @var IJob $job */
252
				$job = \OC::$server->query($row['class']);
253
			} catch (QueryException $e) {
254
				if (class_exists($row['class'])) {
255
					$class = $row['class'];
256
					$job = new $class();
257
				} else {
258
					// job from disabled app or old version of an app, no need to do anything
259
					return null;
260
				}
261
			}
262
263
			$job->setId($row['id']);
264
			$job->setLastRun($row['last_run']);
265
			$job->setArgument(json_decode($row['argument'], true));
266
			return $job;
267
		} catch (AutoloadNotAllowedException $e) {
268
			// job is from a disabled app, ignore
269
			return null;
270
		}
271
	}
272
273
	/**
274
	 * set the job that was last ran
275
	 *
276
	 * @param IJob $job
277
	 */
278
	public function setLastJob(IJob $job) {
279
		$this->unlockJob($job);
280
		$this->config->setAppValue('backgroundjob', 'lastjob', $job->getId());
281
	}
282
283
	/**
284
	 * Remove the reservation for a job
285
	 *
286
	 * @param IJob $job
287
	 */
288 View Code Duplication
	public function unlockJob(IJob $job) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
289
		$query = $this->connection->getQueryBuilder();
290
		$query->update('jobs')
291
			->set('reserved_at', $query->expr()->literal(0, IQueryBuilder::PARAM_INT))
292
			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
293
		$query->execute();
294
	}
295
296
	/**
297
	 * get the id of the last ran job
298
	 *
299
	 * @return int
300
	 * @deprecated 9.1.0 - The functionality behind the value is deprecated, it
301
	 *    only tells you which job finished last, but since we now allow multiple
302
	 *    executors to run in parallel, it's not used to calculate the next job.
303
	 */
304
	public function getLastJob() {
305
		return (int) $this->config->getAppValue('backgroundjob', 'lastjob', 0);
306
	}
307
308
	/**
309
	 * set the lastRun of $job to now
310
	 *
311
	 * @param IJob $job
312
	 */
313
	public function setLastRun(IJob $job) {
314
		$query = $this->connection->getQueryBuilder();
315
		$query->update('jobs')
316
			->set('last_run', $query->createNamedParameter(time(), IQueryBuilder::PARAM_INT))
317
			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
318
		$query->execute();
319
	}
320
321
	/**
322
	 * @param IJob $job
323
	 * @param $timeTaken
324
	 */
325
	public function setExecutionTime(IJob $job, $timeTaken) {
326
		$query = $this->connection->getQueryBuilder();
327
		$query->update('jobs')
328
			->set('execution_duration', $query->createNamedParameter($timeTaken, IQueryBuilder::PARAM_INT))
329
			->where($query->expr()->eq('id', $query->createNamedParameter($job->getId(), IQueryBuilder::PARAM_INT)));
330
		$query->execute();
331
	}
332
}
333