Completed
Push — master ( 56fa44...f0fcf8 )
by Morris
21:41
created
apps/dav/lib/Connector/Sabre/CustomPropertiesBackend.php 1 patch
Indentation   +318 added lines, -318 removed lines patch added patch discarded remove patch
@@ -36,323 +36,323 @@
 block discarded – undo
36 36
 
37 37
 class CustomPropertiesBackend implements BackendInterface {
38 38
 
39
-	/**
40
-	 * Ignored properties
41
-	 *
42
-	 * @var array
43
-	 */
44
-	private $ignoredProperties = array(
45
-		'{DAV:}getcontentlength',
46
-		'{DAV:}getcontenttype',
47
-		'{DAV:}getetag',
48
-		'{DAV:}quota-used-bytes',
49
-		'{DAV:}quota-available-bytes',
50
-		'{DAV:}quota-available-bytes',
51
-		'{http://owncloud.org/ns}permissions',
52
-		'{http://owncloud.org/ns}downloadURL',
53
-		'{http://owncloud.org/ns}dDC',
54
-		'{http://owncloud.org/ns}size',
55
-		'{http://nextcloud.org/ns}is-encrypted',
56
-	);
57
-
58
-	/**
59
-	 * @var Tree
60
-	 */
61
-	private $tree;
62
-
63
-	/**
64
-	 * @var IDBConnection
65
-	 */
66
-	private $connection;
67
-
68
-	/**
69
-	 * @var IUser
70
-	 */
71
-	private $user;
72
-
73
-	/**
74
-	 * Properties cache
75
-	 *
76
-	 * @var array
77
-	 */
78
-	private $cache = [];
79
-
80
-	/**
81
-	 * @param Tree $tree node tree
82
-	 * @param IDBConnection $connection database connection
83
-	 * @param IUser $user owner of the tree and properties
84
-	 */
85
-	public function __construct(
86
-		Tree $tree,
87
-		IDBConnection $connection,
88
-		IUser $user) {
89
-		$this->tree = $tree;
90
-		$this->connection = $connection;
91
-		$this->user = $user->getUID();
92
-	}
93
-
94
-	/**
95
-	 * Fetches properties for a path.
96
-	 *
97
-	 * @param string $path
98
-	 * @param PropFind $propFind
99
-	 * @return void
100
-	 */
101
-	public function propFind($path, PropFind $propFind) {
102
-		try {
103
-			$node = $this->tree->getNodeForPath($path);
104
-			if (!($node instanceof Node)) {
105
-				return;
106
-			}
107
-		} catch (ServiceUnavailable $e) {
108
-			// might happen for unavailable mount points, skip
109
-			return;
110
-		} catch (NotFound $e) {
111
-			// in some rare (buggy) cases the node might not be found,
112
-			// we catch the exception to prevent breaking the whole list with a 404
113
-			// (soft fail)
114
-			\OC::$server->getLogger()->warning(
115
-				'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
116
-				array('app' => 'files')
117
-			);
118
-			return;
119
-		}
120
-
121
-		$requestedProps = $propFind->get404Properties();
122
-
123
-		// these might appear
124
-		$requestedProps = array_diff(
125
-			$requestedProps,
126
-			$this->ignoredProperties
127
-		);
128
-
129
-		if (empty($requestedProps)) {
130
-			return;
131
-		}
132
-
133
-		if ($node instanceof Directory
134
-			&& $propFind->getDepth() !== 0
135
-		) {
136
-			// note: pre-fetching only supported for depth <= 1
137
-			$this->loadChildrenProperties($node, $requestedProps);
138
-		}
139
-
140
-		$props = $this->getProperties($node, $requestedProps);
141
-		foreach ($props as $propName => $propValue) {
142
-			$propFind->set($propName, $propValue);
143
-		}
144
-	}
145
-
146
-	/**
147
-	 * Updates properties for a path
148
-	 *
149
-	 * @param string $path
150
-	 * @param PropPatch $propPatch
151
-	 *
152
-	 * @return void
153
-	 */
154
-	public function propPatch($path, PropPatch $propPatch) {
155
-		$node = $this->tree->getNodeForPath($path);
156
-		if (!($node instanceof Node)) {
157
-			return;
158
-		}
159
-
160
-		$propPatch->handleRemaining(function($changedProps) use ($node) {
161
-			return $this->updateProperties($node, $changedProps);
162
-		});
163
-	}
164
-
165
-	/**
166
-	 * This method is called after a node is deleted.
167
-	 *
168
-	 * @param string $path path of node for which to delete properties
169
-	 */
170
-	public function delete($path) {
171
-		$statement = $this->connection->prepare(
172
-			'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
173
-		);
174
-		$statement->execute(array($this->user, '/' . $path));
175
-		$statement->closeCursor();
176
-
177
-		unset($this->cache[$path]);
178
-	}
179
-
180
-	/**
181
-	 * This method is called after a successful MOVE
182
-	 *
183
-	 * @param string $source
184
-	 * @param string $destination
185
-	 *
186
-	 * @return void
187
-	 */
188
-	public function move($source, $destination) {
189
-		$statement = $this->connection->prepare(
190
-			'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
191
-			' WHERE `userid` = ? AND `propertypath` = ?'
192
-		);
193
-		$statement->execute(array('/' . $destination, $this->user, '/' . $source));
194
-		$statement->closeCursor();
195
-	}
196
-
197
-	/**
198
-	 * Returns a list of properties for this nodes.;
199
-	 * @param Node $node
200
-	 * @param array $requestedProperties requested properties or empty array for "all"
201
-	 * @return array
202
-	 * @note The properties list is a list of propertynames the client
203
-	 * requested, encoded as xmlnamespace#tagName, for example:
204
-	 * http://www.example.org/namespace#author If the array is empty, all
205
-	 * properties should be returned
206
-	 */
207
-	private function getProperties(Node $node, array $requestedProperties) {
208
-		$path = $node->getPath();
209
-		if (isset($this->cache[$path])) {
210
-			return $this->cache[$path];
211
-		}
212
-
213
-		// TODO: chunking if more than 1000 properties
214
-		$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
215
-
216
-		$whereValues = array($this->user, $path);
217
-		$whereTypes = array(null, null);
218
-
219
-		if (!empty($requestedProperties)) {
220
-			// request only a subset
221
-			$sql .= ' AND `propertyname` in (?)';
222
-			$whereValues[] = $requestedProperties;
223
-			$whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
224
-		}
225
-
226
-		$result = $this->connection->executeQuery(
227
-			$sql,
228
-			$whereValues,
229
-			$whereTypes
230
-		);
231
-
232
-		$props = [];
233
-		while ($row = $result->fetch()) {
234
-			$props[$row['propertyname']] = $row['propertyvalue'];
235
-		}
236
-
237
-		$result->closeCursor();
238
-
239
-		$this->cache[$path] = $props;
240
-		return $props;
241
-	}
242
-
243
-	/**
244
-	 * Update properties
245
-	 *
246
-	 * @param Node $node node for which to update properties
247
-	 * @param array $properties array of properties to update
248
-	 *
249
-	 * @return bool
250
-	 */
251
-	private function updateProperties($node, $properties) {
252
-		$path = $node->getPath();
253
-
254
-		$deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
255
-			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
256
-
257
-		$insertStatement = 'INSERT INTO `*PREFIX*properties`' .
258
-			' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
259
-
260
-		$updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
261
-			' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
262
-
263
-		// TODO: use "insert or update" strategy ?
264
-		$existing = $this->getProperties($node, array());
265
-		$this->connection->beginTransaction();
266
-		foreach ($properties as $propertyName => $propertyValue) {
267
-			// If it was null, we need to delete the property
268
-			if (is_null($propertyValue)) {
269
-				if (array_key_exists($propertyName, $existing)) {
270
-					$this->connection->executeUpdate($deleteStatement,
271
-						array(
272
-							$this->user,
273
-							$path,
274
-							$propertyName
275
-						)
276
-					);
277
-				}
278
-			} else {
279
-				if (!array_key_exists($propertyName, $existing)) {
280
-					$this->connection->executeUpdate($insertStatement,
281
-						array(
282
-							$this->user,
283
-							$path,
284
-							$propertyName,
285
-							$propertyValue
286
-						)
287
-					);
288
-				} else {
289
-					$this->connection->executeUpdate($updateStatement,
290
-						array(
291
-							$propertyValue,
292
-							$this->user,
293
-							$path,
294
-							$propertyName
295
-						)
296
-					);
297
-				}
298
-			}
299
-		}
300
-
301
-		$this->connection->commit();
302
-		unset($this->cache[$path]);
303
-
304
-		return true;
305
-	}
306
-
307
-	/**
308
-	 * Bulk load properties for directory children
309
-	 *
310
-	 * @param Directory $node
311
-	 * @param array $requestedProperties requested properties
312
-	 *
313
-	 * @return void
314
-	 */
315
-	private function loadChildrenProperties(Directory $node, $requestedProperties) {
316
-		$path = $node->getPath();
317
-		if (isset($this->cache[$path])) {
318
-			// we already loaded them at some point
319
-			return;
320
-		}
321
-
322
-		$childNodes = $node->getChildren();
323
-		// pre-fill cache
324
-		foreach ($childNodes as $childNode) {
325
-			$this->cache[$childNode->getPath()] = [];
326
-		}
327
-
328
-		$sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?';
329
-		$sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`';
330
-
331
-		$result = $this->connection->executeQuery(
332
-			$sql,
333
-			array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties),
334
-			array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
335
-		);
336
-
337
-		$oldPath = null;
338
-		$props = [];
339
-		while ($row = $result->fetch()) {
340
-			$path = $row['propertypath'];
341
-			if ($oldPath !== $path) {
342
-				// save previously gathered props
343
-				$this->cache[$oldPath] = $props;
344
-				$oldPath = $path;
345
-				// prepare props for next path
346
-				$props = [];
347
-			}
348
-			$props[$row['propertyname']] = $row['propertyvalue'];
349
-		}
350
-		if (!is_null($oldPath)) {
351
-			// save props from last run
352
-			$this->cache[$oldPath] = $props;
353
-		}
354
-
355
-		$result->closeCursor();
356
-	}
39
+    /**
40
+     * Ignored properties
41
+     *
42
+     * @var array
43
+     */
44
+    private $ignoredProperties = array(
45
+        '{DAV:}getcontentlength',
46
+        '{DAV:}getcontenttype',
47
+        '{DAV:}getetag',
48
+        '{DAV:}quota-used-bytes',
49
+        '{DAV:}quota-available-bytes',
50
+        '{DAV:}quota-available-bytes',
51
+        '{http://owncloud.org/ns}permissions',
52
+        '{http://owncloud.org/ns}downloadURL',
53
+        '{http://owncloud.org/ns}dDC',
54
+        '{http://owncloud.org/ns}size',
55
+        '{http://nextcloud.org/ns}is-encrypted',
56
+    );
57
+
58
+    /**
59
+     * @var Tree
60
+     */
61
+    private $tree;
62
+
63
+    /**
64
+     * @var IDBConnection
65
+     */
66
+    private $connection;
67
+
68
+    /**
69
+     * @var IUser
70
+     */
71
+    private $user;
72
+
73
+    /**
74
+     * Properties cache
75
+     *
76
+     * @var array
77
+     */
78
+    private $cache = [];
79
+
80
+    /**
81
+     * @param Tree $tree node tree
82
+     * @param IDBConnection $connection database connection
83
+     * @param IUser $user owner of the tree and properties
84
+     */
85
+    public function __construct(
86
+        Tree $tree,
87
+        IDBConnection $connection,
88
+        IUser $user) {
89
+        $this->tree = $tree;
90
+        $this->connection = $connection;
91
+        $this->user = $user->getUID();
92
+    }
93
+
94
+    /**
95
+     * Fetches properties for a path.
96
+     *
97
+     * @param string $path
98
+     * @param PropFind $propFind
99
+     * @return void
100
+     */
101
+    public function propFind($path, PropFind $propFind) {
102
+        try {
103
+            $node = $this->tree->getNodeForPath($path);
104
+            if (!($node instanceof Node)) {
105
+                return;
106
+            }
107
+        } catch (ServiceUnavailable $e) {
108
+            // might happen for unavailable mount points, skip
109
+            return;
110
+        } catch (NotFound $e) {
111
+            // in some rare (buggy) cases the node might not be found,
112
+            // we catch the exception to prevent breaking the whole list with a 404
113
+            // (soft fail)
114
+            \OC::$server->getLogger()->warning(
115
+                'Could not get node for path: \"' . $path . '\" : ' . $e->getMessage(),
116
+                array('app' => 'files')
117
+            );
118
+            return;
119
+        }
120
+
121
+        $requestedProps = $propFind->get404Properties();
122
+
123
+        // these might appear
124
+        $requestedProps = array_diff(
125
+            $requestedProps,
126
+            $this->ignoredProperties
127
+        );
128
+
129
+        if (empty($requestedProps)) {
130
+            return;
131
+        }
132
+
133
+        if ($node instanceof Directory
134
+            && $propFind->getDepth() !== 0
135
+        ) {
136
+            // note: pre-fetching only supported for depth <= 1
137
+            $this->loadChildrenProperties($node, $requestedProps);
138
+        }
139
+
140
+        $props = $this->getProperties($node, $requestedProps);
141
+        foreach ($props as $propName => $propValue) {
142
+            $propFind->set($propName, $propValue);
143
+        }
144
+    }
145
+
146
+    /**
147
+     * Updates properties for a path
148
+     *
149
+     * @param string $path
150
+     * @param PropPatch $propPatch
151
+     *
152
+     * @return void
153
+     */
154
+    public function propPatch($path, PropPatch $propPatch) {
155
+        $node = $this->tree->getNodeForPath($path);
156
+        if (!($node instanceof Node)) {
157
+            return;
158
+        }
159
+
160
+        $propPatch->handleRemaining(function($changedProps) use ($node) {
161
+            return $this->updateProperties($node, $changedProps);
162
+        });
163
+    }
164
+
165
+    /**
166
+     * This method is called after a node is deleted.
167
+     *
168
+     * @param string $path path of node for which to delete properties
169
+     */
170
+    public function delete($path) {
171
+        $statement = $this->connection->prepare(
172
+            'DELETE FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?'
173
+        );
174
+        $statement->execute(array($this->user, '/' . $path));
175
+        $statement->closeCursor();
176
+
177
+        unset($this->cache[$path]);
178
+    }
179
+
180
+    /**
181
+     * This method is called after a successful MOVE
182
+     *
183
+     * @param string $source
184
+     * @param string $destination
185
+     *
186
+     * @return void
187
+     */
188
+    public function move($source, $destination) {
189
+        $statement = $this->connection->prepare(
190
+            'UPDATE `*PREFIX*properties` SET `propertypath` = ?' .
191
+            ' WHERE `userid` = ? AND `propertypath` = ?'
192
+        );
193
+        $statement->execute(array('/' . $destination, $this->user, '/' . $source));
194
+        $statement->closeCursor();
195
+    }
196
+
197
+    /**
198
+     * Returns a list of properties for this nodes.;
199
+     * @param Node $node
200
+     * @param array $requestedProperties requested properties or empty array for "all"
201
+     * @return array
202
+     * @note The properties list is a list of propertynames the client
203
+     * requested, encoded as xmlnamespace#tagName, for example:
204
+     * http://www.example.org/namespace#author If the array is empty, all
205
+     * properties should be returned
206
+     */
207
+    private function getProperties(Node $node, array $requestedProperties) {
208
+        $path = $node->getPath();
209
+        if (isset($this->cache[$path])) {
210
+            return $this->cache[$path];
211
+        }
212
+
213
+        // TODO: chunking if more than 1000 properties
214
+        $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` = ?';
215
+
216
+        $whereValues = array($this->user, $path);
217
+        $whereTypes = array(null, null);
218
+
219
+        if (!empty($requestedProperties)) {
220
+            // request only a subset
221
+            $sql .= ' AND `propertyname` in (?)';
222
+            $whereValues[] = $requestedProperties;
223
+            $whereTypes[] = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
224
+        }
225
+
226
+        $result = $this->connection->executeQuery(
227
+            $sql,
228
+            $whereValues,
229
+            $whereTypes
230
+        );
231
+
232
+        $props = [];
233
+        while ($row = $result->fetch()) {
234
+            $props[$row['propertyname']] = $row['propertyvalue'];
235
+        }
236
+
237
+        $result->closeCursor();
238
+
239
+        $this->cache[$path] = $props;
240
+        return $props;
241
+    }
242
+
243
+    /**
244
+     * Update properties
245
+     *
246
+     * @param Node $node node for which to update properties
247
+     * @param array $properties array of properties to update
248
+     *
249
+     * @return bool
250
+     */
251
+    private function updateProperties($node, $properties) {
252
+        $path = $node->getPath();
253
+
254
+        $deleteStatement = 'DELETE FROM `*PREFIX*properties`' .
255
+            ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
256
+
257
+        $insertStatement = 'INSERT INTO `*PREFIX*properties`' .
258
+            ' (`userid`,`propertypath`,`propertyname`,`propertyvalue`) VALUES(?,?,?,?)';
259
+
260
+        $updateStatement = 'UPDATE `*PREFIX*properties` SET `propertyvalue` = ?' .
261
+            ' WHERE `userid` = ? AND `propertypath` = ? AND `propertyname` = ?';
262
+
263
+        // TODO: use "insert or update" strategy ?
264
+        $existing = $this->getProperties($node, array());
265
+        $this->connection->beginTransaction();
266
+        foreach ($properties as $propertyName => $propertyValue) {
267
+            // If it was null, we need to delete the property
268
+            if (is_null($propertyValue)) {
269
+                if (array_key_exists($propertyName, $existing)) {
270
+                    $this->connection->executeUpdate($deleteStatement,
271
+                        array(
272
+                            $this->user,
273
+                            $path,
274
+                            $propertyName
275
+                        )
276
+                    );
277
+                }
278
+            } else {
279
+                if (!array_key_exists($propertyName, $existing)) {
280
+                    $this->connection->executeUpdate($insertStatement,
281
+                        array(
282
+                            $this->user,
283
+                            $path,
284
+                            $propertyName,
285
+                            $propertyValue
286
+                        )
287
+                    );
288
+                } else {
289
+                    $this->connection->executeUpdate($updateStatement,
290
+                        array(
291
+                            $propertyValue,
292
+                            $this->user,
293
+                            $path,
294
+                            $propertyName
295
+                        )
296
+                    );
297
+                }
298
+            }
299
+        }
300
+
301
+        $this->connection->commit();
302
+        unset($this->cache[$path]);
303
+
304
+        return true;
305
+    }
306
+
307
+    /**
308
+     * Bulk load properties for directory children
309
+     *
310
+     * @param Directory $node
311
+     * @param array $requestedProperties requested properties
312
+     *
313
+     * @return void
314
+     */
315
+    private function loadChildrenProperties(Directory $node, $requestedProperties) {
316
+        $path = $node->getPath();
317
+        if (isset($this->cache[$path])) {
318
+            // we already loaded them at some point
319
+            return;
320
+        }
321
+
322
+        $childNodes = $node->getChildren();
323
+        // pre-fill cache
324
+        foreach ($childNodes as $childNode) {
325
+            $this->cache[$childNode->getPath()] = [];
326
+        }
327
+
328
+        $sql = 'SELECT * FROM `*PREFIX*properties` WHERE `userid` = ? AND `propertypath` LIKE ?';
329
+        $sql .= ' AND `propertyname` in (?) ORDER BY `propertypath`, `propertyname`';
330
+
331
+        $result = $this->connection->executeQuery(
332
+            $sql,
333
+            array($this->user, $this->connection->escapeLikeParameter(rtrim($path, '/')) . '/%', $requestedProperties),
334
+            array(null, null, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
335
+        );
336
+
337
+        $oldPath = null;
338
+        $props = [];
339
+        while ($row = $result->fetch()) {
340
+            $path = $row['propertypath'];
341
+            if ($oldPath !== $path) {
342
+                // save previously gathered props
343
+                $this->cache[$oldPath] = $props;
344
+                $oldPath = $path;
345
+                // prepare props for next path
346
+                $props = [];
347
+            }
348
+            $props[$row['propertyname']] = $row['propertyvalue'];
349
+        }
350
+        if (!is_null($oldPath)) {
351
+            // save props from last run
352
+            $this->cache[$oldPath] = $props;
353
+        }
354
+
355
+        $result->closeCursor();
356
+    }
357 357
 
358 358
 }
Please login to merge, or discard this patch.