Skip to content

Commit

Permalink
Merge pull request #34499 from owncloud/backport-29830
Browse files Browse the repository at this point in the history
[stable10] Repair step list and execution from occ
  • Loading branch information
Vincent Petry authored Feb 15, 2019
2 parents 4c0a16e + 3c5565e commit a44289a
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 44 deletions.
159 changes: 122 additions & 37 deletions core/Command/Maintenance/Repair.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* @author Robin Appelman <icewind@owncloud.com>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Vincent Petry <pvince81@owncloud.com>
* @author Semih Serhat Karakaya <karakayasemi@itu.edu.tr>
*
* @copyright Copyright (c) 2018, ownCloud GmbH
* @license AGPL-3.0
Expand All @@ -26,7 +27,9 @@
namespace OC\Core\Command\Maintenance;

use Exception;
use OCP\App\IAppManager;
use OCP\IConfig;
use OCP\Migration\IRepairStep;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
Expand All @@ -38,30 +41,52 @@
class Repair extends Command {
/** @var \OC\Repair $repair */
protected $repair;
/** @var IConfig */
/** @var IConfig $config */
protected $config;
/** @var EventDispatcherInterface */
/** @var EventDispatcherInterface $dispatcher */
private $dispatcher;
/** @var IAppManager $appManager */
private $appManager;
/** @var ProgressBar */
private $progress;
/** @var OutputInterface */
/** @var OutputInterface $output */
private $output;

/**
* @param \OC\Repair $repair
* @param IConfig $config
* @param EventDispatcherInterface $dispatcher
* @param IAppManager $appManager
*/
public function __construct(\OC\Repair $repair, IConfig $config, EventDispatcherInterface $dispatcher) {
public function __construct(
\OC\Repair $repair,
IConfig $config,
EventDispatcherInterface $dispatcher,
IAppManager $appManager
) {
$this->repair = $repair;
$this->config = $config;
$this->dispatcher = $dispatcher;
$this->appManager = $appManager;
parent::__construct();
}

protected function configure() {
$this
->setName('maintenance:repair')
->setDescription('Repair the installation.')
->addOption(
'list',
null,
InputOption::VALUE_NONE,
'Lists all possible repair steps'
)
->addOption(
'single',
's',
InputOption::VALUE_REQUIRED,
'Run just one repair step given its class name'
)
->addOption(
'include-expensive',
null,
Expand All @@ -70,49 +95,67 @@ protected function configure() {
}

protected function execute(InputInterface $input, OutputInterface $output) {
$includeExpensive = $input->getOption('include-expensive');
if ($includeExpensive) {
foreach ($this->repair->getExpensiveRepairSteps() as $step) {
$this->repair->addStep($step);
$appSteps = $this->getAppsRepairSteps($output);
// Handle listing repair steps
$steps = \array_merge(
\OC\Repair::getRepairSteps(),
\OC\Repair::getExpensiveRepairSteps(),
$appSteps
);
$list = $input->getOption('list');
if ($list) {
$output->writeln("Found ".\count($steps)." repair steps");
$output->writeln("");
foreach ($steps as $step) {
$output->writeln(\get_class($step) . " -> " . $step->getName());
}
return 0;
}

$apps = \OC::$server->getAppManager()->getInstalledApps();
foreach ($apps as $app) {
if (!\OC_App::isEnabled($app)) {
continue;
}
$info = \OC_App::getAppInfo($app);
if (!\is_array($info)) {
continue;
$maintenanceMode = $this->config->getSystemValue('maintenance', false);
if ($maintenanceMode !== true) {
$output->writeln("<error>Turn on maintenance mode to use this command.");
return 0;
}

// Handle running just one repair step
$single = $input->getOption('single');
if ($single) {
// Check it exists
$stepNames = \array_map('get_class', $steps);
if (!\in_array($single, $stepNames, true)) {
$output->writeln("<error>Repair step not found. Use --list to show available steps.");
return 1;
}
\OC_App::loadApp($app);
$steps = $info['repair-steps']['post-migration'];
// Find step and create repair manager
$repair = new \OC\Repair([], $this->dispatcher);
foreach ($steps as $step) {
try {
$this->repair->addStep($step);
} catch (Exception $ex) {
$output->writeln("<error>Failed to load repair step for $app: {$ex->getMessage()}</error>");
if (\get_class($step) === $single) {
$repair->addStep($step);
break;
}
}

$this->initializeProgressBar($output);
$repair->run();
return 0;
}

$maintenanceMode = $this->config->getSystemValue('maintenance', false);
$this->config->setSystemValue('maintenance', true);
$includeExpensive = $input->getOption('include-expensive');
if ($includeExpensive) {
foreach ($this->repair->getExpensiveRepairSteps() as $step) {
$this->repair->addStep($step);
}
}

$this->progress = new ProgressBar($output);
$this->output = $output;
$this->dispatcher->addListener('\OC\Repair::startProgress', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::advance', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::finishProgress', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::step', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::info', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::warning', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::error', [$this, 'handleRepairFeedBack']);
//add all possible steps and run
foreach ($appSteps as $step) {
$this->repair->addStep($step);
}

$this->initializeProgressBar($output);
$this->repair->run();

$this->config->setSystemValue('maintenance', $maintenanceMode);
return 0;
}

public function handleRepairFeedBack($event) {
Expand All @@ -131,10 +174,10 @@ public function handleRepairFeedBack($event) {
$this->output->writeln('');
break;
case '\OC\Repair::step':
$this->output->writeln(' - ' . $event->getArgument(0));
$this->output->writeln(' - Step: ' . $event->getArgument(0));
break;
case '\OC\Repair::info':
$this->output->writeln(' - ' . $event->getArgument(0));
$this->output->writeln(' - INFO: ' . $event->getArgument(0));
break;
case '\OC\Repair::warning':
$this->output->writeln(' - WARNING: ' . $event->getArgument(0));
Expand All @@ -144,4 +187,46 @@ public function handleRepairFeedBack($event) {
break;
}
}

/**
* @param OutputInterface $output
* @@return IRepairStep[]
*/
public function getAppsRepairSteps(OutputInterface $output) {
$apps = $this->appManager->getEnabledAppsForUser();
$steps = [];
foreach ($apps as $app) {
try {
$info = $this->appManager->getAppInfo($app);
if (!\is_array($info)) {
continue;
}
\OC_App::loadApp($app);
$appSteps = $info['repair-steps']['post-migration'];
foreach ($appSteps as $step) {
\array_push($steps, \OC::$server->query($step));
}
} catch (\OC\NeedsUpdateException $ex) {
$output->writeln("<error>ownCloud or one of the apps require upgrade.</error>");
} catch (Exception $ex) {
$output->writeln("<error>Failed to load repair step for $app: {$ex->getMessage()}</error>");
}
}
return $steps;
}

/**
* @param OutputInterface $output
*/
protected function initializeProgressBar(OutputInterface $output) {
$this->progress = new ProgressBar($output);
$this->output = $output;
$this->dispatcher->addListener('\OC\Repair::startProgress', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::advance', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::finishProgress', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::step', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::info', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::warning', [$this, 'handleRepairFeedBack']);
$this->dispatcher->addListener('\OC\Repair::error', [$this, 'handleRepairFeedBack']);
}
}
7 changes: 5 additions & 2 deletions core/register_command.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,11 @@

$application->add(new OC\Core\Command\Upgrade(\OC::$server->getConfig(), \OC::$server->getLogger()));
$application->add(new OC\Core\Command\Maintenance\Repair(
new \OC\Repair(\OC\Repair::getRepairSteps(), \OC::$server->getEventDispatcher()), \OC::$server->getConfig(),
\OC::$server->getEventDispatcher()));
new \OC\Repair(\OC\Repair::getRepairSteps(), \OC::$server->getEventDispatcher()),
\OC::$server->getConfig(),
\OC::$server->getEventDispatcher(),
\OC::$server->getAppManager())
);

$application->add(new OC\Core\Command\User\Add(\OC::$server->getUserManager(), \OC::$server->getGroupManager(), \OC::$server->getMailer()));
$application->add(new OC\Core\Command\User\Delete(\OC::$server->getUserManager()));
Expand Down
9 changes: 4 additions & 5 deletions lib/private/Repair.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

namespace OC;

use OC\Repair\Apps;
use OC\Repair\CleanTags;
use OC\Repair\Collation;
use OC\Repair\DisableExtraThemes;
Expand All @@ -52,14 +51,14 @@
use OCP\AppFramework\QueryException;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\GenericEvent;
use OC\Repair\MoveAvatarOutsideHome;

class Repair implements IOutput {
/* @var IRepairStep[] */
private $repairSteps;
/** @var EventDispatcher */
/** @var EventDispatcherInterface */
private $dispatcher;
/** @var string */
private $currentStep;
Expand All @@ -68,9 +67,9 @@ class Repair implements IOutput {
* Creates a new repair step runner
*
* @param IRepairStep[] $repairSteps array of RepairStep instances
* @param EventDispatcher $dispatcher
* @param EventDispatcherInterface $dispatcher
*/
public function __construct($repairSteps = [], EventDispatcher $dispatcher = null) {
public function __construct($repairSteps = [], EventDispatcherInterface $dispatcher = null) {
$this->repairSteps = $repairSteps;
$this->dispatcher = $dispatcher;
}
Expand Down
2 changes: 2 additions & 0 deletions lib/private/Repair/MoveAvatarOutsideHome.php
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ public function run(IOutput $output) {
$this->userManager->callForSeenUsers($function);

$output->finishProgress();
} else {
$output->info("No action required");
}
}
}
87 changes: 87 additions & 0 deletions tests/Core/Command/Maintenance/RepairTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php
/**
* @author Semih Serhat Karakaya <karakayasemi@itu.edu.tr>
*
* @copyright Copyright (c) 2019, ownCloud GmbH
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* 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, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/

namespace Tests\Core\Command\User;

use OC\Core\Command\Maintenance\Repair;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Test\TestCase;

/**
* Class RepairTest
*
* @group DB
*/
class RepairTest extends TestCase {

/** @var CommandTester */
private $commandTester;

protected function setUp() {
parent::setUp();

$application = new Application(
\OC::$server->getConfig(),
\OC::$server->getEventDispatcher(),
\OC::$server->getRequest()
);
$command = new Repair(
new \OC\Repair(\OC\Repair::getRepairSteps(), \OC::$server->getEventDispatcher()),
\OC::$server->getConfig(),
\OC::$server->getEventDispatcher(),
\OC::$server->getAppManager()
);
$command->setApplication($application);
$this->commandTester = new CommandTester($command);
}

protected function tearDown() {
parent::tearDown();
\OC::$server->getConfig()->setSystemValue('maintenance', false);
}

/**
* @dataProvider inputProvider
* @param array $input
* @param boolean $maintenanceMode
* @param integer $returnValue
* @param string $expectedOutput
*/
public function testCommandInput($input, $maintenanceMode, $returnValue, $expectedOutput) {
\OC::$server->getConfig()->setSystemValue('maintenance', $maintenanceMode);
$result = $this->commandTester->execute($input);
$this->assertEquals($result, $returnValue);
$output = $this->commandTester->getDisplay();
$this->assertContains($expectedOutput, $output);
}

public function inputProvider() {
return [
[['--list' => true], true, 0, 'Found'],
[[], false, 0, 'Turn on maintenance mode to use this command'],
[['--single' => '\OC\UnexistingClass'], true, 1, 'Repair step not found'],
[['--single' => 'OC\Repair\RepairMimeTypes'], true, 0, 'Repair mime types'],
[[], true, 0, '100%'],
[['--include-expensive' => true], true, 0, 'Remove shares of old group memberships']
];
}
}

0 comments on commit a44289a

Please sign in to comment.