ScanFiles.php 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\Files\BackgroundJob;
  8. use OC\Files\Utils\Scanner;
  9. use OCP\AppFramework\Utility\ITimeFactory;
  10. use OCP\BackgroundJob\TimedJob;
  11. use OCP\DB\QueryBuilder\IQueryBuilder;
  12. use OCP\EventDispatcher\IEventDispatcher;
  13. use OCP\IConfig;
  14. use OCP\IDBConnection;
  15. use Psr\Log\LoggerInterface;
  16. /**
  17. * Class ScanFiles is a background job used to run the file scanner over the user
  18. * accounts to ensure integrity of the file cache.
  19. *
  20. * @package OCA\Files\BackgroundJob
  21. */
  22. class ScanFiles extends TimedJob {
  23. private IConfig $config;
  24. private IEventDispatcher $dispatcher;
  25. private LoggerInterface $logger;
  26. private IDBConnection $connection;
  27. /** Amount of users that should get scanned per execution */
  28. public const USERS_PER_SESSION = 500;
  29. public function __construct(
  30. IConfig $config,
  31. IEventDispatcher $dispatcher,
  32. LoggerInterface $logger,
  33. IDBConnection $connection,
  34. ITimeFactory $time
  35. ) {
  36. parent::__construct($time);
  37. // Run once per 10 minutes
  38. $this->setInterval(60 * 10);
  39. $this->config = $config;
  40. $this->dispatcher = $dispatcher;
  41. $this->logger = $logger;
  42. $this->connection = $connection;
  43. }
  44. protected function runScanner(string $user): void {
  45. try {
  46. $scanner = new Scanner(
  47. $user,
  48. null,
  49. $this->dispatcher,
  50. $this->logger
  51. );
  52. $scanner->backgroundScan('');
  53. } catch (\Exception $e) {
  54. $this->logger->error($e->getMessage(), ['exception' => $e, 'app' => 'files']);
  55. }
  56. \OC_Util::tearDownFS();
  57. }
  58. /**
  59. * Find a storage which have unindexed files and return a user with access to the storage
  60. *
  61. * @return string|false
  62. */
  63. private function getUserToScan() {
  64. $query = $this->connection->getQueryBuilder();
  65. $query->select('user_id')
  66. ->from('filecache', 'f')
  67. ->innerJoin('f', 'mounts', 'm', $query->expr()->eq('storage_id', 'storage'))
  68. ->where($query->expr()->lt('size', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
  69. ->andWhere($query->expr()->gt('parent', $query->createNamedParameter(-1, IQueryBuilder::PARAM_INT)))
  70. ->setMaxResults(1);
  71. return $query->executeQuery()->fetchOne();
  72. }
  73. /**
  74. * @param $argument
  75. * @throws \Exception
  76. */
  77. protected function run($argument) {
  78. if ($this->config->getSystemValueBool('files_no_background_scan', false)) {
  79. return;
  80. }
  81. $usersScanned = 0;
  82. $lastUser = '';
  83. $user = $this->getUserToScan();
  84. while ($user && $usersScanned < self::USERS_PER_SESSION && $lastUser !== $user) {
  85. $this->runScanner($user);
  86. $lastUser = $user;
  87. $user = $this->getUserToScan();
  88. $usersScanned += 1;
  89. }
  90. if ($lastUser === $user) {
  91. $this->logger->warning("User $user still has unscanned files after running background scan, background scan might be stopped prematurely");
  92. }
  93. }
  94. }