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