setName('sharing:cleanup-remote-storages') ->setDescription('Cleanup shared storage entries that have no matching entry in the shares_external table') ->addOption( 'dry-run', null, InputOption::VALUE_NONE, 'only show which storages would be deleted' ); } public function execute(InputInterface $input, OutputInterface $output): int { $remoteStorages = $this->getRemoteStorages(); $output->writeln(count($remoteStorages) . ' remote storage(s) need(s) to be checked'); $remoteShareIds = $this->getRemoteShareIds(); $output->writeln(count($remoteShareIds) . ' remote share(s) exist'); foreach ($remoteShareIds as $id => $remoteShareId) { if (isset($remoteStorages[$remoteShareId])) { if ($input->getOption('dry-run') || $output->isVerbose()) { $output->writeln("$remoteShareId belongs to remote share $id"); } unset($remoteStorages[$remoteShareId]); } else { $output->writeln("$remoteShareId for share $id has no matching storage, yet"); } } if (empty($remoteStorages)) { $output->writeln('no storages deleted'); } else { $dryRun = $input->getOption('dry-run'); foreach ($remoteStorages as $id => $numericId) { if ($dryRun) { $output->writeln("$id [$numericId] can be deleted"); $this->countFiles($numericId, $output); } else { $this->deleteStorage($id, $numericId, $output); } } } return 0; } public function countFiles($numericId, OutputInterface $output) { $queryBuilder = $this->connection->getQueryBuilder(); $queryBuilder->select($queryBuilder->func()->count('fileid')) ->from('filecache') ->where($queryBuilder->expr()->eq( 'storage', $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR) ); $result = $queryBuilder->executeQuery(); $count = $result->fetchOne(); $result->closeCursor(); $output->writeln("$count files can be deleted for storage $numericId"); } public function deleteStorage($id, $numericId, OutputInterface $output) { $queryBuilder = $this->connection->getQueryBuilder(); $queryBuilder->delete('storages') ->where($queryBuilder->expr()->eq( 'id', $queryBuilder->createNamedParameter($id, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR) ); $output->write("deleting $id [$numericId] ... "); $count = $queryBuilder->executeStatement(); $output->writeln("deleted $count storage"); $this->deleteFiles($numericId, $output); } public function deleteFiles($numericId, OutputInterface $output) { $queryBuilder = $this->connection->getQueryBuilder(); $queryBuilder->delete('filecache') ->where($queryBuilder->expr()->eq( 'storage', $queryBuilder->createNamedParameter($numericId, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR) ); $output->write("deleting files for storage $numericId ... "); $count = $queryBuilder->executeStatement(); $output->writeln("deleted $count files"); } public function getRemoteStorages() { $queryBuilder = $this->connection->getQueryBuilder(); $queryBuilder->select(['id', 'numeric_id']) ->from('storages') ->where($queryBuilder->expr()->like( 'id', // match all 'shared::' + 32 characters storages $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::') . str_repeat('_', 32)), IQueryBuilder::PARAM_STR) ) ->andWhere($queryBuilder->expr()->notLike( 'id', // but not the ones starting with a '/', they are for normal shares $queryBuilder->createNamedParameter($this->connection->escapeLikeParameter('shared::/') . '%'), IQueryBuilder::PARAM_STR) ) ->orderBy('numeric_id'); $result = $queryBuilder->executeQuery(); $remoteStorages = []; while ($row = $result->fetch()) { $remoteStorages[$row['id']] = $row['numeric_id']; } $result->closeCursor(); return $remoteStorages; } public function getRemoteShareIds() { $queryBuilder = $this->connection->getQueryBuilder(); $queryBuilder->select(['id', 'share_token', 'owner', 'remote']) ->from('share_external'); $result = $queryBuilder->executeQuery(); $remoteShareIds = []; while ($row = $result->fetch()) { $cloudId = $this->cloudIdManager->getCloudId($row['owner'], $row['remote']); $remote = $cloudId->getRemote(); $remoteShareIds[$row['id']] = 'shared::' . md5($row['share_token'] . '@' . $remote); } $result->closeCursor(); return $remoteShareIds; } }