Completed
Pull Request — stable8.2 (#29162)
by Tom
08:26
created

DavProperties::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 0
dl 0
loc 12
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Tom Needham <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2017 ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OC\Core\Command\Migrate;
23
24
use Doctrine\DBAL\Exception\DriverException;
25
use Doctrine\DBAL\Exception\NonUniqueFieldNameException;
26
use OC\Files\Filesystem;
27
use OCP\DB\QueryBuilder\IQueryBuilder;
28
use OCP\IDBConnection;
29
use OCP\IUser;
30
use OCP\IUserManager;
31
use Symfony\Component\Console\Command\Command;
32
use Symfony\Component\Console\Helper\ProgressBar;
33
use Symfony\Component\Console\Input\InputInterface;
34
use Symfony\Component\Console\Input\InputOption;
35
use Symfony\Component\Console\Output\ConsoleOutput;
36
use Symfony\Component\Console\Output\OutputInterface;
37
38
class DavProperties extends Command {
39
40
	/**
41
	 * @var IUserManager
42
	 */
43
	protected $userManager;
44
45
	/**
46
	 * @var IDBConnection
47
	 */
48
	protected $connection;
49
50
	/**
51
	 * @var ConsoleOutput
52
	 */
53
	protected $output;
54
55
	public function __construct(IUserManager $userManager, IDBConnection $connection) {
56
		parent::__construct();
57
		$this->userManager = $userManager;
58
		$this->connection = $connection;
59
60
	}
61
62
	protected function configure() {
63
		$this
64
			->setName('migrate:davproperties')
65
			->setDescription('pre-runs the oc_properties migration in readiness for ownCloud X')
66
			->addOption(
67
				'limit',
68
				null,
69
				InputOption::VALUE_REQUIRED,
70
				'limit rows returned from database for testing. Defaults to all',
71
				0
72
			);
73
	}
74
75
	protected function execute(InputInterface $input, OutputInterface $output) {
76
		$this->output = $output;
0 ignored issues
show
Documentation Bug introduced by
It seems like $output of type object<Symfony\Component...Output\OutputInterface> is incompatible with the declared type object<Symfony\Component...e\Output\ConsoleOutput> of property $output.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
77
78
		$this->fixDatabase();
79
80
		// Get the statements to run
81
		$output->writeln("Computing the statements that need executing");
82
		$limit = $input->getOption('limit');
83
		if($limit !== 0) {
84
			$output->writeln("Limiting to $limit results from DB");
85
		}
86
		$statements = $this->getSqlStatements($this->connection, $limit);
87
88
		// Run the statements on the DB
89
		if(empty($statements)) {
90
			$output->writeln("No update queries necessary");
91
			return 0;
92
		}
93
		$output->writeln("Executing statements on the DB");
94
		$bar = new ProgressBar($output, count($statements));
95
		foreach($statements as $s) {
96
			$this->connection->executeQuery($s);
97
			$bar->advance();
98
		}
99
		$bar->finish();
100
		$output->writeln("");
101
102
		if($input->getOption('limit') === 0) {
103
			$output->writeln("Dropping rows that are orphaned");
104
			// drop entries with empty fileid
105
			$qb = $this->connection->getQueryBuilder();
106
			$dropQuery = $qb
107
				->delete('properties')
108
				->where(
109
					$qb->expr()->eq('fileid', $qb->expr()->literal('0'))
110
				)
111
				->orWhere(
112
					$qb->expr()->isNull('fileid')
113
				);
114
			$dropQuery->execute();
115
		}
116
117
		$output->writeln("Finished");
118
119
120
	}
121
122
	protected function fixDatabase() {
123
		$this->output->writeln("Creating the additional column on the oc_properties table");
124
125
		// Add the column
126
		try {
127
			$sql = "ALTER TABLE `*PREFIX*properties` ADD `fileid` BIGINT NULL";
128
			$this->connection->executeQuery($sql);
129
		} catch (NonUniqueFieldNameException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Exception\...niqueFieldNameException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
130
			$this->output->writeln("Column already appear to exist");
131
		}
132
133
		$this->output->writeln("Adding fileid index to oc_properties table");
134
		// Add the index
135
		try {
136
			$sql = "CREATE INDEX fileid_index ON `*PREFIX*properties` (`fileid`)";
137
			$this->connection->executeQuery($sql);
138
		} catch (DriverException $e) {
0 ignored issues
show
Bug introduced by
The class Doctrine\DBAL\Exception\DriverException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
139
			$this->output->writeln("Exception adding index - potentially already exists");
140
		}
141
	}
142
143
	/**
144
	 * @param IQueryBuilder $qb
145
	 * @param $entry
146
	 * @return string|null
147
	 */
148
	private function getRepairEntrySql(IQueryBuilder $qb, $entry) {
149
		$userId = $entry['userid'];
150
		$user = $this->userManager->get($userId);
151
		if (!($user instanceof IUser)) {
152
			return null;
153
		}
154
155
		// Get the user folder (sets up mounts etc)
156
		$userFolder = \OC::$server->getUserFolder($userId);
157
		/** @var $storage \OC\Files\Storage\Storage */
158
		$path = $userFolder->getFullPath('') . substr($entry['propertypath'], 1, strlen($entry['propertypath']));
159
		list($storage, $internalPath) = Filesystem::resolvePath($path);
160
161
		$id = $storage->getCache()->getId($internalPath);
162
163
		if($id !== -1) {
164
			$updateQuery = $this->getRepairQuery($qb, $id, $userId, $entry['propertypath']);
165
			return $updateQuery->getSQL();
166
		}
167
168
		return null;
169
	}
170
171
	/**
172
	 * @param IQueryBuilder $qb
173
	 * @param int $fileId
174
	 * @param string $userId
175
	 * @param string $propertyPath
176
	 * @return IQueryBuilder
177
	 */
178
	private function getRepairQuery(IQueryBuilder $qb, $fileId, $userId, $propertyPath){
179
		return $qb->resetQueryParts()
180
			->update('properties')
181
			->set(
182
				'fileid',
183
				$qb->expr()->literal($fileId)
184
			)
185
			->where(
186
				$qb->expr()->eq('userid', $qb->expr()->literal($userId))
187
			)
188
			->andWhere(
189
				$qb->expr()->eq(
190
					'propertypath',
191
					$qb->expr()->literal($propertyPath)
192
				)
193
			);
194
	}
195
196
	/**
197
	 * @param IDBConnection $connection
198
	 * @param integer $limit
199
	 * @return array
200
	 */
201
	public function getSqlStatements(IDBConnection $connection, $limit) {
202
		$statements = [];
203
204
		$qb = $connection->getQueryBuilder();
205
		$qb->select('*')
206
			->from('properties', 'props')
207
			->setMaxResults(1);
208
		$result = $qb->execute();
209
		$row = $result->fetch();
210
		// There is nothing to do if table is empty or has no userid field
211
		if (!$row || !isset($row['userid'])) {
212
			$this->output->writeln("Properties table empty or does not have userid field");
213
			return [];
214
		}
215
		$qb->resetQueryParts()
216
			->select('userid', 'propertypath')
0 ignored issues
show
Unused Code introduced by
The call to IQueryBuilder::select() has too many arguments starting with 'propertypath'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
217
			->from('properties', 'props')
218
			->where($qb->expr()->isNull('fileid'))
219
			->groupBy('userid')
220
			->addGroupBy('propertypath')
221
			->orderBy('userid')
222
			->addOrderBy('propertypath');
223
224
225
		if($limit !== 0) {
226
			$qb->setMaxResults($limit);
227
		} else {
228
			$qb->setMaxResults(null);
229
		}
230
231
		$this->output->writeLn($qb->getSQL());
232
233
234
		$selectResult = $qb->execute();
235
		$bar = new ProgressBar($this->output, $selectResult->rowCount());
236
		while ($row = $selectResult->fetch()) {
237
			try {
238
				$sql = $this->getRepairEntrySql($qb, $row);
239
				if (!is_null($sql)) {
240
					$statements[] = $sql;
241
				}
242
			} catch (\Exception $e) {
243
				$this->output->writeln("<error>Exception: ".get_class($e)." Message: ".$e->getMessage()."</error>");
244
				\OC::$server->getLogger()->logException($e);
245
			}
246
			$bar->advance();
247
		}
248
		$bar->finish();
249
		$this->output->writeln('');
250
		//Mounted FS can have side effects on further migrations
251
		\OC_Util::tearDownFS();
252
253
		return $statements;
254
	}
255
}
256