CleanTempFiles.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. namespace App\Service\Cron\Job;
  3. use App\Entity\Core\File;
  4. use App\Enum\Core\FileHostEnum;
  5. use App\Enum\Core\FileStatusEnum;
  6. use App\Repository\Core\FileRepository;
  7. use App\Service\Cron\BaseCronJob;
  8. use App\Service\File\Storage\LocalStorage;
  9. use App\Service\Utils\DatesUtils;
  10. use Doctrine\DBAL\Connection;
  11. use Doctrine\ORM\QueryBuilder;
  12. use JetBrains\PhpStorm\Pure;
  13. /**
  14. * Cronjob that delete temporary files older than N days.
  15. *
  16. * >>> ot:cron run clean-temp-files --preview
  17. * >>> ot:cron run clean-temp-files
  18. */
  19. class CleanTempFiles extends BaseCronJob
  20. {
  21. /**
  22. * Delay before removing a temporary file (in days).
  23. */
  24. private const DELETE_OLDER_THAN = 60;
  25. #[Pure]
  26. public function __construct(
  27. private Connection $connection,
  28. private FileRepository $fileRepository,
  29. private LocalStorage $storage,
  30. ) {
  31. parent::__construct();
  32. }
  33. /**
  34. * Preview the result of the execution, without actually deleting anything.
  35. *
  36. * @throws \Exception
  37. */
  38. public function preview(): void
  39. {
  40. $maxDate = DatesUtils::new();
  41. $maxDate->sub(new \DateInterval('P'.self::DELETE_OLDER_THAN.'D'));
  42. $files = $this->listFilesToDelete($maxDate);
  43. $total = count($files);
  44. $this->ui->print($total.' temporary files to be removed');
  45. $this->ui->print('> Printing the first and last 50 :');
  46. $i = 0;
  47. foreach ($files as $file) {
  48. ++$i;
  49. if ($i > 50 && ($total - $i) > 50) {
  50. continue;
  51. }
  52. if (($total - $i) === 50) {
  53. $this->ui->print(' (...)');
  54. }
  55. $this->ui->print(' * '.$file->getPath());
  56. }
  57. }
  58. /**
  59. * Proceed to the deletion of the files and the purge of the DB.
  60. *
  61. * @throws \Exception
  62. */
  63. public function execute(): void
  64. {
  65. $maxDate = DatesUtils::new();
  66. $maxDate->sub(new \DateInterval('P'.self::DELETE_OLDER_THAN.'D'));
  67. $files = $this->listFilesToDelete($maxDate);
  68. $this->logger->info(count($files).' temporary files to be removed');
  69. $this->deleteFiles($files);
  70. }
  71. /**
  72. * List the files to delete in the DB.
  73. *
  74. * @return array<File>
  75. *
  76. * @throws \Exception
  77. */
  78. protected function listFilesToDelete(\DateTime $maxDate): array
  79. {
  80. $this->ui->print('List temporary files created before '.$maxDate->format('c'));
  81. $queryBuilder = $this->fileRepository->createQueryBuilder('f');
  82. $queryBuilder->select();
  83. $this->getQueryConditions($queryBuilder, $maxDate);
  84. return $queryBuilder->getQuery()->getResult();
  85. }
  86. /**
  87. * Delete the files.
  88. *
  89. * @param array<File> $files
  90. */
  91. protected function deleteFiles(array $files): void
  92. {
  93. $total = count($files);
  94. $this->logger->info($total.' temporary files to be removed');
  95. $this->connection->setAutoCommit(false);
  96. $queryBuilder = $this->fileRepository->createQueryBuilder('f');
  97. $this->logger->info('Deleting files...');
  98. $i = 0;
  99. $deleted = 0;
  100. $this->ui->progress(0, $total);
  101. foreach ($files as $file) {
  102. $this->connection->beginTransaction();
  103. $this->ui->progress($i, $total);
  104. ++$i;
  105. try {
  106. // Delete from disk
  107. $this->storage->hardDelete($file);
  108. // Remove from DB
  109. $queryBuilder->delete()->where('f.id = :id')->setParameter('id', $file->getId());
  110. $this->connection->commit();
  111. ++$deleted;
  112. } catch (\RuntimeException|\InvalidArgumentException $e) {
  113. // Non blocking errors
  114. $this->connection->rollback();
  115. $this->logger->error('ERROR : '.$e->getMessage());
  116. } catch (\Exception $exception) {
  117. $this->connection->rollback();
  118. throw $exception;
  119. }
  120. }
  121. $this->logger->info($deleted.' files deleted');
  122. }
  123. protected function getQueryConditions(QueryBuilder $queryBuilder, \DateTime $maxDate): void
  124. {
  125. $queryBuilder
  126. ->andWhere(
  127. $queryBuilder->expr()->orX(
  128. $queryBuilder->expr()->eq('f.isTemporaryFile', ':temporaryTrue'),
  129. $queryBuilder->expr()->eq('f.status', ':status')
  130. )
  131. )
  132. ->andWhere($queryBuilder->expr()->eq('f.host', ':host'))
  133. ->andWhere(
  134. $queryBuilder->expr()->orX(
  135. $queryBuilder->expr()->lt('f.createDate', ':maxDate'),
  136. $queryBuilder->expr()->isNull('f.createDate')
  137. )
  138. )
  139. ->setParameter('temporaryTrue', true)
  140. ->setParameter('host', FileHostEnum::AP2I)
  141. ->setParameter('status', FileStatusEnum::DELETED)
  142. ->setParameter('maxDate', $maxDate->format('Y-m-d'))
  143. ;
  144. }
  145. }