aboutsummaryrefslogtreecommitdiffstats
path: root/core/command
diff options
context:
space:
mode:
authorAndreas Fischer <bantu@owncloud.com>2014-05-28 22:57:33 +0200
committerAndreas Fischer <bantu@owncloud.com>2014-05-28 22:57:33 +0200
commit5754b0b9e70824786878b847abb6b728ca0414bb (patch)
tree9ad1fbc35edde8f54f44c9ef7029e007a16815b9 /core/command
parentf81ee94cad8a997c3a2fef0b6aac4f8d509571f6 (diff)
parentce9d5df6df37e51587dcde638086dfe501892b56 (diff)
downloadnextcloud-server-5754b0b9e70824786878b847abb6b728ca0414bb.tar.gz
nextcloud-server-5754b0b9e70824786878b847abb6b728ca0414bb.zip
Merge remote-tracking branch 'owncloud/master' into add_resetadminpass_command
* owncloud/master: (238 commits) Change visibility of scanner internals [tx-robot] updated from transifex remove legacy OC_Filesystem being used in a hook callback add title property to share dialog forgotten infobox messages translations reverts 188c543 and translates only mail fix warning text and margin Adjust core apps to use "requiremin" instead of "require" Added requiremin/requiremax fields for apps [tx-robot] updated from transifex unwrapped strings fix allow resharing of files with only share permissions don't lose file size during rename drop superflous statement in phpdoc add preRememberedLogin hook and document this and postRememberedLogin in class descripttion. Also fixes documentation of postLogin hook [tx-robot] updated from transifex fix grammar make user_ldap fully translatable [tx-robot] updated from transifex fix typo ... Conflicts: core/register_command.php
Diffstat (limited to 'core/command')
-rw-r--r--core/command/db/converttype.php295
-rw-r--r--core/command/maintenance/repair.php2
-rw-r--r--core/command/user/lastseen.php47
3 files changed, 343 insertions, 1 deletions
diff --git a/core/command/db/converttype.php b/core/command/db/converttype.php
new file mode 100644
index 00000000000..39e87853d60
--- /dev/null
+++ b/core/command/db/converttype.php
@@ -0,0 +1,295 @@
+<?php
+/**
+ * Copyright (c) 2013 Bart Visscher <bartv@thisnet.nl>
+ * Copyright (c) 2014 Andreas Fischer <bantu@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ *
+ */
+
+namespace OC\Core\Command\Db;
+
+use OC\Config;
+use OC\DB\Connection;
+use OC\DB\ConnectionFactory;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class ConvertType extends Command {
+ /**
+ * @var \OC\Config
+ */
+ protected $config;
+
+ /**
+ * @var \OC\DB\ConnectionFactory
+ */
+ protected $connectionFactory;
+
+ /**
+ * @param \OC\Config $config
+ * @param \OC\DB\ConnectionFactory $connectionFactory
+ */
+ public function __construct(Config $config, ConnectionFactory $connectionFactory) {
+ $this->config = $config;
+ $this->connectionFactory = $connectionFactory;
+ parent::__construct();
+ }
+
+ protected function configure() {
+ $this
+ ->setName('db:convert-type')
+ ->setDescription('Convert the ownCloud database to the newly configured one')
+ ->addArgument(
+ 'type',
+ InputArgument::REQUIRED,
+ 'the type of the database to convert to'
+ )
+ ->addArgument(
+ 'username',
+ InputArgument::REQUIRED,
+ 'the username of the database to convert to'
+ )
+ ->addArgument(
+ 'hostname',
+ InputArgument::REQUIRED,
+ 'the hostname of the database to convert to'
+ )
+ ->addArgument(
+ 'database',
+ InputArgument::REQUIRED,
+ 'the name of the database to convert to'
+ )
+ ->addOption(
+ 'port',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'the port of the database to convert to'
+ )
+ ->addOption(
+ 'password',
+ null,
+ InputOption::VALUE_REQUIRED,
+ 'the password of the database to convert to. Will be asked when not specified. Can also be passed via stdin.'
+ )
+ ->addOption(
+ 'clear-schema',
+ null,
+ InputOption::VALUE_NONE,
+ 'remove all tables from the destination database'
+ )
+ ->addOption(
+ 'all-apps',
+ null,
+ InputOption::VALUE_NONE,
+ 'whether to create schema for all apps instead of only installed apps'
+ )
+ ;
+ }
+
+ protected function validateInput(InputInterface $input, OutputInterface $output) {
+ $type = $this->connectionFactory->normalizeType($input->getArgument('type'));
+ if ($type === 'sqlite3') {
+ throw new \InvalidArgumentException(
+ 'Converting to SQLite (sqlite3) is currently not supported.'
+ );
+ }
+ if ($type === 'mssql') {
+ throw new \InvalidArgumentException(
+ 'Converting to Microsoft SQL Server (mssql) is currently not supported.'
+ );
+ }
+ if ($type === $this->config->getValue('dbtype', '')) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Can not convert from %1$s to %1$s.',
+ $type
+ ));
+ }
+ if ($type === 'oci' && $input->getOption('clear-schema')) {
+ // Doctrine unconditionally tries (at least in version 2.3)
+ // to drop sequence triggers when dropping a table, even though
+ // such triggers may not exist. This results in errors like
+ // "ORA-04080: trigger 'OC_STORAGES_AI_PK' does not exist".
+ throw new \InvalidArgumentException(
+ 'The --clear-schema option is not supported when converting to Oracle (oci).'
+ );
+ }
+ }
+
+ protected function readPassword(InputInterface $input, OutputInterface $output) {
+ // Explicitly specified password
+ if ($input->getOption('password')) {
+ return;
+ }
+
+ // Read from stdin. stream_set_blocking is used to prevent blocking
+ // when nothing is passed via stdin.
+ stream_set_blocking(STDIN, 0);
+ $password = file_get_contents('php://stdin');
+ stream_set_blocking(STDIN, 1);
+ if (trim($password) !== '') {
+ $input->setOption('password', $password);
+ return;
+ }
+
+ // Read password by interacting
+ if ($input->isInteractive()) {
+ /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */
+ $dialog = $this->getHelperSet()->get('dialog');
+ $password = $dialog->askHiddenResponse(
+ $output,
+ '<question>What is the database password?</question>',
+ false
+ );
+ $input->setOption('password', $password);
+ return;
+ }
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $this->validateInput($input, $output);
+ $this->readPassword($input, $output);
+
+ $fromDB = \OC_DB::getConnection();
+ $toDB = $this->getToDBConnection($input, $output);
+
+ if ($input->getOption('clear-schema')) {
+ $this->clearSchema($toDB, $input, $output);
+ }
+
+ $this->createSchema($toDB, $input, $output);
+
+ $toTables = $this->getTables($toDB);
+ $fromTables = $this->getTables($fromDB);
+
+ // warn/fail if there are more tables in 'from' database
+ $extraFromTables = array_diff($fromTables, $toTables);
+ if (!empty($extraFromTables)) {
+ $output->writeln('<comment>The following tables will not be converted:</comment>');
+ $output->writeln($extraFromTables);
+ if (!$input->getOption('all-apps')) {
+ $output->writeln('<comment>Please note that tables belonging to available but currently not installed apps</comment>');
+ $output->writeln('<comment>can be included by specifying the --all-apps option.</comment>');
+ }
+ /** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */
+ $dialog = $this->getHelperSet()->get('dialog');
+ if (!$dialog->askConfirmation(
+ $output,
+ '<question>Continue with the conversion?</question>',
+ false
+ )) {
+ return;
+ }
+ }
+ $intersectingTables = array_intersect($toTables, $fromTables);
+ $this->convertDB($fromDB, $toDB, $intersectingTables, $input, $output);
+ }
+
+ protected function createSchema(Connection $toDB, InputInterface $input, OutputInterface $output) {
+ $output->writeln('<info>Creating schema in new database</info>');
+ $schemaManager = new \OC\DB\MDB2SchemaManager($toDB);
+ $schemaManager->createDbFromStructure(\OC::$SERVERROOT.'/db_structure.xml');
+ $apps = $input->getOption('all-apps') ? \OC_App::getAllApps() : \OC_App::getEnabledApps();
+ foreach($apps as $app) {
+ if (file_exists(\OC_App::getAppPath($app).'/appinfo/database.xml')) {
+ $schemaManager->createDbFromStructure(\OC_App::getAppPath($app).'/appinfo/database.xml');
+ }
+ }
+ }
+
+ protected function getToDBConnection(InputInterface $input, OutputInterface $output) {
+ $type = $input->getArgument('type');
+ $connectionParams = array(
+ 'host' => $input->getArgument('hostname'),
+ 'user' => $input->getArgument('username'),
+ 'password' => $input->getOption('password'),
+ 'dbname' => $input->getArgument('database'),
+ 'tablePrefix' => $this->config->getValue('dbtableprefix', 'oc_'),
+ );
+ if ($input->getOption('port')) {
+ $connectionParams['port'] = $input->getOption('port');
+ }
+ return $this->connectionFactory->getConnection($type, $connectionParams);
+ }
+
+ protected function clearSchema(Connection $db, InputInterface $input, OutputInterface $output) {
+ $toTables = $this->getTables($db);
+ if (!empty($toTables)) {
+ $output->writeln('<info>Clearing schema in new database</info>');
+ }
+ foreach($toTables as $table) {
+ $db->getSchemaManager()->dropTable($table);
+ }
+ }
+
+ protected function getTables(Connection $db) {
+ return $db->getSchemaManager()->listTableNames();
+ }
+
+ protected function copyTable(Connection $fromDB, Connection $toDB, $table, InputInterface $input, OutputInterface $output) {
+ /** @var $progress \Symfony\Component\Console\Helper\ProgressHelper */
+ $progress = $this->getHelperSet()->get('progress');
+ $query = 'SELECT COUNT(*) FROM '.$table;
+ $count = $fromDB->fetchColumn($query);
+ $query = 'SELECT * FROM '.$table;
+ $statement = $fromDB->executeQuery($query);
+ $progress->start($output, $count);
+ $progress->setRedrawFrequency($count > 100 ? 5 : 1);
+ while($row = $statement->fetch()) {
+ $progress->advance();
+ if ($input->getArgument('type') === 'oci') {
+ $data = $row;
+ } else {
+ $data = array();
+ foreach ($row as $columnName => $value) {
+ $data[$toDB->quoteIdentifier($columnName)] = $value;
+ }
+ }
+ $toDB->insert($table, $data);
+ }
+ $progress->finish();
+ }
+
+ protected function convertDB(Connection $fromDB, Connection $toDB, array $tables, InputInterface $input, OutputInterface $output) {
+ $this->config->setValue('maintenance', true);
+ try {
+ // copy table rows
+ foreach($tables as $table) {
+ $output->writeln($table);
+ $this->copyTable($fromDB, $toDB, $table, $input, $output);
+ }
+ if ($input->getArgument('type') === 'pgsql') {
+ $tools = new \OC\DB\PgSqlTools;
+ $tools->resynchronizeDatabaseSequences($toDB);
+ }
+ // save new database config
+ $this->saveDBInfo($input);
+ } catch(\Exception $e) {
+ $this->config->setValue('maintenance', false);
+ throw $e;
+ }
+ $this->config->setValue('maintenance', false);
+ }
+
+ protected function saveDBInfo(InputInterface $input) {
+ $type = $input->getArgument('type');
+ $username = $input->getArgument('username');
+ $dbhost = $input->getArgument('hostname');
+ $dbname = $input->getArgument('database');
+ $password = $input->getOption('password');
+ if ($input->getOption('port')) {
+ $dbhost .= ':'.$input->getOption('port');
+ }
+
+ $this->config->setValue('dbtype', $type);
+ $this->config->setValue('dbname', $dbname);
+ $this->config->setValue('dbhost', $dbhost);
+ $this->config->setValue('dbuser', $username);
+ $this->config->setValue('dbpassword', $password);
+ }
+}
diff --git a/core/command/maintenance/repair.php b/core/command/maintenance/repair.php
index c5ef0c55cc0..310c01fbe2a 100644
--- a/core/command/maintenance/repair.php
+++ b/core/command/maintenance/repair.php
@@ -29,7 +29,7 @@ class Repair extends Command {
protected function configure() {
$this
->setName('maintenance:repair')
- ->setDescription('set single user mode');
+ ->setDescription('repair this installation');
}
protected function execute(InputInterface $input, OutputInterface $output) {
diff --git a/core/command/user/lastseen.php b/core/command/user/lastseen.php
new file mode 100644
index 00000000000..7a8db013e3a
--- /dev/null
+++ b/core/command/user/lastseen.php
@@ -0,0 +1,47 @@
+<?php
+/**
+ * Copyright (c) 2014 Arthur Schiwon <blizzz@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+namespace OC\Core\Command\User;
+
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Input\InputArgument;
+
+class LastSeen extends Command {
+ protected function configure() {
+ $this
+ ->setName('user:lastseen')
+ ->setDescription('shows when the user was logged it last time')
+ ->addArgument(
+ 'uid',
+ InputArgument::REQUIRED,
+ 'the username'
+ );
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output) {
+ $userManager = \OC::$server->getUserManager();
+ $user = $userManager->get($input->getArgument('uid'));
+ if(is_null($user)) {
+ $output->writeln('User does not exist');
+ return;
+ }
+
+ $lastLogin = $user->getLastLogin();
+ if($lastLogin === 0) {
+ $output->writeln('User ' . $user->getUID() .
+ ' has never logged in, yet.');
+ } else {
+ $date = new \DateTime();
+ $date->setTimestamp($lastLogin);
+ $output->writeln($user->getUID() .
+ '`s last login: ' . $date->format('d.m.Y H:i'));
+ }
+ }
+}