Completed
Push — master ( d89320...9ae203 )
by Morris
21s
created

BackgroundScanner::run()   F

Complexity

Conditions 18
Paths 225

Size

Total Lines 96

Duplication

Lines 68
Ratio 70.83 %

Code Coverage

Tests 0
CRAP Score 342

Importance

Changes 0
Metric Value
dl 68
loc 96
ccs 0
cts 78
cp 0
rs 3.0639
c 0
b 0
f 0
cc 18
nc 225
nop 1
crap 342

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
/**
4
 * Copyright (c) 2012 Bart Visscher <[email protected]>
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later.
7
 * See the COPYING-README file.
8
 */
9
10
namespace OCA\Files_Antivirus\BackgroundJob;
11
12
use OC\BackgroundJob\TimedJob;
13
use OCA\Files_Antivirus\AppConfig;
14
use OCA\Files_Antivirus\ItemFactory;
15
use OCA\Files_Antivirus\Scanner\ScannerFactory;
16
use OCP\DB\QueryBuilder\IQueryBuilder;
17
use OCP\Files\File;
18
use OCP\Files\IMimeTypeLoader;
19
use OCP\IDBConnection;
20
use OCP\Files\IRootFolder;
21
use OCP\ILogger;
22
use OCP\IUser;
23
use OCP\IUserManager;
24
25
class BackgroundScanner extends TimedJob {
26
	/** @var IRootFolder */
27
	protected $rootFolder;
28
29
	/** @var ScannerFactory */
30
	private $scannerFactory;
31
32
	/** @var  AppConfig  */
33
	private $appConfig;
34
35
	/** @var ILogger */
36
	protected $logger;
37
38
	/** @var IUserManager */
39
	protected $userManager;
40
41
	/** @var IDBConnection */
42
	protected $db;
43
44
	/** @var IMimeTypeLoader */
45
	protected $mimeTypeLoader;
46
47
	/** @var ItemFactory */
48
	protected $itemFactory;
49
	/** @var bool */
50
	private $isCLI;
51
52
	public function __construct(ScannerFactory $scannerFactory,
53
								AppConfig $appConfig,
54
								IRootFolder $rootFolder,
55
								ILogger $logger,
56
								IUserManager $userManager,
57
								IDBConnection $db,
58
								IMimeTypeLoader $mimeTypeLoader,
59
								ItemFactory $itemFactory,
60
								bool $isCLI
61
	){
62
		$this->rootFolder = $rootFolder;
63
		$this->scannerFactory = $scannerFactory;
64
		$this->appConfig = $appConfig;
65
		$this->logger = $logger;
66
		$this->userManager = $userManager;
67
		$this->db = $db;
68
		$this->mimeTypeLoader = $mimeTypeLoader;
69
		$this->itemFactory = $itemFactory;
70
		$this->isCLI = $isCLI;
71
72
		// Run once per 15 minutes
73
		$this->setInterval(60 * 15);
74
	}
75
	
76
	/**
77
	 * Background scanner main job
78
	 */
79
	public function run($args): void {
80
		// locate files that are not checked yet
81
		try {
82
			$result = $this->getUnscannedFiled();
83
		} catch(\Exception $e) {
84
			$this->logger->logException($e);
85
			return;
86
		}
87
88
		$batchSize = $this->getBatchSize();
89
90
		// Run for unscanned files
91
		$cnt = 0;
92 View Code Duplication
		while (($row = $result->fetch()) && $cnt < $batchSize) {
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...
93
			try {
94
				$fileId = $row['fileid'];
95
				$users = $this->getUserWithAccessToStorage((int)$row['storage']);
96
97
				foreach ($users as $user) {
98
					/** @var IUser $owner */
99
					$owner = $this->userManager->get($user['user_id']);
100
					if (!$owner instanceof IUser){
0 ignored issues
show
Bug introduced by
The class OCP\IUser does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
101
						continue;
102
					}
103
104
					$userFolder = $this->rootFolder->getUserFolder($owner->getUID());
105
					$files = $userFolder->getById($fileId);
106
107
					if ($files === []) {
108
						continue;
109
					}
110
111
					$file = array_pop($files);
112
					if ($file instanceof File) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\File does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
113
						$this->scanOneFile($file);
114
					} else {
115
						$this->logger->error('Tried to scan non file');
116
					}
117
118
					// increased only for successfully scanned files
119
					$cnt++;
120
					break;
121
				}
122
			} catch (\Exception $e) {
123
				$this->logger->error( __METHOD__ . ', exception: ' . $e->getMessage(), ['app' => 'files_antivirus']);
124
			}
125
		}
126
127
		if ($cnt === $batchSize) {
128
			// we are done
129
			return;
130
		}
131
132
		// Run for updated files
133
		try {
134
			$result = $this->getToRescanFiles();
135
		} catch(\Exception $e) {
136
			$this->logger->logException($e);
137
			return;
138
		}
139
140 View Code Duplication
		while (($row = $result->fetch()) && $cnt < $batchSize) {
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...
141
			try {
142
				$fileId = $row['fileid'];
143
				$users = $this->getUserWithAccessToStorage((int)$row['storage']);
144
145
				foreach ($users as $user) {
146
					/** @var IUser $owner */
147
					$owner = $this->userManager->get($user);
148
					if (!$owner instanceof IUser){
0 ignored issues
show
Bug introduced by
The class OCP\IUser does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
149
						continue;
150
					}
151
152
					$userFolder = $this->rootFolder->getUserFolder($owner->getUID());
153
					$files = $userFolder->getById($fileId);
154
155
					if ($files === []) {
156
						continue;
157
					}
158
159
					$file = array_pop($files);
160
					if ($file instanceof File) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\File does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
161
						$this->scanOneFile($file);
162
					} else {
163
						$this->logger->error('Tried to scan non file');
164
					}
165
166
					// increased only for successfully scanned files
167
					$cnt++;
168
					break;
169
				}
170
			} catch (\Exception $e) {
171
				$this->logger->error( __METHOD__ . ', exception: ' . $e->getMessage(), ['app' => 'files_antivirus']);
172
			}
173
		}
174
	}
175
176
	protected function getBatchSize(): int {
177
		$batchSize = 10;
178
		if ($this->isCLI) {
179
			$batchSize = 100;
180
		}
181
		return $batchSize;
182
	}
183
184
	protected function getSizeLimitExpression(IQueryBuilder $qb)  {
185
		$sizeLimit = (int)$this->appConfig->getAvMaxFileSize();
186
		if ( $sizeLimit === -1 ){
187
			$sizeLimitExpr = $qb->expr()->neq('fc.size', $qb->expr()->literal('0'));
188
		} else {
189
			$sizeLimitExpr = $qb->expr()->andX(
190
				$qb->expr()->neq('fc.size', $qb->expr()->literal('0')),
191
				$qb->expr()->lt('fc.size', $qb->createNamedParameter($sizeLimit))
192
			);
193
		}
194
195
		return $sizeLimitExpr;
196
	}
197
198
	protected function getUserWithAccessToStorage(int $storageId): array {
199
		$qb = $this->db->getQueryBuilder();
200
201
		$qb->select('user_id')
202
			->from('mounts')
203
			->where($qb->expr()->eq('storage_id', $qb->createNamedParameter($storageId)));
204
205
		$cursor = $qb->execute();
206
		$data = $cursor->fetchAll();
207
		$cursor->closeCursor();
208
		return $data;
209
	}
210
211
	protected function getUnscannedFiled() {
212
		$dirMimeTypeId = $this->mimeTypeLoader->getId('httpd/unix-directory');
213
214
		$qb1 = $this->db->getQueryBuilder();
215
		$qb1->select('fileid')
216
			->from('files_antivirus');
217
218
		$qb2 = $this->db->getQueryBuilder();
219
		$qb2->select('fileid', 'storage')
220
			->from('filecache', 'fc')
221
			->where($qb2->expr()->notIn('fileid', $qb2->createFunction($qb1->getSQL())))
222
			->andWhere($qb2->expr()->neq('mimetype', $qb2->expr()->literal($dirMimeTypeId)))
223
			->andWhere($qb2->expr()->like('path', $qb2->expr()->literal('files/%')))
224
			->andWhere($this->getSizeLimitExpression($qb2))
225
			->setMaxResults($this->getBatchSize() * 10);
226
227
		return $qb2->execute();
228
	}
229
230
	protected function getToRescanFiles() {
231
		$qb = $this->db->getQueryBuilder();
232
		$qb->select('fc.fileid', 'fc.storage')
233
			->from('filecache', 'fc')
234
			->join('fc', 'files_antivirus', 'fa', $qb->expr()->eq('fc.fileid', 'fa.fileid'))
235
			->andWhere($qb->expr()->lt('fa.check_time', 'fc.mtime'))
236
			->andWhere($this->getSizeLimitExpression($qb))
237
			->setMaxResults($this->getBatchSize() * 10);
238
239
		return $qb->execute();
240
	}
241
242
	protected function scanOneFile(File $file): void {
243
		$item = $this->itemFactory->newItem($file);
244
		$scanner = $this->scannerFactory->getScanner();
245
		$status = $scanner->scan($item);
246
		$status->dispatch($item);
247
	}
248
}
249