CleanupRemoteStorages.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud GmbH.
  4. *
  5. * @author Joas Schilling <coding@schilljs.com>
  6. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  7. * @author Roeland Jago Douma <roeland@famdouma.nl>
  8. *
  9. * @license AGPL-3.0
  10. *
  11. * This code is free software: you can redistribute it and/or modify
  12. * it under the terms of the GNU Affero General Public License, version 3,
  13. * as published by the Free Software Foundation.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU Affero General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Affero General Public License, version 3,
  21. * along with this program. If not, see <http://www.gnu.org/licenses/>
  22. *
  23. */
  24. namespace OCA\Files_Sharing\Command;
  25. use OCP\DB\QueryBuilder\IQueryBuilder;
  26. use OCP\IDBConnection;
  27. use Symfony\Component\Console\Command\Command;
  28. use Symfony\Component\Console\Input\InputInterface;
  29. use Symfony\Component\Console\Input\InputOption;
  30. use Symfony\Component\Console\Output\OutputInterface;
  31. /**
  32. * Cleanup 'shared::' storage entries that have no matching entries in the
  33. * shares_external table.
  34. */
  35. class CleanupRemoteStorages extends Command {
  36. /**
  37. * @var IDBConnection
  38. */
  39. protected $connection;
  40. public function __construct(IDBConnection $connection) {
  41. $this->connection = $connection;
  42. parent::__construct();
  43. }
  44. protected function configure() {
  45. $this
  46. ->setName('sharing:cleanup-remote-storages')
  47. ->setDescription('Cleanup shared storage entries that have no matching entry in the shares_external table')
  48. ->addOption(
  49. 'dry-run',
  50. null,
  51. InputOption::VALUE_NONE,
  52. 'only show which storages would be deleted'
  53. );
  54. }
  55. public function execute(InputInterface $input, OutputInterface $output) {
  56. $remoteStorages = $this->getRemoteStorages();
  57. $output->writeln(count($remoteStorages) . ' remote storage(s) need(s) to be checked');
  58. $remoteShareIds = $this->getRemoteShareIds();
  59. $output->writeln(count($remoteShareIds) . ' remote share(s) exist');
  60. foreach ($remoteShareIds as $id => $remoteShareId) {
  61. if (isset($remoteStorages[$remoteShareId])) {
  62. if ($input->getOption('dry-run') || $output->isVerbose()) {
  63. $output->writeln("<info>$remoteShareId belongs to remote share $id</info>");
  64. }
  65. unset($remoteStorages[$remoteShareId]);
  66. } else {
  67. $output->writeln("<comment>$remoteShareId for share $id has no matching storage, yet</comment>");
  68. }
  69. }
  70. if (empty($remoteStorages)) {
  71. $output->writeln('<info>no storages deleted</info>');
  72. } else {
  73. $dryRun = $input->getOption('dry-run');
  74. foreach ($remoteStorages as $id => $numericId) {
  75. if ($dryRun) {
  76. $output->writeln("<error>$id [$numericId] can be deleted</error>");
  77. $this->countFiles($numericId, $output);
  78. } else {
  79. $this->deleteStorage($id, $numericId, $output);
  80. }
  81. }
  82. }
  83. }
  84. public function countFiles($numericId, OutputInterface $output) {
  85. $queryBuilder = $this->connection->getQueryBuilder();
  86. $queryBuilder->select($queryBuilder->createFunction('count(fileid)'))
  87. ->from('filecache')
  88. ->where($queryBuilder->expr()->eq(
  89. 'storage',
  90. $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
  91. IQueryBuilder::PARAM_STR)
  92. );
  93. $result = $queryBuilder->execute();
  94. $count = $result->fetchColumn();
  95. $output->writeln("$count files can be deleted for storage $numericId");
  96. }
  97. public function deleteStorage($id, $numericId, OutputInterface $output) {
  98. $queryBuilder = $this->connection->getQueryBuilder();
  99. $queryBuilder->delete('storages')
  100. ->where($queryBuilder->expr()->eq(
  101. 'id',
  102. $queryBuilder->createNamedParameter($id, IQueryBuilder::PARAM_STR),
  103. IQueryBuilder::PARAM_STR)
  104. );
  105. $output->write("deleting $id [$numericId] ... ");
  106. $count = $queryBuilder->execute();
  107. $output->writeln("deleted $count storage");
  108. $this->deleteFiles($numericId, $output);
  109. }
  110. public function deleteFiles($numericId, OutputInterface $output) {
  111. $queryBuilder = $this->connection->getQueryBuilder();
  112. $queryBuilder->delete('filecache')
  113. ->where($queryBuilder->expr()->eq(
  114. 'storage',
  115. $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR),
  116. IQueryBuilder::PARAM_STR)
  117. );
  118. $output->write("deleting files for storage $numericId ... ");
  119. $count = $queryBuilder->execute();
  120. $output->writeln("deleted $count files");
  121. }
  122. public function getRemoteStorages() {
  123. $queryBuilder = $this->connection->getQueryBuilder();
  124. $queryBuilder->select(['id', 'numeric_id'])
  125. ->from('storages')
  126. ->where($queryBuilder->expr()->like(
  127. 'id',
  128. // match all 'shared::' + 32 characters storages
  129. $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::') . str_repeat('_', 32)),
  130. IQueryBuilder::PARAM_STR)
  131. )
  132. ->andWhere($queryBuilder->expr()->notLike(
  133. 'id',
  134. // but not the ones starting with a '/', they are for normal shares
  135. $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::/') . '%'),
  136. IQueryBuilder::PARAM_STR)
  137. )->orderBy('numeric_id');
  138. $query = $queryBuilder->execute();
  139. $remoteStorages = [];
  140. while ($row = $query->fetch()) {
  141. $remoteStorages[$row['id']] = $row['numeric_id'];
  142. }
  143. return $remoteStorages;
  144. }
  145. public function getRemoteShareIds() {
  146. $queryBuilder = $this->connection->getQueryBuilder();
  147. $queryBuilder->select(['id', 'share_token', 'remote'])
  148. ->from('share_external');
  149. $query = $queryBuilder->execute();
  150. $remoteShareIds = [];
  151. while ($row = $query->fetch()) {
  152. $remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $row['remote']);
  153. }
  154. return $remoteShareIds;
  155. }
  156. }