Completed
Push — master ( ec2cb6...3686c1 )
by Maxence
12s
created

FilesService   F

Complexity

Total Complexity 123

Size/Duplication

Total Lines 1088
Duplicated Lines 9.47 %

Coupling/Cohesion

Components 1
Dependencies 12

Importance

Changes 0
Metric Value
wmc 123
lcom 1
cbo 12
dl 103
loc 1088
rs 1.248
c 0
b 0
f 0

43 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 24 1
A setRunner() 0 3 1
A getChunksFromUser() 0 12 2
A getChunksFromDirectory() 0 21 5
A getFilesFromUser() 0 21 3
B getFilesFromDirectory() 0 40 6
A initFileSystems() 0 12 3
A generateFilesDocumentFromFile() 0 32 4
A getFileSource() 13 13 2
A getFileFromPath() 0 4 1
A getFileFromId() 0 16 3
A getFileFromIndex() 0 6 1
A getPathFromViewerId() 0 21 3
A generateDocument() 0 14 2
A generateDocumentFromIndex() 0 21 2
A isDocumentUpToDate() 0 20 4
A updateDocument() 0 9 1
A updateFilesDocument() 0 11 2
A updateFilesDocumentFromFile() 0 10 1
A updateDocumentAccess() 15 15 1
B updateContentFromFile() 0 32 6
A updateCommentsFromFile() 0 10 2
B updateShareNames() 0 31 6
A parseMimeType() 0 13 2
A parseMimeTypeText() 6 20 4
A parseMimeTypeTextByExtension() 0 19 4
A parseMimeTypePDF() 0 7 2
A parseMimeTypeZip() 0 6 2
A parseMimeTypeOffice() 6 19 3
A extractContentFromFileText() 0 14 3
A extractContentFromFilePDF() 21 21 4
A extractContentFromFileZip() 21 21 4
A extractContentFromFileOffice() 21 21 4
A isSourceIndexable() 0 10 2
A impersonateOwner() 0 8 2
A updateRunnerAction() 0 7 2
A updateRunnerInfo() 0 7 2
A manageContentErrorException() 0 18 3
A updateDirectoryContentIndex() 0 14 4
A updateDirectoryMeta() 0 17 5
A updateNewIndexError() 0 8 2
A isNodeIndexable() 0 16 4
A getPathFromRoot() 0 10 3

How to fix   Duplicated Code    Complexity   

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:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like FilesService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use FilesService, and based on these observations, apply Extract Interface, too.

1
<?php
2
declare(strict_types=1);
3
4
5
/**
6
 * Files_FullTextSearch - Index the content of your files
7
 *
8
 * This file is licensed under the Affero General Public License version 3 or
9
 * later. See the COPYING file.
10
 *
11
 * @author Maxence Lange <[email protected]>
12
 * @copyright 2018
13
 * @license GNU AGPL version 3 or any later version
14
 *
15
 * This program is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License as
17
 * published by the Free Software Foundation, either version 3 of the
18
 * License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License
26
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27
 *
28
 */
29
30
31
namespace OCA\Files_FullTextSearch\Service;
32
33
34
use daita\MySmallPhpTools\Traits\TPathTools;
35
use Exception;
36
use OCA\Files_FullTextSearch\Exceptions\EmptyUserException;
37
use OCA\Files_FullTextSearch\Exceptions\FileIsNotIndexableException;
38
use OCA\Files_FullTextSearch\Exceptions\FilesNotFoundException;
39
use OCA\Files_FullTextSearch\Exceptions\KnownFileMimeTypeException;
40
use OCA\Files_FullTextSearch\Exceptions\KnownFileSourceException;
41
use OCA\Files_FullTextSearch\Model\FilesDocument;
42
use OCA\Files_FullTextSearch\Provider\FilesProvider;
43
use OCP\App\IAppManager;
44
use OCP\AppFramework\IAppContainer;
45
use OCP\Comments\ICommentsManager;
46
use OCP\Files\File;
47
use OCP\Files\FileInfo;
48
use OCP\Files\Folder;
49
use OCP\Files\InvalidPathException;
50
use OCP\Files\IRootFolder;
51
use OCP\Files\Node;
52
use OCP\Files\NotFoundException;
53
use OCP\Files\NotPermittedException;
54
use OCP\Files\StorageNotAvailableException;
55
use OCP\FullTextSearch\IFullTextSearchManager;
56
use OCP\FullTextSearch\Model\DocumentAccess;
57
use OCP\FullTextSearch\Model\IIndex;
58
use OCP\FullTextSearch\Model\IIndexOptions;
59
use OCP\FullTextSearch\Model\IndexDocument;
60
use OCP\FullTextSearch\Model\IRunner;
61
use OCP\IUserManager;
62
use OCP\Share\IManager as IShareManager;
63
use Throwable;
64
65
66
/**
67
 * Class FilesService
68
 *
69
 * @package OCA\Files_FullTextSearch\Service
70
 */
71
class FilesService {
72
73
74
	use TPathTools;
75
76
77
	const MIMETYPE_TEXT = 'files_text';
78
	const MIMETYPE_PDF = 'files_pdf';
79
	const MIMETYPE_OFFICE = 'files_office';
80
	const MIMETYPE_ZIP = 'files_zip';
81
	const MIMETYPE_IMAGE = 'files_image';
82
	const MIMETYPE_AUDIO = 'files_audio';
83
84
	const CHUNK_TREE_SIZE = 2;
85
86
87
	/** @var IAppContainer */
88
	private $container;
89
90
	/** @var IRootFolder */
91
	private $rootFolder;
92
93
	/** @var IUserManager */
94
	private $userManager;
95
96
	/** @var IAppManager */
97
	private $appManager;
98
99
	/** @var IShareManager */
100
	private $shareManager;
101
102
	/** @var ICommentsManager */
103
	private $commentsManager;
104
105
	/** @var ConfigService */
106
	private $configService;
107
108
	/** @var LocalFilesService */
109
	private $localFilesService;
110
111
	/** @var ExternalFilesService */
112
	private $externalFilesService;
113
114
	/** @var GroupFoldersService */
115
	private $groupFoldersService;
116
117
	/** @var ExtensionService */
118
	private $extensionService;
119
120
	/** @var IFullTextSearchManager */
121
	private $fullTextSearchManager;
122
123
	/** @var MiscService */
124
	private $miscService;
125
126
127
	/** @var IRunner */
128
	private $runner;
129
130
	/** @var int */
131
	private $sumDocuments;
132
133
134
	/**
135
	 * FilesService constructor.
136
	 *
137
	 * @param IAppContainer $container
138
	 * @param IRootFolder $rootFolder
139
	 * @param IAppManager $appManager
140
	 * @param IUserManager $userManager
141
	 * @param IShareManager $shareManager
142
	 * @param ICommentsManager $commentsManager
143
	 * @param ConfigService $configService
144
	 * @param LocalFilesService $localFilesService
145
	 * @param ExternalFilesService $externalFilesService
146
	 * @param GroupFoldersService $groupFoldersService
147
	 * @param ExtensionService $extensionService
148
	 * @param IFullTextSearchManager $fullTextSearchManager
149
	 * @param MiscService $miscService
150
	 *
151
	 * @internal param IProviderFactory $factory
152
	 */
153
	public function __construct(
154
		IAppContainer $container, IRootFolder $rootFolder, IAppManager $appManager,
155
		IUserManager $userManager, IShareManager $shareManager, ICommentsManager $commentsManager,
156
		ConfigService $configService,
157
		LocalFilesService $localFilesService, ExternalFilesService $externalFilesService,
158
		GroupFoldersService $groupFoldersService, ExtensionService $extensionService,
159
		IFullTextSearchManager $fullTextSearchManager, MiscService $miscService
160
	) {
161
		$this->container = $container;
162
		$this->rootFolder = $rootFolder;
163
		$this->appManager = $appManager;
164
		$this->userManager = $userManager;
165
		$this->shareManager = $shareManager;
166
		$this->commentsManager = $commentsManager;
167
168
		$this->configService = $configService;
169
		$this->localFilesService = $localFilesService;
170
		$this->externalFilesService = $externalFilesService;
171
		$this->groupFoldersService = $groupFoldersService;
172
		$this->extensionService = $extensionService;
173
		$this->fullTextSearchManager = $fullTextSearchManager;
174
175
		$this->miscService = $miscService;
176
	}
177
178
179
	/**
180
	 * @param IRunner $runner
181
	 */
182
	public function setRunner(IRunner $runner) {
183
		$this->runner = $runner;
184
	}
185
186
187
	/**
188
	 * @param string $userId
189
	 * @param IIndexOptions $indexOptions
190
	 *
191
	 * @return FilesDocument[]
192
	 * @throws NotFoundException
193
	 * @throws InvalidPathException
194
	 */
195
	public function getChunksFromUser(string $userId, IIndexOptions $indexOptions): array {
196
		$this->initFileSystems($userId);
197
198
		/** @var Folder $files */
199
		$files = $this->rootFolder->getUserFolder($userId)
200
								  ->get($indexOptions->getOption('path', '/'));
201
		if ($files instanceof Folder) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\Folder 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...
202
			return $this->getChunksFromDirectory($userId, $files);
203
		} else {
204
			return [$files];
205
		}
206
	}
207
208
209
	/**
210
	 * @param string $userId
211
	 * @param Folder $node
212
	 * @param int $level
213
	 *
214
	 * @return FilesDocument[]
215
	 * @throws InvalidPathException
216
	 * @throws NotFoundException
217
	 */
218
	public function getChunksFromDirectory(string $userId, Folder $node, $level = 0): array {
219
		$entries = [];
220
		$level++;
221
222
		$files = $node->getDirectoryListing();
223
		if (empty($files)) {
224
			$entries[] = $this->getPathFromRoot($node->getPath(), $userId, true);
225
		}
226
227
		foreach ($files as $file) {
228
			if ($file->getType() === FileInfo::TYPE_FOLDER && $level < self::CHUNK_TREE_SIZE) {
229
				/** @var $file Folder */
230
				$entries =
231
					array_merge($entries, $this->getChunksFromDirectory($userId, $file, $level));
232
			} else {
233
				$entries[] = $this->getPathFromRoot($file->getPath(), $userId, true);
234
			}
235
		}
236
237
		return $entries;
238
	}
239
240
241
	/**
242
	 * @param string $userId
243
	 * @param string $chunk
244
	 *
245
	 * @return FilesDocument[]
246
	 * @throws InvalidPathException
247
	 * @throws NotFoundException
248
	 */
249
	public function getFilesFromUser(string $userId, string $chunk): array {
250
251
		$this->initFileSystems($userId);
252
		$this->sumDocuments = 0;
253
254
		/** @var Folder $files */
255
		$files = $this->rootFolder->getUserFolder($userId)
256
								  ->get($chunk);
257
		if ($files instanceof Folder) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\Folder 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...
258
			$result = $this->getFilesFromDirectory($userId, $files);
259
		} else {
260
			$result = [];
261
			try {
262
				$result[] = $this->generateFilesDocumentFromFile($userId, $files);
263
			} catch (FileIsNotIndexableException $e) {
264
				/** we do nothin' */
265
			}
266
		}
267
268
		return $result;
269
	}
270
271
272
	/**
273
	 * @param string $userId
274
	 * @param Folder $node
275
	 *
276
	 * @return FilesDocument[]
277
	 * @throws InvalidPathException
278
	 * @throws NotFoundException
279
	 * @throws Exception
280
	 */
281
	public function getFilesFromDirectory(string $userId, Folder $node): array {
282
		$documents = [];
283
284
		$this->updateRunnerAction('generateIndexFiles', true);
285
		$this->updateRunnerInfo(
286
			[
287
				'info'          => $node->getPath(),
288
				'title'         => '',
289
				'content'       => '',
290
				'documentTotal' => $this->sumDocuments
291
			]
292
		);
293
294
		try {
295
			if ($node->nodeExists('.noindex')) {
296
				return $documents;
297
			}
298
		} catch (StorageNotAvailableException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\StorageNotAvailableException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
299
			return $documents;
300
		}
301
302
		$files = $node->getDirectoryListing();
303
		foreach ($files as $file) {
304
305
			try {
306
				$documents[] = $this->generateFilesDocumentFromFile($userId, $file);
307
				$this->sumDocuments++;
308
			} catch (FileIsNotIndexableException $e) {
309
				continue;
310
			}
311
312
			if ($file->getType() === FileInfo::TYPE_FOLDER) {
313
				/** @var $file Folder */
314
				$documents =
315
					array_merge($documents, $this->getFilesFromDirectory($userId, $file));
316
			}
317
		}
318
319
		return $documents;
320
	}
321
322
323
	/**
324
	 * @param string $userId
325
	 */
326
	private function initFileSystems(string $userId) {
327
		if ($userId === '') {
328
			return;
329
		}
330
331
		if ($this->userManager->get($userId) === null) {
332
			return;
333
		}
334
335
		$this->externalFilesService->initExternalFilesForUser($userId);
336
		$this->groupFoldersService->initGroupSharesForUser($userId);
337
	}
338
339
340
	/**
341
	 * @param string $viewerId
342
	 * @param Node $file
343
	 *
344
	 * @return FilesDocument
345
	 * @throws FileIsNotIndexableException
346
	 * @throws InvalidPathException
347
	 * @throws NotFoundException
348
	 * @throws Exception
349
	 */
350
	private function generateFilesDocumentFromFile(string $viewerId, Node $file): FilesDocument {
351
352
		$this->isNodeIndexable($file);
353
354
		$source = $this->getFileSource($file);
355
		$document = new FilesDocument(FilesProvider::FILES_PROVIDER_ID, (string)$file->getId());
356
		$document->setAccess(new DocumentAccess());
357
358
		if ($file->getId() === -1) {
359
			throw new FileIsNotIndexableException();
360
		}
361
362
		$ownerId = '';
363
		if ($file->getOwner() !== null) {
364
			$ownerId = $file->getOwner()
365
							->getUID();
366
		}
367
368
		if (!is_string($ownerId)) {
369
			$ownerId = '';
370
		}
371
372
		$document->setType($file->getType())
373
				 ->setOwnerId($ownerId)
374
				 ->setPath($this->getPathFromViewerId($file->getId(), $viewerId))
375
				 ->setViewerId($viewerId)
376
				 ->setMimetype($file->getMimetype());
377
		$document->setModifiedTime($file->getMTime())
378
				 ->setSource($source);
379
380
		return $document;
381
	}
382
383
384
	/**
385
	 * @param Node $file
386
	 *
387
	 * @return string
388
	 * @throws FileIsNotIndexableException
389
	 */
390 View Code Duplication
	private function getFileSource(Node $file): string {
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...
391
		$source = '';
392
393
		try {
394
			$this->localFilesService->getFileSource($file, $source);
395
			$this->externalFilesService->getFileSource($file, $source);
396
			$this->groupFoldersService->getFileSource($file, $source);
397
		} catch (KnownFileSourceException $e) {
398
			/** we know the source, just leave. */
399
		}
400
401
		return $source;
402
	}
403
404
405
	/**
406
	 * @param string $userId
407
	 * @param string $path
408
	 *
409
	 * @return Node
410
	 * @throws NotFoundException
411
	 */
412
	public function getFileFromPath(string $userId, string $path): Node {
413
		return $this->rootFolder->getUserFolder($userId)
414
								->get($path);
415
	}
416
417
418
	/**
419
	 * @param string $userId
420
	 * @param int $fileId
421
	 *
422
	 * @return Node
423
	 * @throws FilesNotFoundException
424
	 * @throws EmptyUserException
425
	 */
426
	public function getFileFromId(string $userId, int $fileId): Node {
427
428
		if ($userId === '') {
429
			throw new EmptyUserException();
430
		}
431
432
		$files = $this->rootFolder->getUserFolder($userId)
433
								  ->getById($fileId);
434
		if (sizeof($files) === 0) {
435
			throw new FilesNotFoundException();
436
		}
437
438
		$file = array_shift($files);
439
440
		return $file;
441
	}
442
443
444
	/**
445
	 * @param IIndex $index
446
	 *
447
	 * @return Node
448
	 * @throws EmptyUserException
449
	 * @throws FilesNotFoundException
450
	 */
451
	public function getFileFromIndex(IIndex $index): Node {
452
		// it seems the method is already call slightly earlier in the process
453
//		$this->impersonateOwner($index);
454
455
		return $this->getFileFromId($index->getOwnerId(), (int)$index->getDocumentId());
456
	}
457
458
459
	/**
460
	 * @param int $fileId
461
	 * @param string $viewerId
462
	 *
463
	 * @throws Exception
464
	 * @return string
465
	 */
466
	private function getPathFromViewerId(int $fileId, string $viewerId): string {
467
468
		$viewerFiles = $this->rootFolder->getUserFolder($viewerId)
469
										->getById($fileId);
470
471
		if (sizeof($viewerFiles) === 0) {
472
			return '';
473
		}
474
475
		$file = array_shift($viewerFiles);
476
477
		// TODO: better way to do this : we remove the '/userid/files/'
478
		$path = $this->getPathFromRoot($file->getPath(), $viewerId);
479
		if (!is_string($path)) {
480
			throw new FileIsNotIndexableException();
481
		}
482
483
		$path = $this->withoutEndSlash($path);
484
485
		return $path;
486
	}
487
488
489
	/**
490
	 * @param FilesDocument $document
491
	 */
492
	public function generateDocument(FilesDocument $document) {
493
494
		try {
495
			$this->updateFilesDocument($document);
496
		} catch (Exception $e) {
497
			// TODO - update $document with a error status instead of just ignore !
498
			$document->getIndex()
499
					 ->setStatus(IIndex::INDEX_IGNORE);
500
			$this->miscService->log(
501
				'Exception while generateDocument: ' . $e->getMessage() . ' - trace: '
502
				. json_encode($e->getTrace())
503
			);
504
		}
505
	}
506
507
508
	/**
509
	 * @param IIndex $index
510
	 *
511
	 * @return FilesDocument
512
	 * @throws FileIsNotIndexableException
513
	 * @throws InvalidPathException
514
	 * @throws NotFoundException
515
	 */
516
	private function generateDocumentFromIndex(IIndex $index): FilesDocument {
517
518
		try {
519
			$file = $this->getFileFromIndex($index);
520
		} catch (Exception $e) {
521
			$index->setStatus(IIndex::INDEX_REMOVE);
522
			$document = new FilesDocument($index->getProviderId(), $index->getDocumentId());
523
			$document->setIndex($index);
524
525
			return $document;
526
		}
527
528
		$this->isNodeIndexable($file);
529
530
		$document = $this->generateFilesDocumentFromFile($index->getOwnerId(), $file);
531
		$document->setIndex($index);
532
533
		$this->updateFilesDocumentFromFile($document, $file);
534
535
		return $document;
536
	}
537
538
539
	/**
540
	 * @param IndexDocument $document
541
	 *
542
	 * @return bool
543
	 */
544
	public function isDocumentUpToDate(IndexDocument $document): bool {
545
		$index = $document->getIndex();
546
547
		if (!$this->configService->compareIndexOptions($index)) {
548
			$index->setStatus(IIndex::INDEX_CONTENT);
549
			$document->setIndex($index);
550
551
			return false;
552
		}
553
554
		if ($index->getStatus() !== IIndex::INDEX_OK) {
555
			return false;
556
		}
557
558
		if ($index->getLastIndex() >= $document->getModifiedTime()) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $index->getLastIn...ent->getModifiedTime();.
Loading history...
559
			return true;
560
		}
561
562
		return false;
563
	}
564
565
566
	/**
567
	 * @param IIndex $index
568
	 *
569
	 * @return FilesDocument
570
	 * @throws InvalidPathException
571
	 * @throws NotFoundException
572
	 * @throws FileIsNotIndexableException
573
	 */
574
	public function updateDocument(IIndex $index): FilesDocument {
575
		$this->impersonateOwner($index);
576
		$this->initFileSystems($index->getOwnerId());
577
578
		$document = $this->generateDocumentFromIndex($index);
579
		$this->updateDirectoryContentIndex($index);
580
581
		return $document;
582
	}
583
584
585
	/**
586
	 * @param FilesDocument $document
587
	 *
588
	 * @throws NotFoundException
589
	 */
590
	private function updateFilesDocument(FilesDocument $document) {
591
		$userFolder = $this->rootFolder->getUserFolder($document->getViewerId());
592
		$file = $userFolder->get($document->getPath());
593
594
		try {
595
			$this->updateFilesDocumentFromFile($document, $file);
596
		} catch (FileIsNotIndexableException $e) {
597
			$document->getIndex()
598
					 ->setStatus(IIndex::INDEX_IGNORE);
599
		}
600
	}
601
602
603
	/**
604
	 * @param FilesDocument $document
605
	 * @param Node $file
606
	 *
607
	 * @throws FileIsNotIndexableException
608
	 */
609
	private function updateFilesDocumentFromFile(FilesDocument $document, Node $file) {
610
611
		$document->getIndex()
612
				 ->setSource($document->getSource());
613
614
		$this->updateDocumentAccess($document, $file);
615
		$this->updateContentFromFile($document, $file);
616
617
		$document->addMetaTag($document->getSource());
618
	}
619
620
621
	/**
622
	 * @param FilesDocument $document
623
	 * @param Node $file
624
	 *
625
	 * @throws FileIsNotIndexableException
626
	 */
627 View Code Duplication
	private function updateDocumentAccess(FilesDocument $document, Node $file) {
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...
628
629
//		$index = $document->getIndex();
630
		// This should not be needed, let's assume we _need_ to update document access
631
//		if (!$index->isStatus(IIndex::INDEX_FULL)
632
//			&& !$index->isStatus(IIndex::INDEX_META)) {
633
//			return;
634
//		}
635
636
		$this->localFilesService->updateDocumentAccess($document, $file);
637
		$this->externalFilesService->updateDocumentAccess($document, $file);
638
		$this->groupFoldersService->updateDocumentAccess($document, $file);
639
640
		$this->updateShareNames($document, $file);
641
	}
642
643
644
	/**
645
	 * @param FilesDocument $document
646
	 * @param Node $file
647
	 */
648
	private function updateContentFromFile(FilesDocument $document, Node $file) {
649
650
		$document->setTitle($document->getPath());
651
652
		if (!$document->getIndex()
653
					  ->isStatus(IIndex::INDEX_CONTENT)
654
			|| $file->getType() !== FileInfo::TYPE_FILE) {
655
			return;
656
		}
657
658
		try {
659
			/** @var File $file */
660
			if ($file->getSize() <
661
				($this->configService->getAppValue(ConfigService::FILES_SIZE) * 1024 * 1024)) {
662
				$this->extractContentFromFileText($document, $file);
663
				$this->extractContentFromFileOffice($document, $file);
664
				$this->extractContentFromFilePDF($document, $file);
665
				$this->extractContentFromFileZip($document, $file);
666
667
				$this->extensionService->fileIndexing($document, $file);
668
			}
669
		} catch (Throwable $t) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
670
			$this->manageContentErrorException($document, $t);
671
		}
672
673
		if ($document->getContent() === null) {
674
			$document->getIndex()
675
					 ->unsetStatus(IIndex::INDEX_CONTENT);
676
		}
677
678
		$this->updateCommentsFromFile($document);
679
	}
680
681
682
	/**
683
	 * @param FilesDocument $document
684
	 */
685
	private function updateCommentsFromFile(FilesDocument $document) {
686
		$comments = $this->commentsManager->getForObject('files', $document->getId());
687
688
		$part = [];
689
		foreach ($comments as $comment) {
690
			$part[] = '<' . $comment->getActorId() . '> ' . $comment->getMessage();
691
		}
692
693
		$document->addPart('comments', implode(" \n ", $part));
694
	}
695
696
697
	/**
698
	 * @param FilesDocument $document
699
	 * @param Node $file
700
	 *
701
	 * @return array
702
	 */
703
	private function updateShareNames(FilesDocument $document, Node $file): array {
704
705
		$users = [];
706
707
		$this->localFilesService->getShareUsersFromFile($file, $users);
708
		$this->externalFilesService->getShareUsers($document, $users);
709
		$this->groupFoldersService->getShareUsers($document, $users);
710
711
		$shareNames = [];
712
		foreach ($users as $username) {
713
			try {
714
				$user = $this->userManager->get($username);
715
				if ($user === null || $user->getLastLogin() === 0) {
716
					continue;
717
				}
718
719
				$path = $this->getPathFromViewerId($file->getId(), $username);
720
				$shareNames[$this->miscService->secureUsername($username)] =
721
					(!is_string($path)) ? $path = '' : $path;
722
723
			} catch (Exception $e) {
724
				$this->miscService->log(
725
					'Issue while getting information on documentId:' . $document->getId(), 0
726
				);
727
			}
728
		}
729
730
		$document->setInfoArray('share_names', $shareNames);
731
732
		return $shareNames;
733
	}
734
735
736
	/**
737
	 * @param string $mimeType
738
	 * @param string $extension
739
	 *
740
	 * @return string
741
	 */
742
	private function parseMimeType(string $mimeType, string $extension): string {
743
744
		$parsed = '';
745
		try {
746
			$this->parseMimeTypeText($mimeType, $extension, $parsed);
747
			$this->parseMimeTypePDF($mimeType, $parsed);
748
			$this->parseMimeTypeOffice($mimeType, $parsed);
749
			$this->parseMimeTypeZip($mimeType, $parsed);
750
		} catch (KnownFileMimeTypeException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
751
		}
752
753
		return $parsed;
754
	}
755
756
757
	/**
758
	 * @param string $mimeType
759
	 * @param string $extension
760
	 * @param string $parsed
761
	 *
762
	 * @throws KnownFileMimeTypeException
763
	 */
764
	private function parseMimeTypeText(string $mimeType, string $extension, string &$parsed) {
765
766
		if (substr($mimeType, 0, 5) === 'text/') {
767
			$parsed = self::MIMETYPE_TEXT;
768
			throw new KnownFileMimeTypeException();
769
		}
770
771
		$textMimes = [
772
			'application/epub+zip'
773
		];
774
775 View Code Duplication
		foreach ($textMimes as $mime) {
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...
776
			if (strpos($mimeType, $mime) === 0) {
777
				$parsed = self::MIMETYPE_TEXT;
778
				throw new KnownFileMimeTypeException();
779
			}
780
		}
781
782
		$this->parseMimeTypeTextByExtension($mimeType, $extension, $parsed);
783
	}
784
785
786
	/**
787
	 * @param string $mimeType
788
	 * @param string $extension
789
	 * @param string $parsed
790
	 *
791
	 * @throws KnownFileMimeTypeException
792
	 */
793
	private function parseMimeTypeTextByExtension(
794
		string $mimeType, string $extension, string &$parsed
795
	) {
796
		$textMimes = [
797
			'application/octet-stream'
798
		];
799
		$textExtension = [
800
		];
801
802
		foreach ($textMimes as $mime) {
803
			if (strpos($mimeType, $mime) === 0
804
				&& in_array(
805
					strtolower($extension), $textExtension
806
				)) {
807
				$parsed = self::MIMETYPE_TEXT;
808
				throw new KnownFileMimeTypeException();
809
			}
810
		}
811
	}
812
813
814
	/**
815
	 * @param string $mimeType
816
	 * @param string $parsed
817
	 *
818
	 * @throws KnownFileMimeTypeException
819
	 */
820
	private function parseMimeTypePDF(string $mimeType, string &$parsed) {
821
822
		if ($mimeType === 'application/pdf') {
823
			$parsed = self::MIMETYPE_PDF;
824
			throw new KnownFileMimeTypeException();
825
		}
826
	}
827
828
829
	/**
830
	 * @param string $mimeType
831
	 * @param string $parsed
832
	 *
833
	 * @throws KnownFileMimeTypeException
834
	 */
835
	private function parseMimeTypeZip(string $mimeType, string &$parsed) {
836
		if ($mimeType === 'application/zip') {
837
			$parsed = self::MIMETYPE_ZIP;
838
			throw new KnownFileMimeTypeException();
839
		}
840
	}
841
842
843
	/**
844
	 * @param string $mimeType
845
	 * @param string $parsed
846
	 *
847
	 * @throws KnownFileMimeTypeException
848
	 */
849
	private function parseMimeTypeOffice(string $mimeType, string &$parsed) {
850
851
		$officeMimes = [
852
			'application/msword',
853
			'application/vnd.oasis.opendocument',
854
			'application/vnd.sun.xml',
855
			'application/vnd.openxmlformats-officedocument',
856
			'application/vnd.ms-word',
857
			'application/vnd.ms-powerpoint',
858
			'application/vnd.ms-excel'
859
		];
860
861 View Code Duplication
		foreach ($officeMimes as $mime) {
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...
862
			if (strpos($mimeType, $mime) === 0) {
863
				$parsed = self::MIMETYPE_OFFICE;
864
				throw new KnownFileMimeTypeException();
865
			}
866
		}
867
	}
868
869
870
	/**
871
	 * @param FilesDocument $document
872
	 * @param File $file
873
	 *
874
	 * @throws NotPermittedException
875
	 */
876
	private function extractContentFromFileText(FilesDocument $document, File $file) {
877
		if ($this->parseMimeType($document->getMimeType(), $file->getExtension())
878
			!== self::MIMETYPE_TEXT) {
879
			return;
880
		}
881
882
		if (!$this->isSourceIndexable($document)) {
883
			return;
884
		}
885
886
		$document->setContent(
887
			base64_encode($file->getContent()), IndexDocument::ENCODED_BASE64
888
		);
889
	}
890
891
892
	/**
893
	 * @param FilesDocument $document
894
	 * @param File $file
895
	 *
896
	 * @throws NotPermittedException
897
	 */
898 View Code Duplication
	private function extractContentFromFilePDF(FilesDocument $document, File $file) {
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...
899
		if ($this->parseMimeType($document->getMimeType(), $file->getExtension())
900
			!== self::MIMETYPE_PDF) {
901
			return;
902
		}
903
904
		$this->configService->setDocumentIndexOption($document, ConfigService::FILES_PDF);
905
		if (!$this->isSourceIndexable($document)) {
906
			return;
907
		}
908
909
		if ($this->configService->getAppValue(ConfigService::FILES_PDF) !== '1') {
910
			$document->setContent('');
911
912
			return;
913
		}
914
915
		$document->setContent(
916
			base64_encode($file->getContent()), IndexDocument::ENCODED_BASE64
917
		);
918
	}
919
920
921
	/**
922
	 * @param FilesDocument $document
923
	 * @param File $file
924
	 *
925
	 * @throws NotPermittedException
926
	 */
927 View Code Duplication
	private function extractContentFromFileZip(FilesDocument $document, File $file) {
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...
928
		if ($this->parseMimeType($document->getMimeType(), $file->getExtension())
929
			!== self::MIMETYPE_ZIP) {
930
			return;
931
		}
932
933
		$this->configService->setDocumentIndexOption($document, ConfigService::FILES_ZIP);
934
		if (!$this->isSourceIndexable($document)) {
935
			return;
936
		}
937
938
		if ($this->configService->getAppValue(ConfigService::FILES_ZIP) !== '1') {
939
			$document->setContent('');
940
941
			return;
942
		}
943
944
		$document->setContent(
945
			base64_encode($file->getContent()), IndexDocument::ENCODED_BASE64
946
		);
947
	}
948
949
950
	/**
951
	 * @param FilesDocument $document
952
	 * @param File $file
953
	 *
954
	 * @throws NotPermittedException
955
	 */
956 View Code Duplication
	private function extractContentFromFileOffice(FilesDocument $document, File $file) {
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...
957
		if ($this->parseMimeType($document->getMimeType(), $file->getExtension())
958
			!== self::MIMETYPE_OFFICE) {
959
			return;
960
		}
961
962
		$this->configService->setDocumentIndexOption($document, ConfigService::FILES_OFFICE);
963
		if (!$this->isSourceIndexable($document)) {
964
			return;
965
		}
966
967
		if ($this->configService->getAppValue(ConfigService::FILES_OFFICE) !== '1') {
968
			$document->setContent('');
969
970
			return;
971
		}
972
973
		$document->setContent(
974
			base64_encode($file->getContent()), IndexDocument::ENCODED_BASE64
975
		);
976
	}
977
978
979
	/**
980
	 * @param FilesDocument $document
981
	 *
982
	 * @return bool
983
	 */
984
	private function isSourceIndexable(FilesDocument $document): bool {
985
		$this->configService->setDocumentIndexOption($document, $document->getSource());
986
		if ($this->configService->getAppValue($document->getSource()) !== '1') {
987
			$document->setContent('');
988
989
			return false;
990
		}
991
992
		return true;
993
	}
994
995
996
	/**
997
	 * @param IIndex $index
998
	 */
999
	private function impersonateOwner(IIndex $index) {
1000
		if ($index->getOwnerId() !== '') {
1001
			return;
1002
		}
1003
1004
		$this->groupFoldersService->impersonateOwner($index);
1005
		$this->externalFilesService->impersonateOwner($index);
1006
	}
1007
1008
1009
	/**
1010
	 * @param $action
1011
	 * @param bool $force
1012
	 *
1013
	 * @throws Exception
1014
	 */
1015
	private function updateRunnerAction(string $action, bool $force = false) {
1016
		if ($this->runner === null) {
1017
			return;
1018
		}
1019
1020
		$this->runner->updateAction($action, $force);
1021
	}
1022
1023
1024
	/**
1025
	 * @param array $data
1026
	 */
1027
	private function updateRunnerInfo($data) {
1028
		if ($this->runner === null) {
1029
			return;
1030
		}
1031
1032
		$this->runner->setInfoArray($data);
1033
	}
1034
1035
	/**
1036
	 * @param IndexDocument $document
1037
	 * @param Throwable $t
1038
	 */
1039
	private function manageContentErrorException(IndexDocument $document, Throwable $t) {
1040
		$document->getIndex()
1041
				 ->addError(
1042
					 'Error while getting file content', $t->getMessage(), IIndex::ERROR_SEV_3
1043
				 );
1044
		$this->updateNewIndexError(
1045
			$document->getIndex(), 'Error while getting file content', $t->getMessage(),
1046
			IIndex::ERROR_SEV_3
1047
		);
1048
1049
		$trace = $t->getTrace();
1050
		if (is_array($trace)) {
1051
			$trace = json_encode($trace);
1052
		}
1053
		if (is_string($trace)) {
1054
			$this->miscService->log($trace, 0);
1055
		}
1056
	}
1057
1058
1059
	/**
1060
	 * @param IIndex $index
1061
	 */
1062
	private function updateDirectoryContentIndex(IIndex $index) {
1063
		if (!$index->isStatus(IIndex::INDEX_META)) {
1064
			return;
1065
		}
1066
1067
		try {
1068
			$file = $this->getFileFromIndex($index);
1069
			if ($file->getType() === File::TYPE_FOLDER) {
1070
				/** @var Folder $file */
1071
				$this->updateDirectoryMeta($file);
1072
			}
1073
		} catch (Exception $e) {
1074
		}
1075
	}
1076
1077
1078
	/**
1079
	 * @param Folder $node
1080
	 */
1081
	private function updateDirectoryMeta(Folder $node) {
1082
		try {
1083
			$files = $node->getDirectoryListing();
1084
		} catch (NotFoundException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
1085
			return;
1086
		}
1087
1088
		foreach ($files as $file) {
1089
			try {
1090
				$this->fullTextSearchManager->updateIndexStatus(
1091
					'files', (string)$file->getId(), IIndex::INDEX_META
1092
				);
1093
			} catch (InvalidPathException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class OCP\Files\InvalidPathException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
1094
			} catch (NotFoundException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class OCP\Files\NotFoundException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
1095
			}
1096
		}
1097
	}
1098
1099
1100
	/**
1101
	 * @param IIndex $index
1102
	 * @param string $message
1103
	 * @param string $exception
1104
	 * @param int $sev
1105
	 */
1106
	private function updateNewIndexError(IIndex $index, string $message, string $exception, int $sev
1107
	) {
1108
		if ($this->runner === null) {
1109
			return;
1110
		}
1111
1112
		$this->runner->newIndexError($index, $message, $exception, $sev);
1113
	}
1114
1115
1116
	/**
1117
	 * @param Node $file
1118
	 *
1119
	 * @throws FileIsNotIndexableException
1120
	 */
1121
	private function isNodeIndexable(Node $file) {
1122
1123
		if ($file->getType() === File::TYPE_FOLDER) {
1124
			/** @var Folder $file */
1125
			if ($file->nodeExists('.noindex')) {
1126
				throw new FileIsNotIndexableException();
1127
			}
1128
		}
1129
1130
		$parent = $file->getParent();
1131
		$parentPath = $this->withoutBeginSlash($parent->getPath());
1132
		$path = substr($parent->getPath(), 8 + strpos($parentPath, '/'));
1133
		if (is_string($path)) {
1134
			$this->isNodeIndexable($file->getParent());
1135
		}
1136
	}
1137
1138
1139
	/**
1140
	 * @param string $path
1141
	 * @param string $userId
1142
	 *
1143
	 * @param bool $entrySlash
1144
	 *
1145
	 * @return string
1146
	 */
1147
	private function getPathFromRoot(string $path, string $userId, bool $entrySlash = false) {
1148
		// TODO: better way to do this : we remove the '/userid/files/'
1149
		// TODO: do we need userId, or can we crop the path like in isNodeIndexable()
1150
		$path = substr($path, 8 + strlen($userId));
1151
		if (!is_string($path)) {
1152
			$path = '';
1153
		}
1154
1155
		return (($entrySlash) ? '/' : '') . $path;
1156
	}
1157
1158
}
1159
1160