Completed
Pull Request — master (#3828)
by Robin
47:33 queued 18:43
created
lib/private/Files/Cache/Cache.php 2 patches
Doc Comments   +4 added lines, -1 removed lines patch added patch discarded remove patch
@@ -410,6 +410,9 @@  discard block
 block discarded – undo
410 410
 		}
411 411
 	}
412 412
 
413
+	/**
414
+	 * @param string $path
415
+	 */
413 416
 	private function getParentPath($path) {
414 417
 		$parent = dirname($path);
415 418
 		if ($parent === '.') {
@@ -629,7 +632,7 @@  discard block
 block discarded – undo
629 632
 	 *
630 633
 	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
631 634
 	 *        where it will search for all mimetypes in the group ('image/*')
632
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
635
+	 * @return CacheEntry[] an array of cache entries where the mimetype matches the search
633 636
 	 */
634 637
 	public function searchByMime($mimetype) {
635 638
 		if (strpos($mimetype, '/')) {
Please login to merge, or discard this patch.
Indentation   +810 added lines, -810 removed lines patch added patch discarded remove patch
@@ -54,824 +54,824 @@
 block discarded – undo
54 54
  * - ChangePropagator: updates the mtime and etags of parent folders whenever a change to the cache is made to the cache by the updater
55 55
  */
56 56
 class Cache implements ICache {
57
-	use MoveFromCacheTrait {
58
-		MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
59
-	}
60
-
61
-	/**
62
-	 * @var array partial data for the cache
63
-	 */
64
-	protected $partial = array();
65
-
66
-	/**
67
-	 * @var string
68
-	 */
69
-	protected $storageId;
70
-
71
-	/**
72
-	 * @var Storage $storageCache
73
-	 */
74
-	protected $storageCache;
75
-
76
-	/** @var IMimeTypeLoader */
77
-	protected $mimetypeLoader;
78
-
79
-	/**
80
-	 * @var IDBConnection
81
-	 */
82
-	protected $connection;
83
-
84
-	/** @var QuerySearchHelper */
85
-	protected $querySearchHelper;
86
-
87
-	/**
88
-	 * @param \OC\Files\Storage\Storage|string $storage
89
-	 */
90
-	public function __construct($storage) {
91
-		if ($storage instanceof \OC\Files\Storage\Storage) {
92
-			$this->storageId = $storage->getId();
93
-		} else {
94
-			$this->storageId = $storage;
95
-		}
96
-		if (strlen($this->storageId) > 64) {
97
-			$this->storageId = md5($this->storageId);
98
-		}
99
-
100
-		$this->storageCache = new Storage($storage);
101
-		$this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
102
-		$this->connection = \OC::$server->getDatabaseConnection();
103
-		$this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
104
-	}
105
-
106
-	/**
107
-	 * Get the numeric storage id for this cache's storage
108
-	 *
109
-	 * @return int
110
-	 */
111
-	public function getNumericStorageId() {
112
-		return $this->storageCache->getNumericId();
113
-	}
114
-
115
-	/**
116
-	 * get the stored metadata of a file or folder
117
-	 *
118
-	 * @param string | int $file either the path of a file or folder or the file id for a file or folder
119
-	 * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
120
-	 */
121
-	public function get($file) {
122
-		if (is_string($file) or $file == '') {
123
-			// normalize file
124
-			$file = $this->normalize($file);
125
-
126
-			$where = 'WHERE `storage` = ? AND `path_hash` = ?';
127
-			$params = array($this->getNumericStorageId(), md5($file));
128
-		} else { //file id
129
-			$where = 'WHERE `fileid` = ?';
130
-			$params = array($file);
131
-		}
132
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
57
+    use MoveFromCacheTrait {
58
+        MoveFromCacheTrait::moveFromCache as moveFromCacheFallback;
59
+    }
60
+
61
+    /**
62
+     * @var array partial data for the cache
63
+     */
64
+    protected $partial = array();
65
+
66
+    /**
67
+     * @var string
68
+     */
69
+    protected $storageId;
70
+
71
+    /**
72
+     * @var Storage $storageCache
73
+     */
74
+    protected $storageCache;
75
+
76
+    /** @var IMimeTypeLoader */
77
+    protected $mimetypeLoader;
78
+
79
+    /**
80
+     * @var IDBConnection
81
+     */
82
+    protected $connection;
83
+
84
+    /** @var QuerySearchHelper */
85
+    protected $querySearchHelper;
86
+
87
+    /**
88
+     * @param \OC\Files\Storage\Storage|string $storage
89
+     */
90
+    public function __construct($storage) {
91
+        if ($storage instanceof \OC\Files\Storage\Storage) {
92
+            $this->storageId = $storage->getId();
93
+        } else {
94
+            $this->storageId = $storage;
95
+        }
96
+        if (strlen($this->storageId) > 64) {
97
+            $this->storageId = md5($this->storageId);
98
+        }
99
+
100
+        $this->storageCache = new Storage($storage);
101
+        $this->mimetypeLoader = \OC::$server->getMimeTypeLoader();
102
+        $this->connection = \OC::$server->getDatabaseConnection();
103
+        $this->querySearchHelper = new QuerySearchHelper($this->mimetypeLoader);
104
+    }
105
+
106
+    /**
107
+     * Get the numeric storage id for this cache's storage
108
+     *
109
+     * @return int
110
+     */
111
+    public function getNumericStorageId() {
112
+        return $this->storageCache->getNumericId();
113
+    }
114
+
115
+    /**
116
+     * get the stored metadata of a file or folder
117
+     *
118
+     * @param string | int $file either the path of a file or folder or the file id for a file or folder
119
+     * @return ICacheEntry|false the cache entry as array of false if the file is not found in the cache
120
+     */
121
+    public function get($file) {
122
+        if (is_string($file) or $file == '') {
123
+            // normalize file
124
+            $file = $this->normalize($file);
125
+
126
+            $where = 'WHERE `storage` = ? AND `path_hash` = ?';
127
+            $params = array($this->getNumericStorageId(), md5($file));
128
+        } else { //file id
129
+            $where = 'WHERE `fileid` = ?';
130
+            $params = array($file);
131
+        }
132
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
133 133
 					   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
134 134
 				FROM `*PREFIX*filecache` ' . $where;
135
-		$result = $this->connection->executeQuery($sql, $params);
136
-		$data = $result->fetch();
137
-
138
-		//FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
139
-		//PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
140
-		if ($data === null) {
141
-			$data = false;
142
-		}
143
-
144
-		//merge partial data
145
-		if (!$data and is_string($file)) {
146
-			if (isset($this->partial[$file])) {
147
-				$data = $this->partial[$file];
148
-			}
149
-			return $data;
150
-		} else {
151
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
152
-		}
153
-	}
154
-
155
-	/**
156
-	 * Create a CacheEntry from database row
157
-	 *
158
-	 * @param array $data
159
-	 * @param IMimeTypeLoader $mimetypeLoader
160
-	 * @return CacheEntry
161
-	 */
162
-	public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
163
-		//fix types
164
-		$data['fileid'] = (int)$data['fileid'];
165
-		$data['parent'] = (int)$data['parent'];
166
-		$data['size'] = 0 + $data['size'];
167
-		$data['mtime'] = (int)$data['mtime'];
168
-		$data['storage_mtime'] = (int)$data['storage_mtime'];
169
-		$data['encryptedVersion'] = (int)$data['encrypted'];
170
-		$data['encrypted'] = (bool)$data['encrypted'];
171
-		$data['storage_id'] = $data['storage'];
172
-		$data['storage'] = (int)$data['storage'];
173
-		$data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
174
-		$data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
175
-		if ($data['storage_mtime'] == 0) {
176
-			$data['storage_mtime'] = $data['mtime'];
177
-		}
178
-		$data['permissions'] = (int)$data['permissions'];
179
-		return new CacheEntry($data);
180
-	}
181
-
182
-	/**
183
-	 * get the metadata of all files stored in $folder
184
-	 *
185
-	 * @param string $folder
186
-	 * @return ICacheEntry[]
187
-	 */
188
-	public function getFolderContents($folder) {
189
-		$fileId = $this->getId($folder);
190
-		return $this->getFolderContentsById($fileId);
191
-	}
192
-
193
-	/**
194
-	 * get the metadata of all files stored in $folder
195
-	 *
196
-	 * @param int $fileId the file id of the folder
197
-	 * @return ICacheEntry[]
198
-	 */
199
-	public function getFolderContentsById($fileId) {
200
-		if ($fileId > -1) {
201
-			$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
135
+        $result = $this->connection->executeQuery($sql, $params);
136
+        $data = $result->fetch();
137
+
138
+        //FIXME hide this HACK in the next database layer, or just use doctrine and get rid of MDB2 and PDO
139
+        //PDO returns false, MDB2 returns null, oracle always uses MDB2, so convert null to false
140
+        if ($data === null) {
141
+            $data = false;
142
+        }
143
+
144
+        //merge partial data
145
+        if (!$data and is_string($file)) {
146
+            if (isset($this->partial[$file])) {
147
+                $data = $this->partial[$file];
148
+            }
149
+            return $data;
150
+        } else {
151
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
152
+        }
153
+    }
154
+
155
+    /**
156
+     * Create a CacheEntry from database row
157
+     *
158
+     * @param array $data
159
+     * @param IMimeTypeLoader $mimetypeLoader
160
+     * @return CacheEntry
161
+     */
162
+    public static function cacheEntryFromData($data, IMimeTypeLoader $mimetypeLoader) {
163
+        //fix types
164
+        $data['fileid'] = (int)$data['fileid'];
165
+        $data['parent'] = (int)$data['parent'];
166
+        $data['size'] = 0 + $data['size'];
167
+        $data['mtime'] = (int)$data['mtime'];
168
+        $data['storage_mtime'] = (int)$data['storage_mtime'];
169
+        $data['encryptedVersion'] = (int)$data['encrypted'];
170
+        $data['encrypted'] = (bool)$data['encrypted'];
171
+        $data['storage_id'] = $data['storage'];
172
+        $data['storage'] = (int)$data['storage'];
173
+        $data['mimetype'] = $mimetypeLoader->getMimetypeById($data['mimetype']);
174
+        $data['mimepart'] = $mimetypeLoader->getMimetypeById($data['mimepart']);
175
+        if ($data['storage_mtime'] == 0) {
176
+            $data['storage_mtime'] = $data['mtime'];
177
+        }
178
+        $data['permissions'] = (int)$data['permissions'];
179
+        return new CacheEntry($data);
180
+    }
181
+
182
+    /**
183
+     * get the metadata of all files stored in $folder
184
+     *
185
+     * @param string $folder
186
+     * @return ICacheEntry[]
187
+     */
188
+    public function getFolderContents($folder) {
189
+        $fileId = $this->getId($folder);
190
+        return $this->getFolderContentsById($fileId);
191
+    }
192
+
193
+    /**
194
+     * get the metadata of all files stored in $folder
195
+     *
196
+     * @param int $fileId the file id of the folder
197
+     * @return ICacheEntry[]
198
+     */
199
+    public function getFolderContentsById($fileId) {
200
+        if ($fileId > -1) {
201
+            $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `mtime`,
202 202
 						   `storage_mtime`, `encrypted`, `etag`, `permissions`, `checksum`
203 203
 					FROM `*PREFIX*filecache` WHERE `parent` = ? ORDER BY `name` ASC';
204
-			$result = $this->connection->executeQuery($sql, [$fileId]);
205
-			$files = $result->fetchAll();
206
-			return array_map(function (array $data) {
207
-				return self::cacheEntryFromData($data, $this->mimetypeLoader);;
208
-			}, $files);
209
-		} else {
210
-			return array();
211
-		}
212
-	}
213
-
214
-	/**
215
-	 * insert or update meta data for a file or folder
216
-	 *
217
-	 * @param string $file
218
-	 * @param array $data
219
-	 *
220
-	 * @return int file id
221
-	 * @throws \RuntimeException
222
-	 */
223
-	public function put($file, array $data) {
224
-		if (($id = $this->getId($file)) > -1) {
225
-			$this->update($id, $data);
226
-			return $id;
227
-		} else {
228
-			return $this->insert($file, $data);
229
-		}
230
-	}
231
-
232
-	/**
233
-	 * insert meta data for a new file or folder
234
-	 *
235
-	 * @param string $file
236
-	 * @param array $data
237
-	 *
238
-	 * @return int file id
239
-	 * @throws \RuntimeException
240
-	 */
241
-	public function insert($file, array $data) {
242
-		// normalize file
243
-		$file = $this->normalize($file);
244
-
245
-		if (isset($this->partial[$file])) { //add any saved partial data
246
-			$data = array_merge($this->partial[$file], $data);
247
-			unset($this->partial[$file]);
248
-		}
249
-
250
-		$requiredFields = array('size', 'mtime', 'mimetype');
251
-		foreach ($requiredFields as $field) {
252
-			if (!isset($data[$field])) { //data not complete save as partial and return
253
-				$this->partial[$file] = $data;
254
-				return -1;
255
-			}
256
-		}
257
-
258
-		$data['path'] = $file;
259
-		$data['parent'] = $this->getParentId($file);
260
-		$data['name'] = \OC_Util::basename($file);
261
-
262
-		list($queryParts, $params) = $this->buildParts($data);
263
-		$queryParts[] = '`storage`';
264
-		$params[] = $this->getNumericStorageId();
265
-
266
-		$queryParts = array_map(function ($item) {
267
-			return trim($item, "`");
268
-		}, $queryParts);
269
-		$values = array_combine($queryParts, $params);
270
-		if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [
271
-			'storage',
272
-			'path_hash',
273
-		])
274
-		) {
275
-			return (int)$this->connection->lastInsertId('*PREFIX*filecache');
276
-		}
277
-
278
-		// The file was created in the mean time
279
-		if (($id = $this->getId($file)) > -1) {
280
-			$this->update($id, $data);
281
-			return $id;
282
-		} else {
283
-			throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.');
284
-		}
285
-	}
286
-
287
-	/**
288
-	 * update the metadata of an existing file or folder in the cache
289
-	 *
290
-	 * @param int $id the fileid of the existing file or folder
291
-	 * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
292
-	 */
293
-	public function update($id, array $data) {
294
-
295
-		if (isset($data['path'])) {
296
-			// normalize path
297
-			$data['path'] = $this->normalize($data['path']);
298
-		}
299
-
300
-		if (isset($data['name'])) {
301
-			// normalize path
302
-			$data['name'] = $this->normalize($data['name']);
303
-		}
304
-
305
-		list($queryParts, $params) = $this->buildParts($data);
306
-		// duplicate $params because we need the parts twice in the SQL statement
307
-		// once for the SET part, once in the WHERE clause
308
-		$params = array_merge($params, $params);
309
-		$params[] = $id;
310
-
311
-		// don't update if the data we try to set is the same as the one in the record
312
-		// some databases (Postgres) don't like superfluous updates
313
-		$sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
314
-			'WHERE (' .
315
-			implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
316
-			implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
317
-			') AND `fileid` = ? ';
318
-		$this->connection->executeQuery($sql, $params);
319
-
320
-	}
321
-
322
-	/**
323
-	 * extract query parts and params array from data array
324
-	 *
325
-	 * @param array $data
326
-	 * @return array [$queryParts, $params]
327
-	 *        $queryParts: string[], the (escaped) column names to be set in the query
328
-	 *        $params: mixed[], the new values for the columns, to be passed as params to the query
329
-	 */
330
-	protected function buildParts(array $data) {
331
-		$fields = array(
332
-			'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
333
-			'etag', 'permissions', 'checksum');
334
-
335
-		$doNotCopyStorageMTime = false;
336
-		if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
337
-			// this horrific magic tells it to not copy storage_mtime to mtime
338
-			unset($data['mtime']);
339
-			$doNotCopyStorageMTime = true;
340
-		}
341
-
342
-		$params = array();
343
-		$queryParts = array();
344
-		foreach ($data as $name => $value) {
345
-			if (array_search($name, $fields) !== false) {
346
-				if ($name === 'path') {
347
-					$params[] = md5($value);
348
-					$queryParts[] = '`path_hash`';
349
-				} elseif ($name === 'mimetype') {
350
-					$params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
351
-					$queryParts[] = '`mimepart`';
352
-					$value = $this->mimetypeLoader->getId($value);
353
-				} elseif ($name === 'storage_mtime') {
354
-					if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
355
-						$params[] = $value;
356
-						$queryParts[] = '`mtime`';
357
-					}
358
-				} elseif ($name === 'encrypted') {
359
-					if (isset($data['encryptedVersion'])) {
360
-						$value = $data['encryptedVersion'];
361
-					} else {
362
-						// Boolean to integer conversion
363
-						$value = $value ? 1 : 0;
364
-					}
365
-				}
366
-				$params[] = $value;
367
-				$queryParts[] = '`' . $name . '`';
368
-			}
369
-		}
370
-		return array($queryParts, $params);
371
-	}
372
-
373
-	/**
374
-	 * get the file id for a file
375
-	 *
376
-	 * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
377
-	 *
378
-	 * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
379
-	 *
380
-	 * @param string $file
381
-	 * @return int
382
-	 */
383
-	public function getId($file) {
384
-		// normalize file
385
-		$file = $this->normalize($file);
386
-
387
-		$pathHash = md5($file);
388
-
389
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
390
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
391
-		if ($row = $result->fetch()) {
392
-			return $row['fileid'];
393
-		} else {
394
-			return -1;
395
-		}
396
-	}
397
-
398
-	/**
399
-	 * get the id of the parent folder of a file
400
-	 *
401
-	 * @param string $file
402
-	 * @return int
403
-	 */
404
-	public function getParentId($file) {
405
-		if ($file === '') {
406
-			return -1;
407
-		} else {
408
-			$parent = $this->getParentPath($file);
409
-			return (int)$this->getId($parent);
410
-		}
411
-	}
412
-
413
-	private function getParentPath($path) {
414
-		$parent = dirname($path);
415
-		if ($parent === '.') {
416
-			$parent = '';
417
-		}
418
-		return $parent;
419
-	}
420
-
421
-	/**
422
-	 * check if a file is available in the cache
423
-	 *
424
-	 * @param string $file
425
-	 * @return bool
426
-	 */
427
-	public function inCache($file) {
428
-		return $this->getId($file) != -1;
429
-	}
430
-
431
-	/**
432
-	 * remove a file or folder from the cache
433
-	 *
434
-	 * when removing a folder from the cache all files and folders inside the folder will be removed as well
435
-	 *
436
-	 * @param string $file
437
-	 */
438
-	public function remove($file) {
439
-		$entry = $this->get($file);
440
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
441
-		$this->connection->executeQuery($sql, array($entry['fileid']));
442
-		if ($entry['mimetype'] === 'httpd/unix-directory') {
443
-			$this->removeChildren($entry);
444
-		}
445
-	}
446
-
447
-	/**
448
-	 * Get all sub folders of a folder
449
-	 *
450
-	 * @param array $entry the cache entry of the folder to get the subfolders for
451
-	 * @return array[] the cache entries for the subfolders
452
-	 */
453
-	private function getSubFolders($entry) {
454
-		$children = $this->getFolderContentsById($entry['fileid']);
455
-		return array_filter($children, function ($child) {
456
-			return $child['mimetype'] === 'httpd/unix-directory';
457
-		});
458
-	}
459
-
460
-	/**
461
-	 * Recursively remove all children of a folder
462
-	 *
463
-	 * @param array $entry the cache entry of the folder to remove the children of
464
-	 * @throws \OC\DatabaseException
465
-	 */
466
-	private function removeChildren($entry) {
467
-		$subFolders = $this->getSubFolders($entry);
468
-		foreach ($subFolders as $folder) {
469
-			$this->removeChildren($folder);
470
-		}
471
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
472
-		$this->connection->executeQuery($sql, array($entry['fileid']));
473
-	}
474
-
475
-	/**
476
-	 * Move a file or folder in the cache
477
-	 *
478
-	 * @param string $source
479
-	 * @param string $target
480
-	 */
481
-	public function move($source, $target) {
482
-		$this->moveFromCache($this, $source, $target);
483
-	}
484
-
485
-	/**
486
-	 * Get the storage id and path needed for a move
487
-	 *
488
-	 * @param string $path
489
-	 * @return array [$storageId, $internalPath]
490
-	 */
491
-	protected function getMoveInfo($path) {
492
-		return [$this->getNumericStorageId(), $path];
493
-	}
494
-
495
-	/**
496
-	 * Move a file or folder in the cache
497
-	 *
498
-	 * @param \OCP\Files\Cache\ICache $sourceCache
499
-	 * @param string $sourcePath
500
-	 * @param string $targetPath
501
-	 * @throws \OC\DatabaseException
502
-	 */
503
-	public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
504
-		if ($sourceCache instanceof Cache) {
505
-			// normalize source and target
506
-			$sourcePath = $this->normalize($sourcePath);
507
-			$targetPath = $this->normalize($targetPath);
508
-
509
-			$sourceData = $sourceCache->get($sourcePath);
510
-			$sourceId = $sourceData['fileid'];
511
-			$newParentId = $this->getParentId($targetPath);
512
-
513
-			list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
514
-			list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
515
-
516
-			// sql for final update
517
-			$moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` =  ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
518
-
519
-			if ($sourceData['mimetype'] === 'httpd/unix-directory') {
520
-				//find all child entries
521
-				$sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
522
-				$result = $this->connection->executeQuery($sql, [$sourceStorageId, $this->connection->escapeLikeParameter($sourcePath) . '/%']);
523
-				$childEntries = $result->fetchAll();
524
-				$sourceLength = strlen($sourcePath);
525
-				$this->connection->beginTransaction();
526
-				$query = $this->connection->prepare('UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?');
527
-
528
-				foreach ($childEntries as $child) {
529
-					$newTargetPath = $targetPath . substr($child['path'], $sourceLength);
530
-					$query->execute([$targetStorageId, $newTargetPath, md5($newTargetPath), $child['fileid']]);
531
-				}
532
-				$this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
533
-				$this->connection->commit();
534
-			} else {
535
-				$this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
536
-			}
537
-		} else {
538
-			$this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
539
-		}
540
-	}
541
-
542
-	/**
543
-	 * remove all entries for files that are stored on the storage from the cache
544
-	 */
545
-	public function clear() {
546
-		$sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
547
-		$this->connection->executeQuery($sql, array($this->getNumericStorageId()));
548
-
549
-		$sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
550
-		$this->connection->executeQuery($sql, array($this->storageId));
551
-	}
552
-
553
-	/**
554
-	 * Get the scan status of a file
555
-	 *
556
-	 * - Cache::NOT_FOUND: File is not in the cache
557
-	 * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
558
-	 * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
559
-	 * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
560
-	 *
561
-	 * @param string $file
562
-	 *
563
-	 * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
564
-	 */
565
-	public function getStatus($file) {
566
-		// normalize file
567
-		$file = $this->normalize($file);
568
-
569
-		$pathHash = md5($file);
570
-		$sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
571
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
572
-		if ($row = $result->fetch()) {
573
-			if ((int)$row['size'] === -1) {
574
-				return self::SHALLOW;
575
-			} else {
576
-				return self::COMPLETE;
577
-			}
578
-		} else {
579
-			if (isset($this->partial[$file])) {
580
-				return self::PARTIAL;
581
-			} else {
582
-				return self::NOT_FOUND;
583
-			}
584
-		}
585
-	}
586
-
587
-	/**
588
-	 * search for files matching $pattern
589
-	 *
590
-	 * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
591
-	 * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
592
-	 */
593
-	public function search($pattern) {
594
-		// normalize pattern
595
-		$pattern = $this->normalize($pattern);
596
-
597
-		if ($pattern === '%%') {
598
-			return [];
599
-		}
600
-
601
-
602
-		$sql = '
204
+            $result = $this->connection->executeQuery($sql, [$fileId]);
205
+            $files = $result->fetchAll();
206
+            return array_map(function (array $data) {
207
+                return self::cacheEntryFromData($data, $this->mimetypeLoader);;
208
+            }, $files);
209
+        } else {
210
+            return array();
211
+        }
212
+    }
213
+
214
+    /**
215
+     * insert or update meta data for a file or folder
216
+     *
217
+     * @param string $file
218
+     * @param array $data
219
+     *
220
+     * @return int file id
221
+     * @throws \RuntimeException
222
+     */
223
+    public function put($file, array $data) {
224
+        if (($id = $this->getId($file)) > -1) {
225
+            $this->update($id, $data);
226
+            return $id;
227
+        } else {
228
+            return $this->insert($file, $data);
229
+        }
230
+    }
231
+
232
+    /**
233
+     * insert meta data for a new file or folder
234
+     *
235
+     * @param string $file
236
+     * @param array $data
237
+     *
238
+     * @return int file id
239
+     * @throws \RuntimeException
240
+     */
241
+    public function insert($file, array $data) {
242
+        // normalize file
243
+        $file = $this->normalize($file);
244
+
245
+        if (isset($this->partial[$file])) { //add any saved partial data
246
+            $data = array_merge($this->partial[$file], $data);
247
+            unset($this->partial[$file]);
248
+        }
249
+
250
+        $requiredFields = array('size', 'mtime', 'mimetype');
251
+        foreach ($requiredFields as $field) {
252
+            if (!isset($data[$field])) { //data not complete save as partial and return
253
+                $this->partial[$file] = $data;
254
+                return -1;
255
+            }
256
+        }
257
+
258
+        $data['path'] = $file;
259
+        $data['parent'] = $this->getParentId($file);
260
+        $data['name'] = \OC_Util::basename($file);
261
+
262
+        list($queryParts, $params) = $this->buildParts($data);
263
+        $queryParts[] = '`storage`';
264
+        $params[] = $this->getNumericStorageId();
265
+
266
+        $queryParts = array_map(function ($item) {
267
+            return trim($item, "`");
268
+        }, $queryParts);
269
+        $values = array_combine($queryParts, $params);
270
+        if (\OC::$server->getDatabaseConnection()->insertIfNotExist('*PREFIX*filecache', $values, [
271
+            'storage',
272
+            'path_hash',
273
+        ])
274
+        ) {
275
+            return (int)$this->connection->lastInsertId('*PREFIX*filecache');
276
+        }
277
+
278
+        // The file was created in the mean time
279
+        if (($id = $this->getId($file)) > -1) {
280
+            $this->update($id, $data);
281
+            return $id;
282
+        } else {
283
+            throw new \RuntimeException('File entry could not be inserted with insertIfNotExist() but could also not be selected with getId() in order to perform an update. Please try again.');
284
+        }
285
+    }
286
+
287
+    /**
288
+     * update the metadata of an existing file or folder in the cache
289
+     *
290
+     * @param int $id the fileid of the existing file or folder
291
+     * @param array $data [$key => $value] the metadata to update, only the fields provided in the array will be updated, non-provided values will remain unchanged
292
+     */
293
+    public function update($id, array $data) {
294
+
295
+        if (isset($data['path'])) {
296
+            // normalize path
297
+            $data['path'] = $this->normalize($data['path']);
298
+        }
299
+
300
+        if (isset($data['name'])) {
301
+            // normalize path
302
+            $data['name'] = $this->normalize($data['name']);
303
+        }
304
+
305
+        list($queryParts, $params) = $this->buildParts($data);
306
+        // duplicate $params because we need the parts twice in the SQL statement
307
+        // once for the SET part, once in the WHERE clause
308
+        $params = array_merge($params, $params);
309
+        $params[] = $id;
310
+
311
+        // don't update if the data we try to set is the same as the one in the record
312
+        // some databases (Postgres) don't like superfluous updates
313
+        $sql = 'UPDATE `*PREFIX*filecache` SET ' . implode(' = ?, ', $queryParts) . '=? ' .
314
+            'WHERE (' .
315
+            implode(' <> ? OR ', $queryParts) . ' <> ? OR ' .
316
+            implode(' IS NULL OR ', $queryParts) . ' IS NULL' .
317
+            ') AND `fileid` = ? ';
318
+        $this->connection->executeQuery($sql, $params);
319
+
320
+    }
321
+
322
+    /**
323
+     * extract query parts and params array from data array
324
+     *
325
+     * @param array $data
326
+     * @return array [$queryParts, $params]
327
+     *        $queryParts: string[], the (escaped) column names to be set in the query
328
+     *        $params: mixed[], the new values for the columns, to be passed as params to the query
329
+     */
330
+    protected function buildParts(array $data) {
331
+        $fields = array(
332
+            'path', 'parent', 'name', 'mimetype', 'size', 'mtime', 'storage_mtime', 'encrypted',
333
+            'etag', 'permissions', 'checksum');
334
+
335
+        $doNotCopyStorageMTime = false;
336
+        if (array_key_exists('mtime', $data) && $data['mtime'] === null) {
337
+            // this horrific magic tells it to not copy storage_mtime to mtime
338
+            unset($data['mtime']);
339
+            $doNotCopyStorageMTime = true;
340
+        }
341
+
342
+        $params = array();
343
+        $queryParts = array();
344
+        foreach ($data as $name => $value) {
345
+            if (array_search($name, $fields) !== false) {
346
+                if ($name === 'path') {
347
+                    $params[] = md5($value);
348
+                    $queryParts[] = '`path_hash`';
349
+                } elseif ($name === 'mimetype') {
350
+                    $params[] = $this->mimetypeLoader->getId(substr($value, 0, strpos($value, '/')));
351
+                    $queryParts[] = '`mimepart`';
352
+                    $value = $this->mimetypeLoader->getId($value);
353
+                } elseif ($name === 'storage_mtime') {
354
+                    if (!$doNotCopyStorageMTime && !isset($data['mtime'])) {
355
+                        $params[] = $value;
356
+                        $queryParts[] = '`mtime`';
357
+                    }
358
+                } elseif ($name === 'encrypted') {
359
+                    if (isset($data['encryptedVersion'])) {
360
+                        $value = $data['encryptedVersion'];
361
+                    } else {
362
+                        // Boolean to integer conversion
363
+                        $value = $value ? 1 : 0;
364
+                    }
365
+                }
366
+                $params[] = $value;
367
+                $queryParts[] = '`' . $name . '`';
368
+            }
369
+        }
370
+        return array($queryParts, $params);
371
+    }
372
+
373
+    /**
374
+     * get the file id for a file
375
+     *
376
+     * A file id is a numeric id for a file or folder that's unique within an owncloud instance which stays the same for the lifetime of a file
377
+     *
378
+     * File ids are easiest way for apps to store references to a file since unlike paths they are not affected by renames or sharing
379
+     *
380
+     * @param string $file
381
+     * @return int
382
+     */
383
+    public function getId($file) {
384
+        // normalize file
385
+        $file = $this->normalize($file);
386
+
387
+        $pathHash = md5($file);
388
+
389
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
390
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
391
+        if ($row = $result->fetch()) {
392
+            return $row['fileid'];
393
+        } else {
394
+            return -1;
395
+        }
396
+    }
397
+
398
+    /**
399
+     * get the id of the parent folder of a file
400
+     *
401
+     * @param string $file
402
+     * @return int
403
+     */
404
+    public function getParentId($file) {
405
+        if ($file === '') {
406
+            return -1;
407
+        } else {
408
+            $parent = $this->getParentPath($file);
409
+            return (int)$this->getId($parent);
410
+        }
411
+    }
412
+
413
+    private function getParentPath($path) {
414
+        $parent = dirname($path);
415
+        if ($parent === '.') {
416
+            $parent = '';
417
+        }
418
+        return $parent;
419
+    }
420
+
421
+    /**
422
+     * check if a file is available in the cache
423
+     *
424
+     * @param string $file
425
+     * @return bool
426
+     */
427
+    public function inCache($file) {
428
+        return $this->getId($file) != -1;
429
+    }
430
+
431
+    /**
432
+     * remove a file or folder from the cache
433
+     *
434
+     * when removing a folder from the cache all files and folders inside the folder will be removed as well
435
+     *
436
+     * @param string $file
437
+     */
438
+    public function remove($file) {
439
+        $entry = $this->get($file);
440
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `fileid` = ?';
441
+        $this->connection->executeQuery($sql, array($entry['fileid']));
442
+        if ($entry['mimetype'] === 'httpd/unix-directory') {
443
+            $this->removeChildren($entry);
444
+        }
445
+    }
446
+
447
+    /**
448
+     * Get all sub folders of a folder
449
+     *
450
+     * @param array $entry the cache entry of the folder to get the subfolders for
451
+     * @return array[] the cache entries for the subfolders
452
+     */
453
+    private function getSubFolders($entry) {
454
+        $children = $this->getFolderContentsById($entry['fileid']);
455
+        return array_filter($children, function ($child) {
456
+            return $child['mimetype'] === 'httpd/unix-directory';
457
+        });
458
+    }
459
+
460
+    /**
461
+     * Recursively remove all children of a folder
462
+     *
463
+     * @param array $entry the cache entry of the folder to remove the children of
464
+     * @throws \OC\DatabaseException
465
+     */
466
+    private function removeChildren($entry) {
467
+        $subFolders = $this->getSubFolders($entry);
468
+        foreach ($subFolders as $folder) {
469
+            $this->removeChildren($folder);
470
+        }
471
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `parent` = ?';
472
+        $this->connection->executeQuery($sql, array($entry['fileid']));
473
+    }
474
+
475
+    /**
476
+     * Move a file or folder in the cache
477
+     *
478
+     * @param string $source
479
+     * @param string $target
480
+     */
481
+    public function move($source, $target) {
482
+        $this->moveFromCache($this, $source, $target);
483
+    }
484
+
485
+    /**
486
+     * Get the storage id and path needed for a move
487
+     *
488
+     * @param string $path
489
+     * @return array [$storageId, $internalPath]
490
+     */
491
+    protected function getMoveInfo($path) {
492
+        return [$this->getNumericStorageId(), $path];
493
+    }
494
+
495
+    /**
496
+     * Move a file or folder in the cache
497
+     *
498
+     * @param \OCP\Files\Cache\ICache $sourceCache
499
+     * @param string $sourcePath
500
+     * @param string $targetPath
501
+     * @throws \OC\DatabaseException
502
+     */
503
+    public function moveFromCache(ICache $sourceCache, $sourcePath, $targetPath) {
504
+        if ($sourceCache instanceof Cache) {
505
+            // normalize source and target
506
+            $sourcePath = $this->normalize($sourcePath);
507
+            $targetPath = $this->normalize($targetPath);
508
+
509
+            $sourceData = $sourceCache->get($sourcePath);
510
+            $sourceId = $sourceData['fileid'];
511
+            $newParentId = $this->getParentId($targetPath);
512
+
513
+            list($sourceStorageId, $sourcePath) = $sourceCache->getMoveInfo($sourcePath);
514
+            list($targetStorageId, $targetPath) = $this->getMoveInfo($targetPath);
515
+
516
+            // sql for final update
517
+            $moveSql = 'UPDATE `*PREFIX*filecache` SET `storage` =  ?, `path` = ?, `path_hash` = ?, `name` = ?, `parent` =? WHERE `fileid` = ?';
518
+
519
+            if ($sourceData['mimetype'] === 'httpd/unix-directory') {
520
+                //find all child entries
521
+                $sql = 'SELECT `path`, `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path` LIKE ?';
522
+                $result = $this->connection->executeQuery($sql, [$sourceStorageId, $this->connection->escapeLikeParameter($sourcePath) . '/%']);
523
+                $childEntries = $result->fetchAll();
524
+                $sourceLength = strlen($sourcePath);
525
+                $this->connection->beginTransaction();
526
+                $query = $this->connection->prepare('UPDATE `*PREFIX*filecache` SET `storage` = ?, `path` = ?, `path_hash` = ? WHERE `fileid` = ?');
527
+
528
+                foreach ($childEntries as $child) {
529
+                    $newTargetPath = $targetPath . substr($child['path'], $sourceLength);
530
+                    $query->execute([$targetStorageId, $newTargetPath, md5($newTargetPath), $child['fileid']]);
531
+                }
532
+                $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
533
+                $this->connection->commit();
534
+            } else {
535
+                $this->connection->executeQuery($moveSql, [$targetStorageId, $targetPath, md5($targetPath), basename($targetPath), $newParentId, $sourceId]);
536
+            }
537
+        } else {
538
+            $this->moveFromCacheFallback($sourceCache, $sourcePath, $targetPath);
539
+        }
540
+    }
541
+
542
+    /**
543
+     * remove all entries for files that are stored on the storage from the cache
544
+     */
545
+    public function clear() {
546
+        $sql = 'DELETE FROM `*PREFIX*filecache` WHERE `storage` = ?';
547
+        $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
548
+
549
+        $sql = 'DELETE FROM `*PREFIX*storages` WHERE `id` = ?';
550
+        $this->connection->executeQuery($sql, array($this->storageId));
551
+    }
552
+
553
+    /**
554
+     * Get the scan status of a file
555
+     *
556
+     * - Cache::NOT_FOUND: File is not in the cache
557
+     * - Cache::PARTIAL: File is not stored in the cache but some incomplete data is known
558
+     * - Cache::SHALLOW: The folder and it's direct children are in the cache but not all sub folders are fully scanned
559
+     * - Cache::COMPLETE: The file or folder, with all it's children) are fully scanned
560
+     *
561
+     * @param string $file
562
+     *
563
+     * @return int Cache::NOT_FOUND, Cache::PARTIAL, Cache::SHALLOW or Cache::COMPLETE
564
+     */
565
+    public function getStatus($file) {
566
+        // normalize file
567
+        $file = $this->normalize($file);
568
+
569
+        $pathHash = md5($file);
570
+        $sql = 'SELECT `size` FROM `*PREFIX*filecache` WHERE `storage` = ? AND `path_hash` = ?';
571
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId(), $pathHash));
572
+        if ($row = $result->fetch()) {
573
+            if ((int)$row['size'] === -1) {
574
+                return self::SHALLOW;
575
+            } else {
576
+                return self::COMPLETE;
577
+            }
578
+        } else {
579
+            if (isset($this->partial[$file])) {
580
+                return self::PARTIAL;
581
+            } else {
582
+                return self::NOT_FOUND;
583
+            }
584
+        }
585
+    }
586
+
587
+    /**
588
+     * search for files matching $pattern
589
+     *
590
+     * @param string $pattern the search pattern using SQL search syntax (e.g. '%searchstring%')
591
+     * @return ICacheEntry[] an array of cache entries where the name matches the search pattern
592
+     */
593
+    public function search($pattern) {
594
+        // normalize pattern
595
+        $pattern = $this->normalize($pattern);
596
+
597
+        if ($pattern === '%%') {
598
+            return [];
599
+        }
600
+
601
+
602
+        $sql = '
603 603
 			SELECT `fileid`, `storage`, `path`, `parent`, `name`,
604 604
 				`mimetype`, `storage_mtime`, `mimepart`, `size`, `mtime`,
605 605
 				 `encrypted`, `etag`, `permissions`, `checksum`
606 606
 			FROM `*PREFIX*filecache`
607 607
 			WHERE `storage` = ? AND `name` ILIKE ?';
608
-		$result = $this->connection->executeQuery($sql,
609
-			[$this->getNumericStorageId(), $pattern]
610
-		);
611
-
612
-		return $this->searchResultToCacheEntries($result);
613
-	}
614
-
615
-	/**
616
-	 * @param Statement $result
617
-	 * @return CacheEntry[]
618
-	 */
619
-	private function searchResultToCacheEntries(Statement $result) {
620
-		$files = $result->fetchAll();
621
-
622
-		return array_map(function (array $data) {
623
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
624
-		}, $files);
625
-	}
626
-
627
-	/**
628
-	 * search for files by mimetype
629
-	 *
630
-	 * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
631
-	 *        where it will search for all mimetypes in the group ('image/*')
632
-	 * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
633
-	 */
634
-	public function searchByMime($mimetype) {
635
-		if (strpos($mimetype, '/')) {
636
-			$where = '`mimetype` = ?';
637
-		} else {
638
-			$where = '`mimepart` = ?';
639
-		}
640
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
608
+        $result = $this->connection->executeQuery($sql,
609
+            [$this->getNumericStorageId(), $pattern]
610
+        );
611
+
612
+        return $this->searchResultToCacheEntries($result);
613
+    }
614
+
615
+    /**
616
+     * @param Statement $result
617
+     * @return CacheEntry[]
618
+     */
619
+    private function searchResultToCacheEntries(Statement $result) {
620
+        $files = $result->fetchAll();
621
+
622
+        return array_map(function (array $data) {
623
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
624
+        }, $files);
625
+    }
626
+
627
+    /**
628
+     * search for files by mimetype
629
+     *
630
+     * @param string $mimetype either a full mimetype to search ('text/plain') or only the first part of a mimetype ('image')
631
+     *        where it will search for all mimetypes in the group ('image/*')
632
+     * @return ICacheEntry[] an array of cache entries where the mimetype matches the search
633
+     */
634
+    public function searchByMime($mimetype) {
635
+        if (strpos($mimetype, '/')) {
636
+            $where = '`mimetype` = ?';
637
+        } else {
638
+            $where = '`mimepart` = ?';
639
+        }
640
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, `mimetype`, `mimepart`, `size`, `storage_mtime`, `mtime`, `encrypted`, `etag`, `permissions`, `checksum`
641 641
 				FROM `*PREFIX*filecache` WHERE ' . $where . ' AND `storage` = ?';
642
-		$mimetype = $this->mimetypeLoader->getId($mimetype);
643
-		$result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
644
-
645
-		return $this->searchResultToCacheEntries($result);
646
-	}
647
-
648
-	public function searchQuery(ISearchQuery $searchQuery) {
649
-		$builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
650
-
651
-		$query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
652
-			->from('filecache', 'file');
653
-
654
-		$query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
655
-
656
-		if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
657
-			$query
658
-				->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
659
-				->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
660
-					$builder->expr()->eq('tagmap.type', 'tag.type'),
661
-					$builder->expr()->eq('tagmap.categoryid', 'tag.id')
662
-				))
663
-				->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
664
-				->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
665
-		}
666
-
667
-		$query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
668
-
669
-		if ($searchQuery->getLimit()) {
670
-			$query->setMaxResults($searchQuery->getLimit());
671
-		}
672
-		if ($searchQuery->getOffset()) {
673
-			$query->setFirstResult($searchQuery->getOffset());
674
-		}
675
-
676
-		$result = $query->execute();
677
-		return $this->searchResultToCacheEntries($result);
678
-	}
679
-
680
-	/**
681
-	 * Search for files by tag of a given users.
682
-	 *
683
-	 * Note that every user can tag files differently.
684
-	 *
685
-	 * @param string|int $tag name or tag id
686
-	 * @param string $userId owner of the tags
687
-	 * @return ICacheEntry[] file data
688
-	 */
689
-	public function searchByTag($tag, $userId) {
690
-		$sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
691
-			'`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
692
-			'`encrypted`, `etag`, `permissions`, `checksum` ' .
693
-			'FROM `*PREFIX*filecache` `file`, ' .
694
-			'`*PREFIX*vcategory_to_object` `tagmap`, ' .
695
-			'`*PREFIX*vcategory` `tag` ' .
696
-			// JOIN filecache to vcategory_to_object
697
-			'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
698
-			// JOIN vcategory_to_object to vcategory
699
-			'AND `tagmap`.`type` = `tag`.`type` ' .
700
-			'AND `tagmap`.`categoryid` = `tag`.`id` ' .
701
-			// conditions
702
-			'AND `file`.`storage` = ? ' .
703
-			'AND `tag`.`type` = \'files\' ' .
704
-			'AND `tag`.`uid` = ? ';
705
-		if (is_int($tag)) {
706
-			$sql .= 'AND `tag`.`id` = ? ';
707
-		} else {
708
-			$sql .= 'AND `tag`.`category` = ? ';
709
-		}
710
-		$result = $this->connection->executeQuery(
711
-			$sql,
712
-			[
713
-				$this->getNumericStorageId(),
714
-				$userId,
715
-				$tag
716
-			]
717
-		);
718
-
719
-		$files = $result->fetchAll();
720
-
721
-		return array_map(function (array $data) {
722
-			return self::cacheEntryFromData($data, $this->mimetypeLoader);
723
-		}, $files);
724
-	}
725
-
726
-	/**
727
-	 * Re-calculate the folder size and the size of all parent folders
728
-	 *
729
-	 * @param string|boolean $path
730
-	 * @param array $data (optional) meta data of the folder
731
-	 */
732
-	public function correctFolderSize($path, $data = null) {
733
-		$this->calculateFolderSize($path, $data);
734
-		if ($path !== '') {
735
-			$parent = dirname($path);
736
-			if ($parent === '.' or $parent === '/') {
737
-				$parent = '';
738
-			}
739
-			$this->correctFolderSize($parent);
740
-		}
741
-	}
742
-
743
-	/**
744
-	 * calculate the size of a folder and set it in the cache
745
-	 *
746
-	 * @param string $path
747
-	 * @param array $entry (optional) meta data of the folder
748
-	 * @return int
749
-	 */
750
-	public function calculateFolderSize($path, $entry = null) {
751
-		$totalSize = 0;
752
-		if (is_null($entry) or !isset($entry['fileid'])) {
753
-			$entry = $this->get($path);
754
-		}
755
-		if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
756
-			$id = $entry['fileid'];
757
-			$sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
758
-				'FROM `*PREFIX*filecache` ' .
759
-				'WHERE `parent` = ? AND `storage` = ?';
760
-			$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
761
-			if ($row = $result->fetch()) {
762
-				$result->closeCursor();
763
-				list($sum, $min) = array_values($row);
764
-				$sum = 0 + $sum;
765
-				$min = 0 + $min;
766
-				if ($min === -1) {
767
-					$totalSize = $min;
768
-				} else {
769
-					$totalSize = $sum;
770
-				}
771
-				$update = array();
772
-				if ($entry['size'] !== $totalSize) {
773
-					$update['size'] = $totalSize;
774
-				}
775
-				if (count($update) > 0) {
776
-					$this->update($id, $update);
777
-				}
778
-			} else {
779
-				$result->closeCursor();
780
-			}
781
-		}
782
-		return $totalSize;
783
-	}
784
-
785
-	/**
786
-	 * get all file ids on the files on the storage
787
-	 *
788
-	 * @return int[]
789
-	 */
790
-	public function getAll() {
791
-		$sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
792
-		$result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
793
-		$ids = array();
794
-		while ($row = $result->fetch()) {
795
-			$ids[] = $row['fileid'];
796
-		}
797
-		return $ids;
798
-	}
799
-
800
-	/**
801
-	 * find a folder in the cache which has not been fully scanned
802
-	 *
803
-	 * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
804
-	 * use the one with the highest id gives the best result with the background scanner, since that is most
805
-	 * likely the folder where we stopped scanning previously
806
-	 *
807
-	 * @return string|bool the path of the folder or false when no folder matched
808
-	 */
809
-	public function getIncomplete() {
810
-		$query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
811
-			. ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
812
-		$query->execute([$this->getNumericStorageId()]);
813
-		if ($row = $query->fetch()) {
814
-			return $row['path'];
815
-		} else {
816
-			return false;
817
-		}
818
-	}
819
-
820
-	/**
821
-	 * get the path of a file on this storage by it's file id
822
-	 *
823
-	 * @param int $id the file id of the file or folder to search
824
-	 * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
825
-	 */
826
-	public function getPathById($id) {
827
-		$sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
828
-		$result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
829
-		if ($row = $result->fetch()) {
830
-			// Oracle stores empty strings as null...
831
-			if ($row['path'] === null) {
832
-				return '';
833
-			}
834
-			return $row['path'];
835
-		} else {
836
-			return null;
837
-		}
838
-	}
839
-
840
-	/**
841
-	 * get the storage id of the storage for a file and the internal path of the file
842
-	 * unlike getPathById this does not limit the search to files on this storage and
843
-	 * instead does a global search in the cache table
844
-	 *
845
-	 * @param int $id
846
-	 * @deprecated use getPathById() instead
847
-	 * @return array first element holding the storage id, second the path
848
-	 */
849
-	static public function getById($id) {
850
-		$connection = \OC::$server->getDatabaseConnection();
851
-		$sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
852
-		$result = $connection->executeQuery($sql, array($id));
853
-		if ($row = $result->fetch()) {
854
-			$numericId = $row['storage'];
855
-			$path = $row['path'];
856
-		} else {
857
-			return null;
858
-		}
859
-
860
-		if ($id = Storage::getStorageId($numericId)) {
861
-			return array($id, $path);
862
-		} else {
863
-			return null;
864
-		}
865
-	}
866
-
867
-	/**
868
-	 * normalize the given path
869
-	 *
870
-	 * @param string $path
871
-	 * @return string
872
-	 */
873
-	public function normalize($path) {
874
-
875
-		return trim(\OC_Util::normalizeUnicode($path), '/');
876
-	}
642
+        $mimetype = $this->mimetypeLoader->getId($mimetype);
643
+        $result = $this->connection->executeQuery($sql, array($mimetype, $this->getNumericStorageId()));
644
+
645
+        return $this->searchResultToCacheEntries($result);
646
+    }
647
+
648
+    public function searchQuery(ISearchQuery $searchQuery) {
649
+        $builder = \OC::$server->getDatabaseConnection()->getQueryBuilder();
650
+
651
+        $query = $builder->select(['fileid', 'storage', 'path', 'parent', 'name', 'mimetype', 'mimepart', 'size', 'mtime', 'storage_mtime', 'encrypted', 'etag', 'permissions', 'checksum'])
652
+            ->from('filecache', 'file');
653
+
654
+        $query->where($builder->expr()->eq('storage', $builder->createNamedParameter($this->getNumericStorageId())));
655
+
656
+        if ($this->querySearchHelper->shouldJoinTags($searchQuery->getSearchOperation())) {
657
+            $query
658
+                ->innerJoin('file', 'vcategory_to_object', 'tagmap', $builder->expr()->eq('file.fileid', 'tagmap.objid'))
659
+                ->innerJoin('tagmap', 'vcategory', 'tag', $builder->expr()->andX(
660
+                    $builder->expr()->eq('tagmap.type', 'tag.type'),
661
+                    $builder->expr()->eq('tagmap.categoryid', 'tag.id')
662
+                ))
663
+                ->andWhere($builder->expr()->eq('tag.type', $builder->createNamedParameter('files')))
664
+                ->andWhere($builder->expr()->eq('tag.uid', $builder->createNamedParameter($searchQuery->getUser()->getUID())));
665
+        }
666
+
667
+        $query->andWhere($this->querySearchHelper->searchOperatorToDBExpr($builder, $searchQuery->getSearchOperation()));
668
+
669
+        if ($searchQuery->getLimit()) {
670
+            $query->setMaxResults($searchQuery->getLimit());
671
+        }
672
+        if ($searchQuery->getOffset()) {
673
+            $query->setFirstResult($searchQuery->getOffset());
674
+        }
675
+
676
+        $result = $query->execute();
677
+        return $this->searchResultToCacheEntries($result);
678
+    }
679
+
680
+    /**
681
+     * Search for files by tag of a given users.
682
+     *
683
+     * Note that every user can tag files differently.
684
+     *
685
+     * @param string|int $tag name or tag id
686
+     * @param string $userId owner of the tags
687
+     * @return ICacheEntry[] file data
688
+     */
689
+    public function searchByTag($tag, $userId) {
690
+        $sql = 'SELECT `fileid`, `storage`, `path`, `parent`, `name`, ' .
691
+            '`mimetype`, `mimepart`, `size`, `mtime`, `storage_mtime`, ' .
692
+            '`encrypted`, `etag`, `permissions`, `checksum` ' .
693
+            'FROM `*PREFIX*filecache` `file`, ' .
694
+            '`*PREFIX*vcategory_to_object` `tagmap`, ' .
695
+            '`*PREFIX*vcategory` `tag` ' .
696
+            // JOIN filecache to vcategory_to_object
697
+            'WHERE `file`.`fileid` = `tagmap`.`objid` ' .
698
+            // JOIN vcategory_to_object to vcategory
699
+            'AND `tagmap`.`type` = `tag`.`type` ' .
700
+            'AND `tagmap`.`categoryid` = `tag`.`id` ' .
701
+            // conditions
702
+            'AND `file`.`storage` = ? ' .
703
+            'AND `tag`.`type` = \'files\' ' .
704
+            'AND `tag`.`uid` = ? ';
705
+        if (is_int($tag)) {
706
+            $sql .= 'AND `tag`.`id` = ? ';
707
+        } else {
708
+            $sql .= 'AND `tag`.`category` = ? ';
709
+        }
710
+        $result = $this->connection->executeQuery(
711
+            $sql,
712
+            [
713
+                $this->getNumericStorageId(),
714
+                $userId,
715
+                $tag
716
+            ]
717
+        );
718
+
719
+        $files = $result->fetchAll();
720
+
721
+        return array_map(function (array $data) {
722
+            return self::cacheEntryFromData($data, $this->mimetypeLoader);
723
+        }, $files);
724
+    }
725
+
726
+    /**
727
+     * Re-calculate the folder size and the size of all parent folders
728
+     *
729
+     * @param string|boolean $path
730
+     * @param array $data (optional) meta data of the folder
731
+     */
732
+    public function correctFolderSize($path, $data = null) {
733
+        $this->calculateFolderSize($path, $data);
734
+        if ($path !== '') {
735
+            $parent = dirname($path);
736
+            if ($parent === '.' or $parent === '/') {
737
+                $parent = '';
738
+            }
739
+            $this->correctFolderSize($parent);
740
+        }
741
+    }
742
+
743
+    /**
744
+     * calculate the size of a folder and set it in the cache
745
+     *
746
+     * @param string $path
747
+     * @param array $entry (optional) meta data of the folder
748
+     * @return int
749
+     */
750
+    public function calculateFolderSize($path, $entry = null) {
751
+        $totalSize = 0;
752
+        if (is_null($entry) or !isset($entry['fileid'])) {
753
+            $entry = $this->get($path);
754
+        }
755
+        if (isset($entry['mimetype']) && $entry['mimetype'] === 'httpd/unix-directory') {
756
+            $id = $entry['fileid'];
757
+            $sql = 'SELECT SUM(`size`) AS f1, MIN(`size`) AS f2 ' .
758
+                'FROM `*PREFIX*filecache` ' .
759
+                'WHERE `parent` = ? AND `storage` = ?';
760
+            $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
761
+            if ($row = $result->fetch()) {
762
+                $result->closeCursor();
763
+                list($sum, $min) = array_values($row);
764
+                $sum = 0 + $sum;
765
+                $min = 0 + $min;
766
+                if ($min === -1) {
767
+                    $totalSize = $min;
768
+                } else {
769
+                    $totalSize = $sum;
770
+                }
771
+                $update = array();
772
+                if ($entry['size'] !== $totalSize) {
773
+                    $update['size'] = $totalSize;
774
+                }
775
+                if (count($update) > 0) {
776
+                    $this->update($id, $update);
777
+                }
778
+            } else {
779
+                $result->closeCursor();
780
+            }
781
+        }
782
+        return $totalSize;
783
+    }
784
+
785
+    /**
786
+     * get all file ids on the files on the storage
787
+     *
788
+     * @return int[]
789
+     */
790
+    public function getAll() {
791
+        $sql = 'SELECT `fileid` FROM `*PREFIX*filecache` WHERE `storage` = ?';
792
+        $result = $this->connection->executeQuery($sql, array($this->getNumericStorageId()));
793
+        $ids = array();
794
+        while ($row = $result->fetch()) {
795
+            $ids[] = $row['fileid'];
796
+        }
797
+        return $ids;
798
+    }
799
+
800
+    /**
801
+     * find a folder in the cache which has not been fully scanned
802
+     *
803
+     * If multiple incomplete folders are in the cache, the one with the highest id will be returned,
804
+     * use the one with the highest id gives the best result with the background scanner, since that is most
805
+     * likely the folder where we stopped scanning previously
806
+     *
807
+     * @return string|bool the path of the folder or false when no folder matched
808
+     */
809
+    public function getIncomplete() {
810
+        $query = $this->connection->prepare('SELECT `path` FROM `*PREFIX*filecache`'
811
+            . ' WHERE `storage` = ? AND `size` = -1 ORDER BY `fileid` DESC', 1);
812
+        $query->execute([$this->getNumericStorageId()]);
813
+        if ($row = $query->fetch()) {
814
+            return $row['path'];
815
+        } else {
816
+            return false;
817
+        }
818
+    }
819
+
820
+    /**
821
+     * get the path of a file on this storage by it's file id
822
+     *
823
+     * @param int $id the file id of the file or folder to search
824
+     * @return string|null the path of the file (relative to the storage) or null if a file with the given id does not exists within this cache
825
+     */
826
+    public function getPathById($id) {
827
+        $sql = 'SELECT `path` FROM `*PREFIX*filecache` WHERE `fileid` = ? AND `storage` = ?';
828
+        $result = $this->connection->executeQuery($sql, array($id, $this->getNumericStorageId()));
829
+        if ($row = $result->fetch()) {
830
+            // Oracle stores empty strings as null...
831
+            if ($row['path'] === null) {
832
+                return '';
833
+            }
834
+            return $row['path'];
835
+        } else {
836
+            return null;
837
+        }
838
+    }
839
+
840
+    /**
841
+     * get the storage id of the storage for a file and the internal path of the file
842
+     * unlike getPathById this does not limit the search to files on this storage and
843
+     * instead does a global search in the cache table
844
+     *
845
+     * @param int $id
846
+     * @deprecated use getPathById() instead
847
+     * @return array first element holding the storage id, second the path
848
+     */
849
+    static public function getById($id) {
850
+        $connection = \OC::$server->getDatabaseConnection();
851
+        $sql = 'SELECT `storage`, `path` FROM `*PREFIX*filecache` WHERE `fileid` = ?';
852
+        $result = $connection->executeQuery($sql, array($id));
853
+        if ($row = $result->fetch()) {
854
+            $numericId = $row['storage'];
855
+            $path = $row['path'];
856
+        } else {
857
+            return null;
858
+        }
859
+
860
+        if ($id = Storage::getStorageId($numericId)) {
861
+            return array($id, $path);
862
+        } else {
863
+            return null;
864
+        }
865
+    }
866
+
867
+    /**
868
+     * normalize the given path
869
+     *
870
+     * @param string $path
871
+     * @return string
872
+     */
873
+    public function normalize($path) {
874
+
875
+        return trim(\OC_Util::normalizeUnicode($path), '/');
876
+    }
877 877
 }
Please login to merge, or discard this patch.