Completed
Push — stable9 ( 485cb1...e094cf )
by Lukas
26:41 queued 26:23
created

Migration::updateInitiatorInfo()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 0
dl 0
loc 19
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Björn Schießle <[email protected]>
6
 * @author Morris Jobke <[email protected]>
7
 * @author Roeland Jago Douma <[email protected]>
8
 *
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OCA\Files_Sharing;
26
27
use Doctrine\DBAL\Connection;
28
use OCP\IDBConnection;
29
use OC\Cache\CappedMemoryCache;
30
31
/**
32
 * Class Migration
33
 *
34
 * @package OCA\Files_Sharing
35
 * @group DB
36
 */
37
class Migration {
38
39
	/** @var IDBConnection */
40
	private $connection;
41
42
	/** @var  array with all shares we already saw */
43
	private $shareCache;
44
45
	/** @var string */
46
	private $table = 'share';
47
48
	public function __construct(IDBConnection $connection) {
49
		$this->connection = $connection;
50
51
		// We cache up to 10k share items (~20MB)
52
		$this->shareCache = new CappedMemoryCache(10000);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \OC\Cache\CappedMemoryCache(10000) of type object<OC\Cache\CappedMemoryCache> is incompatible with the declared type array of property $shareCache.

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...
53
	}
54
55
	/**
56
	 * move all re-shares to the owner in order to have a flat list of shares
57
	 * upgrade from oC 8.2 to 9.0 with the new sharing
58
	 */
59
	public function removeReShares() {
60
61
		$stmt = $this->getReShares();
62
63
		$owners = [];
64
		while($share = $stmt->fetch()) {
65
66
			$this->shareCache[$share['id']] = $share;
67
68
			$owners[$share['id']] = [
69
					'owner' => $this->findOwner($share),
70
					'initiator' => $share['uid_owner'],
71
					'type' => $share['share_type'],
72
			];
73
74
			if (count($owners) === 1000) {
75
				$this->updateOwners($owners);
76
				$owners = [];
77
			}
78
		}
79
80
		$stmt->closeCursor();
81
82
		if (count($owners)) {
83
			$this->updateOwners($owners);
84
		}
85
	}
86
87
	/**
88
	 * update all owner information so that all shares have an owner
89
	 * and an initiator for the upgrade from oC 8.2 to 9.0 with the new sharing
90
	 */
91
	public function updateInitiatorInfo() {
92
		while (true) {
93
			$shares = $this->getMissingInitiator(1000);
94
95
			if (empty($shares)) {
96
				break;
97
			}
98
99
			$owners = [];
100
			foreach ($shares as $share) {
101
				$owners[$share['id']] = [
102
					'owner' => $share['uid_owner'],
103
					'initiator' => $share['uid_owner'],
104
					'type' => $share['share_type'],
105
				];
106
			}
107
			$this->updateOwners($owners);
108
		}
109
	}
110
111
	/**
112
	 * find the owner of a re-shared file/folder
113
	 *
114
	 * @param array $share
115
	 * @return array
116
	 */
117
	private function findOwner($share) {
118
		$currentShare = $share;
119
		while(!is_null($currentShare['parent'])) {
120
			if (isset($this->shareCache[$currentShare['parent']])) {
121
				$currentShare = $this->shareCache[$currentShare['parent']];
122
			} else {
123
				$currentShare = $this->getShare((int)$currentShare['parent']);
124
				$this->shareCache[$currentShare['id']] = $currentShare;
125
			}
126
		}
127
128
		return $currentShare['uid_owner'];
129
	}
130
131
	/**
132
	 * Get $n re-shares from the database
133
	 *
134
	 * @param int $n The max number of shares to fetch
0 ignored issues
show
Bug introduced by
There is no parameter named $n. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
135
	 * @return \Doctrine\DBAL\Driver\Statement
136
	 */
137 View Code Duplication
	private function getReShares() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
		$query = $this->connection->getQueryBuilder();
139
		$query->select(['id', 'parent', 'uid_owner', 'share_type'])
140
			->from($this->table)
141
			->where($query->expr()->in(
142
				'share_type',
143
				$query->createNamedParameter(
144
					[
145
						\OCP\Share::SHARE_TYPE_USER,
146
						\OCP\Share::SHARE_TYPE_GROUP,
147
						\OCP\Share::SHARE_TYPE_LINK,
148
						\OCP\Share::SHARE_TYPE_REMOTE,
149
					],
150
					Connection::PARAM_INT_ARRAY
151
				)
152
			))
153
			->andWhere($query->expr()->in(
154
				'item_type',
155
				$query->createNamedParameter(
156
					['file', 'folder'],
157
					Connection::PARAM_STR_ARRAY
158
				)
159
			))
160
			->andWhere($query->expr()->isNotNull('parent'))
161
			->orderBy('id', 'asc');
162
		return $query->execute();
0 ignored issues
show
Bug Compatibility introduced by
The expression $query->execute(); of type Doctrine\DBAL\Driver\Statement|integer adds the type integer to the return on line 162 which is incompatible with the return type documented by OCA\Files_Sharing\Migration::getReShares of type Doctrine\DBAL\Driver\Statement.
Loading history...
163
164
165
		$shares = $result->fetchAll();
0 ignored issues
show
Unused Code introduced by
$shares = $result->fetchAll(); does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
166
		$result->closeCursor();
167
168
		$ordered = [];
169
		foreach ($shares as $share) {
170
			$ordered[(int)$share['id']] = $share;
171
		}
172
173
		return $ordered;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $ordered; (array) is incompatible with the return type documented by OCA\Files_Sharing\Migration::getReShares of type Doctrine\DBAL\Driver\Statement.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
174
	}
175
176
	/**
177
	 * Get $n re-shares from the database
178
	 *
179
	 * @param int $n The max number of shares to fetch
180
	 * @return array
181
	 */
182 View Code Duplication
	private function getMissingInitiator($n = 1000) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
183
		$query = $this->connection->getQueryBuilder();
184
		$query->select(['id', 'uid_owner', 'share_type'])
185
			->from($this->table)
186
			->where($query->expr()->in(
187
				'share_type',
188
				$query->createNamedParameter(
189
					[
190
						\OCP\Share::SHARE_TYPE_USER,
191
						\OCP\Share::SHARE_TYPE_GROUP,
192
						\OCP\Share::SHARE_TYPE_LINK,
193
						\OCP\Share::SHARE_TYPE_REMOTE,
194
					],
195
					Connection::PARAM_INT_ARRAY
196
				)
197
			))
198
			->andWhere($query->expr()->in(
199
				'item_type',
200
				$query->createNamedParameter(
201
					['file', 'folder'],
202
					Connection::PARAM_STR_ARRAY
203
				)
204
			))
205
			->andWhere($query->expr()->isNull('uid_initiator'))
206
			->orderBy('id', 'asc')
207
			->setMaxResults($n);
208
		$result = $query->execute();
209
		$shares = $result->fetchAll();
210
		$result->closeCursor();
211
212
		$ordered = [];
213
		foreach ($shares as $share) {
214
			$ordered[(int)$share['id']] = $share;
215
		}
216
217
		return $ordered;
218
	}
219
220
	/**
221
	 * get a specific share
222
	 *
223
	 * @param int $id
224
	 * @return array
225
	 */
226
	private function getShare($id) {
227
		$query = $this->connection->getQueryBuilder();
228
		$query->select(['id', 'parent', 'uid_owner'])
229
			->from($this->table)
230
			->where($query->expr()->eq('id', $query->createNamedParameter($id)));
231
		$result = $query->execute();
232
		$share = $result->fetchAll();
233
		$result->closeCursor();
234
235
		return $share[0];
236
	}
237
238
	/**
239
	 * update database with the new owners
240
	 *
241
	 * @param array $owners
242
	 * @throws \Exception
243
	 */
244
	private function updateOwners($owners) {
245
246
		$this->connection->beginTransaction();
247
248
		try {
249
250
			foreach ($owners as $id => $owner) {
251
				$query = $this->connection->getQueryBuilder();
252
				$query->update($this->table)
253
					->set('uid_owner', $query->createNamedParameter($owner['owner']))
254
					->set('uid_initiator', $query->createNamedParameter($owner['initiator']));
255
256
257
				if ((int)$owner['type'] !== \OCP\Share::SHARE_TYPE_LINK) {
258
					$query->set('parent', $query->createNamedParameter(null));
259
				}
260
261
				$query->where($query->expr()->eq('id', $query->createNamedParameter($id)));
262
263
				$query->execute();
264
			}
265
266
			$this->connection->commit();
267
268
		} catch (\Exception $e) {
269
			$this->connection->rollBack();
270
			throw $e;
271
		}
272
273
	}
274
275
}
276