Signed-off-by: Robin Appelman <robin@icewind.nl>pull/25189/head
parent
7a892a310d
commit
291b72ec50
@ -0,0 +1,107 @@ |
||||
<?php |
||||
|
||||
declare(strict_types=1); |
||||
/** |
||||
* @copyright Copyright (c) 2021 Robin Appelman <robin@icewind.nl> |
||||
* |
||||
* @license GNU AGPL version 3 or any later version |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU Affero General Public License as |
||||
* published by the Free Software Foundation, either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU Affero General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU Affero General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
* |
||||
*/ |
||||
|
||||
namespace OCA\Files\Command; |
||||
|
||||
use OCP\IDBConnection; |
||||
use Symfony\Component\Console\Command\Command; |
||||
use Symfony\Component\Console\Input\InputInterface; |
||||
use Symfony\Component\Console\Output\OutputInterface; |
||||
|
||||
class RepairTree extends Command { |
||||
public const CHUNK_SIZE = 200; |
||||
|
||||
/** |
||||
* @var IDBConnection |
||||
*/ |
||||
protected $connection; |
||||
|
||||
public function __construct(IDBConnection $connection) { |
||||
$this->connection = $connection; |
||||
parent::__construct(); |
||||
} |
||||
|
||||
protected function configure() { |
||||
$this |
||||
->setName('files:repair-tree') |
||||
->setDescription('Try and repair malformed filesystem tree structures') |
||||
->addOption('dry-run'); |
||||
} |
||||
|
||||
public function execute(InputInterface $input, OutputInterface $output): int { |
||||
$rows = $this->findBrokenTreeBits(); |
||||
$fix = !$input->getOption('dry-run'); |
||||
|
||||
$output->writeln("Found " . count($rows) . " file entries with an invalid path"); |
||||
|
||||
if ($fix) { |
||||
$this->connection->beginTransaction(); |
||||
} |
||||
|
||||
$query = $this->connection->getQueryBuilder(); |
||||
$query->update('filecache') |
||||
->set('path', $query->createParameter('path')) |
||||
->set('path_hash', $query->func()->md5($query->createParameter('path'))) |
||||
->where($query->expr()->eq('fileid', $query->createParameter('fileid'))); |
||||
|
||||
foreach ($rows as $row) { |
||||
$output->writeln("Path of file ${row['fileid']} is ${row['path']} but should be ${row['parent_path']}/${row['name']} based on it's parent", OutputInterface::VERBOSITY_VERBOSE); |
||||
|
||||
if ($fix) { |
||||
$query->setParameters([ |
||||
'fileid' => $row['fileid'], |
||||
'path' => $row['parent_path'] . '/' . $row['name'], |
||||
]); |
||||
$query->execute(); |
||||
} |
||||
} |
||||
|
||||
if ($fix) { |
||||
$this->connection->commit(); |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
private function findBrokenTreeBits(): array { |
||||
$query = $this->connection->getQueryBuilder(); |
||||
|
||||
$query->select('f.fileid', 'f.path', 'f.parent', 'f.name') |
||||
->selectAlias('p.path', 'parent_path') |
||||
->from('filecache', 'f') |
||||
->innerJoin('f', 'filecache', 'p', $query->expr()->eq('f.parent', 'p.fileid')) |
||||
->where($query->expr()->orX( |
||||
$query->expr()->andX( |
||||
$query->expr()->neq('p.path_hash', $query->createNamedParameter(md5(''))), |
||||
$query->expr()->neq('f.path', $query->func()->concat('p.path', $query->func()->concat($query->createNamedParameter('/'), 'f.name'))) |
||||
), |
||||
$query->expr()->andX( |
||||
$query->expr()->eq('p.path_hash', $query->createNamedParameter(md5(''))), |
||||
$query->expr()->neq('f.path', 'f.name') |
||||
), |
||||
$query->expr()->neq('f.storage', 'p.storage') |
||||
)); |
||||
|
||||
return $query->execute()->fetchAll(); |
||||
} |
||||
} |
Loading…
Reference in new issue