From 1bf4c75e8bfd32160ee7316c492ddc436f673f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Wed, 30 Mar 2016 23:38:26 +0200 Subject: [PATCH 1/4] Show individual sql schema migration steps during upgrade - on web as well as on the command line --- core/ajax/update.php | 12 ++++++ core/command/upgrade.php | 20 +++++++++ core/css/update.css | 9 ++++ core/js/update.js | 64 ++++++++++++++++++++-------- core/templates/update.admin.php | 8 +++- lib/base.php | 1 + lib/private/db/mdb2schemamanager.php | 19 +++++---- lib/private/db/migrator.php | 26 +++++++++-- 8 files changed, 128 insertions(+), 31 deletions(-) create mode 100644 core/css/update.css diff --git a/core/ajax/update.php b/core/ajax/update.php index 4d8fe19f16..bb4738c83a 100644 --- a/core/ajax/update.php +++ b/core/ajax/update.php @@ -26,6 +26,8 @@ * along with this program. If not, see * */ +use Symfony\Component\EventDispatcher\GenericEvent; + set_time_limit(0); require_once '../../lib/base.php'; @@ -53,6 +55,13 @@ if (OC::checkUpgrade(false)) { $incompatibleApps = []; $disabledThirdPartyApps = []; + $dispatcher = \OC::$server->getEventDispatcher(); + $dispatcher->addListener('\OC\DB\Migrator::executeSql', function($event) use ($eventSource, $l) { + if ($event instanceof GenericEvent) { + $eventSource->send('success', (string)$l->t('[%d / %d]: %s', [$event[0], $event[1], $event->getSubject()])); + } + }); + $updater->listen('\OC\Updater', 'maintenanceEnabled', function () use ($eventSource, $l) { $eventSource->send('success', (string)$l->t('Turned on maintenance mode')); }); @@ -132,6 +141,9 @@ if (OC::checkUpgrade(false)) { $disabledApps[$app] = (string) $l->t('%s (incompatible)', [$app]); } + $disabledApps=[ + 'Contacts Plus (incompatible)' + ]; if (!empty($disabledApps)) { $eventSource->send('notice', (string)$l->t('Following apps have been disabled: %s', implode(', ', $disabledApps))); diff --git a/core/command/upgrade.php b/core/command/upgrade.php index c45984d7a3..fbabf67a16 100644 --- a/core/command/upgrade.php +++ b/core/command/upgrade.php @@ -34,9 +34,11 @@ use OC\Updater; use OCP\IConfig; use OCP\ILogger; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\ProgressBar; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\EventDispatcher\GenericEvent; class Upgrade extends Command { @@ -135,6 +137,24 @@ class Upgrade extends Command { $updater->setSimulateStepEnabled($simulateStepEnabled); $updater->setUpdateStepEnabled($updateStepEnabled); $updater->setSkip3rdPartyAppsDisable($skip3rdPartyAppsDisable); + $dispatcher = \OC::$server->getEventDispatcher(); + $progress = new ProgressBar($output); + $progress->setFormat("%message%\n %current%/%max% [%bar%] %percent:3s%%"); + $dispatcher->addListener('\OC\DB\Migrator::executeSql', function($event) use ($progress, $output) { + if ($event instanceof GenericEvent) { + if ($event[0] === 1) { + $output->writeln(''); + $progress->start($event[1]); + } + $progress->setMessage($event->getSubject()); + $progress->setProgress($event[0]); + $progress->display(); + if ($event[0] === $event[1]) { + $progress->finish(); + $output->writeln(''); + } + } + }); $updater->listen('\OC\Updater', 'maintenanceEnabled', function () use($output) { $output->writeln('Turned on maintenance mode'); diff --git a/core/css/update.css b/core/css/update.css new file mode 100644 index 0000000000..34b78a4fa9 --- /dev/null +++ b/core/css/update.css @@ -0,0 +1,9 @@ +#update-progress-icon { + height: 32px; + margin: 10px; + background-size: 32px; +} + +#update-progress-message { + margin-bottom: 10px; +} \ No newline at end of file diff --git a/core/js/update.js b/core/js/update.js index 77ac1bb20f..d15609ad47 100644 --- a/core/js/update.js +++ b/core/js/update.js @@ -28,38 +28,47 @@ this._started = true; + var self = this; + $(window).on('beforeunload.inprogress', function () { return t('core', 'The upgrade is in progress, leaving this page might interrupt the process in some environments.'); }); - this.addMessage(t( + $('#update-progress-title').html(t( 'core', - 'Updating {productName} to version {version}, this may take a while.', { - productName: options.productName || 'ownCloud', + 'Updating to {version}', { version: options.version - }), - 'bold' - ).append('
'); // FIXME: these should be ul/li with CSS paddings! + }) + ); var updateEventSource = new OC.EventSource(OC.webroot+'/core/ajax/update.php'); updateEventSource.listen('success', function(message) { - $('').append(message).append('
').appendTo($el); + self.setMessage(message); }); updateEventSource.listen('notice', function(message) { - $('').addClass('error').append(message).append('
').appendTo($el); + self.setPermanentMessage(message); hasWarnings = true; }); updateEventSource.listen('error', function(message) { + $('#update-progress-message').hide(); + $('#update-progress-icon') + .addClass('icon-error-white') + .removeClass('icon-loading-dark'); message = message || t('core', 'An error occurred.'); $(window).off('beforeunload.inprogress'); - $('').addClass('error').append(message).append('
').appendTo($el); + self.setErrorMessage(message); message = t('core', 'Please reload the page.'); $('').addClass('error').append(''+message+'
').appendTo($el); updateEventSource.close(); }); updateEventSource.listen('failure', function(message) { $(window).off('beforeunload.inprogress'); - $('').addClass('error').append(message).append('
').appendTo($el); + $('#update-progress-message').hide(); + $('#update-progress-icon') + .addClass('icon-error-white') + .removeClass('icon-loading-dark'); + + self.setErrorMessage(message); var span = $('') .addClass('bold'); if(message === 'Exception: Updates between multiple major versions and downgrades are unsupported.') { @@ -74,8 +83,14 @@ updateEventSource.listen('done', function() { $(window).off('beforeunload.inprogress'); + $('#update-progress-message').hide(); + + $('#update-progress-icon') + .addClass('icon-checkmark-white') + .removeClass('icon-loading-dark'); + if (hasWarnings) { - $('').addClass('bold') + $('') .append('
') .append(t('core', 'The update was successful. There were warnings.')) .appendTo($el); @@ -83,7 +98,7 @@ $('').append('
').append(message).append('
').appendTo($el); } else { // FIXME: use product name - $('').addClass('bold') + $('') .append('
') .append(t('core', 'The update was successful. Redirecting you to ownCloud now.')) .appendTo($el); @@ -94,10 +109,21 @@ }); }, - addMessage: function(message, className) { - var $span = $(''); - $span.addClass(className).append(message).append('
').appendTo(this.$el); - return $span; + setMessage: function(message) { + $('#update-progress-message').html(message); + }, + + setPermanentMessage: function(message) { + $('#update-progress-message').html(message); + $('#update-progress-message-warnings') + .show() + .append($('
    ').append(message)) + }, + + setErrorMessage: function (message) { + $('#update-progress-message-error') + .show() + .html(message); } }; @@ -106,12 +132,14 @@ $(document).ready(function() { $('.updateButton').on('click', function() { var $updateEl = $('.update'); - var $progressEl = $('.updateProgress'); + var $progressEl = $('.update-progress'); $progressEl.removeClass('hidden'); $('.updateOverview').addClass('hidden'); + $('#update-progress-message-error').hide(); + $('#update-progress-message-warnings').hide(); OC.Update.start($progressEl, { productName: $updateEl.attr('data-productname'), - version: $updateEl.attr('data-version'), + version: $updateEl.attr('data-version') }); return false; }); diff --git a/core/templates/update.admin.php b/core/templates/update.admin.php index 75815de84b..31b8120764 100644 --- a/core/templates/update.admin.php +++ b/core/templates/update.admin.php @@ -41,5 +41,11 @@ - + diff --git a/lib/base.php b/lib/base.php index f3076a1181..706322fb54 100644 --- a/lib/base.php +++ b/lib/base.php @@ -365,6 +365,7 @@ class OC { $systemConfig->setValue('theme', ''); \OCP\Util::addScript('config'); // needed for web root \OCP\Util::addScript('update'); + \OCP\Util::addStyle('update'); // check whether this is a core update or apps update $installedVersion = $systemConfig->getValue('version', '0.0.0'); diff --git a/lib/private/db/mdb2schemamanager.php b/lib/private/db/mdb2schemamanager.php index bcabb6fe57..f73f6b4351 100644 --- a/lib/private/db/mdb2schemamanager.php +++ b/lib/private/db/mdb2schemamanager.php @@ -32,15 +32,14 @@ use Doctrine\DBAL\Platforms\MySqlPlatform; use Doctrine\DBAL\Platforms\OraclePlatform; use Doctrine\DBAL\Platforms\PostgreSqlPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; +use OCP\IDBConnection; class MDB2SchemaManager { - /** - * @var \OC\DB\Connection $conn - */ + /** @var \OC\DB\Connection $conn */ protected $conn; /** - * @param \OCP\IDBConnection $conn + * @param IDBConnection $conn */ public function __construct($conn) { $this->conn = $conn; @@ -77,16 +76,17 @@ class MDB2SchemaManager { $random = \OC::$server->getSecureRandom(); $platform = $this->conn->getDatabasePlatform(); $config = \OC::$server->getConfig(); + $dispatcher = \OC::$server->getEventDispatcher(); if ($platform instanceof SqlitePlatform) { - return new SQLiteMigrator($this->conn, $random, $config); + return new SQLiteMigrator($this->conn, $random, $config, $dispatcher); } else if ($platform instanceof OraclePlatform) { - return new OracleMigrator($this->conn, $random, $config); + return new OracleMigrator($this->conn, $random, $config, $dispatcher); } else if ($platform instanceof MySqlPlatform) { - return new MySQLMigrator($this->conn, $random, $config); + return new MySQLMigrator($this->conn, $random, $config, $dispatcher); } else if ($platform instanceof PostgreSqlPlatform) { - return new Migrator($this->conn, $random, $config); + return new Migrator($this->conn, $random, $config, $dispatcher); } else { - return new NoCheckMigrator($this->conn, $random, $config); + return new NoCheckMigrator($this->conn, $random, $config, $dispatcher); } } @@ -94,6 +94,7 @@ class MDB2SchemaManager { * Reads database schema from file * * @param string $file file to read from + * @return \Doctrine\DBAL\Schema\Schema */ private function readSchemaFromFile($file) { $platform = $this->conn->getDatabasePlatform(); diff --git a/lib/private/db/migrator.php b/lib/private/db/migrator.php index 7ca3f98135..f477da3bad 100644 --- a/lib/private/db/migrator.php +++ b/lib/private/db/migrator.php @@ -35,6 +35,8 @@ use \Doctrine\DBAL\Schema\SchemaConfig; use \Doctrine\DBAL\Schema\Comparator; use OCP\IConfig; use OCP\Security\ISecureRandom; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\EventDispatcher\GenericEvent; class Migrator { @@ -51,15 +53,23 @@ class Migrator { /** @var IConfig */ protected $config; + /** @var EventDispatcher */ + private $dispatcher; + /** - * @param Connection $connection + * @param \Doctrine\DBAL\Connection|Connection $connection * @param ISecureRandom $random * @param IConfig $config + * @param EventDispatcher $dispatcher */ - public function __construct(\Doctrine\DBAL\Connection $connection, ISecureRandom $random, IConfig $config) { + public function __construct(\Doctrine\DBAL\Connection $connection, + ISecureRandom $random, + IConfig $config, + EventDispatcher $dispatcher = null) { $this->connection = $connection; $this->random = $random; $this->config = $config; + $this->dispatcher = $dispatcher; } /** @@ -215,7 +225,10 @@ class Migrator { $schemaDiff = $this->getDiff($targetSchema, $connection); $connection->beginTransaction(); - foreach ($schemaDiff->toSql($connection->getDatabasePlatform()) as $sql) { + $sqls = $schemaDiff->toSql($connection->getDatabasePlatform()); + $step = 0; + foreach ($sqls as $sql) { + $this->emit($sql, $step++, count($sqls)); $connection->query($sql); } $connection->commit(); @@ -254,4 +267,11 @@ class Migrator { protected function getFilterExpression() { return '/^' . preg_quote($this->config->getSystemValue('dbtableprefix', 'oc_')) . '/'; } + + protected function emit($sql, $step, $max) { + if(is_null($this->dispatcher)) { + return; + } + $this->dispatcher->dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step+1, $max])); + } } From 15339efd60ebcb4b140fc0e27f66ed9541f9d77f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 31 Mar 2016 12:41:57 +0200 Subject: [PATCH 2/4] Limit the message to the console to the first 30 characters in order to not break the progress bar --- core/command/upgrade.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/command/upgrade.php b/core/command/upgrade.php index fbabf67a16..c2de699d86 100644 --- a/core/command/upgrade.php +++ b/core/command/upgrade.php @@ -146,7 +146,11 @@ class Upgrade extends Command { $output->writeln(''); $progress->start($event[1]); } - $progress->setMessage($event->getSubject()); + $message = $event->getSubject(); + if (strlen($message) > 30) { + $message = substr($message, 0, 27) . '...'; + } + $progress->setMessage($message); $progress->setProgress($event[0]); $progress->display(); if ($event[0] === $event[1]) { From 1f7e02e4d4fa918d933b904abf12fca6fda2c526 Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Thu, 31 Mar 2016 14:27:01 +0200 Subject: [PATCH 3/4] Add detailed logs hidden and show them on request --- core/ajax/update.php | 3 --- core/css/update.css | 17 +++++++++++++++++ core/js/update.js | 30 +++++++++++++++++++++++------- core/templates/update.admin.php | 2 ++ 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/core/ajax/update.php b/core/ajax/update.php index bb4738c83a..8ed0fad57e 100644 --- a/core/ajax/update.php +++ b/core/ajax/update.php @@ -141,9 +141,6 @@ if (OC::checkUpgrade(false)) { $disabledApps[$app] = (string) $l->t('%s (incompatible)', [$app]); } - $disabledApps=[ - 'Contacts Plus (incompatible)' - ]; if (!empty($disabledApps)) { $eventSource->send('notice', (string)$l->t('Following apps have been disabled: %s', implode(', ', $disabledApps))); diff --git a/core/css/update.css b/core/css/update.css index 34b78a4fa9..b1e086af3f 100644 --- a/core/css/update.css +++ b/core/css/update.css @@ -6,4 +6,21 @@ #update-progress-message { margin-bottom: 10px; +} + +.update-show-detailed { + padding: 13px; + display: block; +} + +#body-login .update a.update-show-detailed { + border-bottom: inherit; +} + +#update-progress-detailed { + text-align: left; +} + +#body-login .warning.hidden { + display: none; } \ No newline at end of file diff --git a/core/js/update.js b/core/js/update.js index d15609ad47..31b97c5689 100644 --- a/core/js/update.js +++ b/core/js/update.js @@ -90,16 +90,16 @@ .removeClass('icon-loading-dark'); if (hasWarnings) { - $('') - .append('
    ') - .append(t('core', 'The update was successful. There were warnings.')) - .appendTo($el); + $el.find('.update-show-detailed').before( + $('') + .append('
    ') + .append(t('core', 'The update was successful. There were warnings.')) + ); var message = t('core', 'Please reload the page.'); - $('').append('
    ').append(message).append('
    ').appendTo($el); + $('').append(message).append('
    ').appendTo($el); } else { // FIXME: use product name $('') - .append('
    ') .append(t('core', 'The update was successful. Redirecting you to ownCloud now.')) .appendTo($el); setTimeout(function () { @@ -111,19 +111,31 @@ setMessage: function(message) { $('#update-progress-message').html(message); + $('#update-progress-detailed') + .append($('')) + .append(message) + .append($('
    ')); }, setPermanentMessage: function(message) { $('#update-progress-message').html(message); $('#update-progress-message-warnings') .show() - .append($('
      ').append(message)) + .append($('
        ').append(message)); + $('#update-progress-detailed') + .append($('')) + .append(message) + .append($('
        ')); }, setErrorMessage: function (message) { $('#update-progress-message-error') .show() .html(message); + $('#update-progress-detailed') + .append($('')) + .append(message) + .append($('
        ')); } }; @@ -143,4 +155,8 @@ $(document).ready(function() { }); return false; }); + $('.update-show-detailed').on('click', function() { + $('#update-progress-detailed').toggleClass('hidden'); + return false; + }); }); diff --git a/core/templates/update.admin.php b/core/templates/update.admin.php index 31b8120764..24b2beec6d 100644 --- a/core/templates/update.admin.php +++ b/core/templates/update.admin.php @@ -47,5 +47,7 @@

        + t( 'Detailed logs' )); ?> + From 4b79fb10a28d9edbace8ec44dfb43861c5570a64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Mon, 4 Apr 2016 16:20:53 +0200 Subject: [PATCH 4/4] Fix verbose output of upgrade command - not progressbar in this case and the schema migration test has one progressbar now for all tables - before we had one progressbar for each table --- core/ajax/update.php | 5 +++++ core/command/upgrade.php | 38 +++++++++++++++++++++---------------- lib/private/db/migrator.php | 21 +++++++++++++++++--- 3 files changed, 45 insertions(+), 19 deletions(-) diff --git a/core/ajax/update.php b/core/ajax/update.php index 8ed0fad57e..631a8a7871 100644 --- a/core/ajax/update.php +++ b/core/ajax/update.php @@ -61,6 +61,11 @@ if (OC::checkUpgrade(false)) { $eventSource->send('success', (string)$l->t('[%d / %d]: %s', [$event[0], $event[1], $event->getSubject()])); } }); + $dispatcher->addListener('\OC\DB\Migrator::checkTable', function($event) use ($eventSource, $l) { + if ($event instanceof GenericEvent) { + $eventSource->send('success', (string)$l->t('[%d / %d]: Checking table %s', [$event[0], $event[1], $event->getSubject()])); + } + }); $updater->listen('\OC\Updater', 'maintenanceEnabled', function () use ($eventSource, $l) { $eventSource->send('success', (string)$l->t('Turned on maintenance mode')); diff --git a/core/command/upgrade.php b/core/command/upgrade.php index c2de699d86..cbb1f26f93 100644 --- a/core/command/upgrade.php +++ b/core/command/upgrade.php @@ -139,26 +139,32 @@ class Upgrade extends Command { $updater->setSkip3rdPartyAppsDisable($skip3rdPartyAppsDisable); $dispatcher = \OC::$server->getEventDispatcher(); $progress = new ProgressBar($output); - $progress->setFormat("%message%\n %current%/%max% [%bar%] %percent:3s%%"); - $dispatcher->addListener('\OC\DB\Migrator::executeSql', function($event) use ($progress, $output) { + $progress->setFormat(" %message%\n %current%/%max% [%bar%] %percent:3s%%"); + $listener = function($event) use ($progress, $output) { if ($event instanceof GenericEvent) { - if ($event[0] === 1) { - $output->writeln(''); - $progress->start($event[1]); - } $message = $event->getSubject(); - if (strlen($message) > 30) { - $message = substr($message, 0, 27) . '...'; - } - $progress->setMessage($message); - $progress->setProgress($event[0]); - $progress->display(); - if ($event[0] === $event[1]) { - $progress->finish(); - $output->writeln(''); + if (OutputInterface::VERBOSITY_NORMAL < $output->getVerbosity()) { + $output->writeln(' Checking table ' . $message); + } else { + if (strlen($message) > 60) { + $message = substr($message, 0, 57) . '...'; + } + $progress->setMessage($message); + if ($event[0] === 1) { + $output->writeln(''); + $progress->start($event[1]); + } + $progress->setProgress($event[0]); + if ($event[0] === $event[1]) { + $progress->setMessage('Done'); + $progress->finish(); + $output->writeln(''); + } } } - }); + }; + $dispatcher->addListener('\OC\DB\Migrator::executeSql', $listener); + $dispatcher->addListener('\OC\DB\Migrator::checkTable', $listener); $updater->listen('\OC\Updater', 'maintenanceEnabled', function () use($output) { $output->writeln('Turned on maintenance mode'); diff --git a/lib/private/db/migrator.php b/lib/private/db/migrator.php index f477da3bad..8b8a34d986 100644 --- a/lib/private/db/migrator.php +++ b/lib/private/db/migrator.php @@ -56,6 +56,9 @@ class Migrator { /** @var EventDispatcher */ private $dispatcher; + /** @var bool */ + private $noEmit = false; + /** * @param \Doctrine\DBAL\Connection|Connection $connection * @param ISecureRandom $random @@ -76,6 +79,7 @@ class Migrator { * @param \Doctrine\DBAL\Schema\Schema $targetSchema */ public function migrate(Schema $targetSchema) { + $this->noEmit = true; $this->applySchema($targetSchema); } @@ -100,21 +104,22 @@ class Migrator { * @throws \OC\DB\MigrationException */ public function checkMigrate(Schema $targetSchema) { - /** - * @var \Doctrine\DBAL\Schema\Table[] $tables - */ + $this->noEmit = true; + /**@var \Doctrine\DBAL\Schema\Table[] $tables */ $tables = $targetSchema->getTables(); $filterExpression = $this->getFilterExpression(); $this->connection->getConfiguration()-> setFilterSchemaAssetsExpression($filterExpression); $existingTables = $this->connection->getSchemaManager()->listTableNames(); + $step = 0; foreach ($tables as $table) { if (strpos($table->getName(), '.')) { list(, $tableName) = explode('.', $table->getName()); } else { $tableName = $table->getName(); } + $this->emitCheckStep($tableName, $step++, count($tables)); // don't need to check for new tables if (array_search($tableName, $existingTables) !== false) { $this->checkTableMigrate($table); @@ -269,9 +274,19 @@ class Migrator { } protected function emit($sql, $step, $max) { + if ($this->noEmit) { + return; + } if(is_null($this->dispatcher)) { return; } $this->dispatcher->dispatch('\OC\DB\Migrator::executeSql', new GenericEvent($sql, [$step+1, $max])); } + + private function emitCheckStep($tableName, $step, $max) { + if(is_null($this->dispatcher)) { + return; + } + $this->dispatcher->dispatch('\OC\DB\Migrator::checkTable', new GenericEvent($tableName, [$step+1, $max])); + } }